@bolloon/bolloon-agent 0.3.16 → 0.3.17

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.
@@ -0,0 +1,248 @@
1
+ /**
2
+ * P2P 工具调用模块
3
+ * 支持文件和本地信息的 P2P 传递
4
+ */
5
+ // 工具类型枚举
6
+ export var P2PToolType;
7
+ (function (P2PToolType) {
8
+ P2PToolType["FILE_TRANSFER"] = "file_transfer";
9
+ P2PToolType["LOCAL_INFO"] = "local_info";
10
+ P2PToolType["SYSTEM_INFO"] = "system_info";
11
+ P2PToolType["FILE_LIST"] = "file_list";
12
+ })(P2PToolType || (P2PToolType = {}));
13
+ // 文件传输
14
+ export async function transferFile(targetDid, fileInfo, messageId) {
15
+ try {
16
+ const payload = {
17
+ type: 'file',
18
+ tool: P2PToolType.FILE_TRANSFER,
19
+ data: {
20
+ name: fileInfo.name,
21
+ size: fileInfo.size,
22
+ mimeType: fileInfo.mimeType,
23
+ content: fileInfo.content
24
+ }
25
+ };
26
+ const res = await fetch('/api/message-p2p', {
27
+ method: 'POST',
28
+ headers: { 'Content-Type': 'application/json' },
29
+ body: JSON.stringify({
30
+ targetDid,
31
+ content: JSON.stringify(payload),
32
+ type: 'file'
33
+ })
34
+ });
35
+ if (res.ok) {
36
+ const data = await res.json();
37
+ return {
38
+ success: true,
39
+ messageId: data.messageId || messageId
40
+ };
41
+ }
42
+ else {
43
+ return { success: false, error: '文件传输失败' };
44
+ }
45
+ }
46
+ catch (e) {
47
+ return { success: false, error: e.message };
48
+ }
49
+ }
50
+ // 本地信息查询
51
+ export async function queryLocalInfo(targetDid, query) {
52
+ try {
53
+ const payload = {
54
+ type: 'info_query',
55
+ tool: P2PToolType.LOCAL_INFO,
56
+ query: query
57
+ };
58
+ const res = await fetch('/api/message-p2p', {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({
62
+ targetDid,
63
+ content: JSON.stringify(payload),
64
+ type: 'ai-dialogue'
65
+ })
66
+ });
67
+ if (res.ok) {
68
+ const data = await res.json();
69
+ return {
70
+ success: true,
71
+ data: data.response,
72
+ messageId: data.messageId
73
+ };
74
+ }
75
+ else {
76
+ return { success: false, error: '信息查询失败' };
77
+ }
78
+ }
79
+ catch (e) {
80
+ return { success: false, error: e.message };
81
+ }
82
+ }
83
+ // 系统信息请求
84
+ export async function getSystemInfo(targetDid) {
85
+ try {
86
+ const payload = {
87
+ type: 'system_info_request',
88
+ tool: P2PToolType.SYSTEM_INFO
89
+ };
90
+ const res = await fetch('/api/message-p2p', {
91
+ method: 'POST',
92
+ headers: { 'Content-Type': 'application/json' },
93
+ body: JSON.stringify({
94
+ targetDid,
95
+ content: JSON.stringify(payload),
96
+ type: 'ai-dialogue'
97
+ })
98
+ });
99
+ if (res.ok) {
100
+ const data = await res.json();
101
+ return {
102
+ success: true,
103
+ data: data.response,
104
+ messageId: data.messageId
105
+ };
106
+ }
107
+ else {
108
+ return { success: false, error: '系统信息获取失败' };
109
+ }
110
+ }
111
+ catch (e) {
112
+ return { success: false, error: e.message };
113
+ }
114
+ }
115
+ // 文件列表请求
116
+ export async function listFiles(targetDid, path = '/') {
117
+ try {
118
+ const payload = {
119
+ type: 'file_list_request',
120
+ tool: P2PToolType.FILE_LIST,
121
+ path: path
122
+ };
123
+ const res = await fetch('/api/message-p2p', {
124
+ method: 'POST',
125
+ headers: { 'Content-Type': 'application/json' },
126
+ body: JSON.stringify({
127
+ targetDid,
128
+ content: JSON.stringify(payload),
129
+ type: 'ai-dialogue'
130
+ })
131
+ });
132
+ if (res.ok) {
133
+ const data = await res.json();
134
+ return {
135
+ success: true,
136
+ data: data.response,
137
+ messageId: data.messageId
138
+ };
139
+ }
140
+ else {
141
+ return { success: false, error: '文件列表获取失败' };
142
+ }
143
+ }
144
+ catch (e) {
145
+ return { success: false, error: e.message };
146
+ }
147
+ }
148
+ // 工具调用入口
149
+ export async function executeP2PTool(request) {
150
+ const { toolName, payload, targetDid } = request;
151
+ if (!targetDid) {
152
+ return { success: false, error: '未指定目标节点' };
153
+ }
154
+ switch (toolName) {
155
+ case P2PToolType.FILE_TRANSFER:
156
+ return transferFile(targetDid, payload.data);
157
+ case P2PToolType.LOCAL_INFO:
158
+ return queryLocalInfo(targetDid, payload.data);
159
+ case P2PToolType.SYSTEM_INFO:
160
+ return getSystemInfo(targetDid);
161
+ case P2PToolType.FILE_LIST:
162
+ return listFiles(targetDid, payload.data?.path || '/');
163
+ default:
164
+ return { success: false, error: `未知工具: ${toolName}` };
165
+ }
166
+ }
167
+ // 本地系统信息获取 (用于响应来自远程的请求)
168
+ export function getLocalSystemInfo() {
169
+ const memUsage = process.memoryUsage();
170
+ const cpuCores = require('os').cpus();
171
+ return {
172
+ platform: process.platform,
173
+ arch: process.arch,
174
+ nodeVersion: process.version,
175
+ memory: {
176
+ total: memUsage.heapTotal,
177
+ free: memUsage.heapTotal - memUsage.heapUsed,
178
+ used: memUsage.heapUsed
179
+ },
180
+ cpu: {
181
+ cores: cpuCores.length,
182
+ model: cpuCores[0]?.model || 'Unknown'
183
+ },
184
+ uptime: process.uptime()
185
+ };
186
+ }
187
+ // 本地文件列表 (用于响应来自远程的请求)
188
+ export function getLocalFileList(dirPath) {
189
+ try {
190
+ // Dynamic import for ESM compatibility
191
+ const pathModule = require('path');
192
+ const fs = require('fs');
193
+ const fullPath = pathModule.resolve(dirPath);
194
+ const files = fs.readdirSync(fullPath);
195
+ const fileList = files.map((name) => {
196
+ const fullFilePath = pathModule.join(fullPath, name);
197
+ let stat;
198
+ try {
199
+ stat = fs.statSync(fullFilePath);
200
+ }
201
+ catch {
202
+ return null;
203
+ }
204
+ return {
205
+ name: name,
206
+ size: stat.size,
207
+ isDirectory: stat.isDirectory(),
208
+ modified: stat.mtime.toISOString()
209
+ };
210
+ }).filter(Boolean);
211
+ return {
212
+ success: true,
213
+ path: fullPath,
214
+ files: fileList
215
+ };
216
+ }
217
+ catch (e) {
218
+ return {
219
+ success: false,
220
+ path: dirPath,
221
+ error: e.message
222
+ };
223
+ }
224
+ }
225
+ // 导出工具名称列表
226
+ export const P2P_TOOLS = [
227
+ {
228
+ name: P2PToolType.FILE_TRANSFER,
229
+ description: '传输文件到远程节点',
230
+ parameters: ['targetDid', 'fileInfo']
231
+ },
232
+ {
233
+ name: P2PToolType.LOCAL_INFO,
234
+ description: '查询远程节点本地信息',
235
+ parameters: ['targetDid', 'query']
236
+ },
237
+ {
238
+ name: P2PToolType.SYSTEM_INFO,
239
+ description: '获取远程节点系统信息',
240
+ parameters: ['targetDid']
241
+ },
242
+ {
243
+ name: P2PToolType.FILE_LIST,
244
+ description: '列出远程节点目录文件',
245
+ parameters: ['targetDid', 'path']
246
+ }
247
+ ];
248
+ console.log('[P2P Tools] 工具模块已加载');
@@ -78,6 +78,13 @@
78
78
  </div>
79
79
 
80
80
  <div class="sidebar-footer">
81
+ <div class="user-avatar" id="user-avatar" title="点击查看 DID 身份">
82
+ <span class="avatar-letter" id="avatar-letter">?</span>
83
+ </div>
84
+ <div class="user-info">
85
+ <span class="user-name" id="user-name">加载中...</span>
86
+ <span class="user-did" id="user-did"></span>
87
+ </div>
81
88
  <div class="agent-status">
82
89
  <span class="status-dot"></span>
83
90
  <span class="status-text">已连接</span>
@@ -2082,9 +2082,7 @@ ${goalDesc}
2082
2082
  // v3 修复: 用 setInterval 替代一次性 setTimeout, 确保分享变更后能持续推送给 peer
2083
2083
  setInterval(v3BroadcastOwn, 30000);
2084
2084
  // 保留 @diap/sdk 的旧实例 (它的 Hyperswarm 实例能帮 P2PDirect 做 DHT bootstrap)
2085
- // 2026-07-04: hyperswarm 4.x 删除了 Discovery.update() (被 .flushed() 替代).
2086
- // @diap/sdk 0.1.10 还调 .update(), 是上游 bug (已记录 docs/plans/2026-06-17-supervisor-iter-1.md).
2087
- // 这里静默 joinTopic 失败, P2PDirect (v3 主路径) 不依赖 @diap/sdk, 不影响功能.
2085
+ // 2026-07-04: @diap/sdk v0.2.0 已修复 seed/*update*/connect 类型, 此处走正常路径.
2088
2086
  try {
2089
2087
  const rawSeed = crypto.getRandomValues(new Uint8Array(32));
2090
2088
  p2pCommunicator = createHyperswarmCommunicator({
@@ -2113,18 +2111,17 @@ ${goalDesc}
2113
2111
  });
2114
2112
  await p2pCommunicator.start();
2115
2113
  // @diap/sdk 也 join topic — 它的 Hyperswarm 实例帮 P2PDirect 做 DHT 引导
2116
- // joinTopic 失败已知 (hyperswarm 4.x + @diap/sdk 0.1.10), catch 静默
2114
+ // v0.2.1 (2026-07-28): hyperswarm seed 类型固定 + join() 返回 { refresh, flushed, destroy }
2115
+ // discovery.update 错误已彻底修复. catch 保留做防御性兜底.
2117
2116
  const oldTopic = createTopic('bolloon-agent-harness');
2118
2117
  try {
2119
2118
  await p2pCommunicator.joinTopic(oldTopic);
2120
2119
  console.log(`P2P 老通道已就绪 (DHT bootstrap 帮 P2PDirect, 实际数据走 P2PDirect)`);
2121
2120
  }
2122
2121
  catch (joinErr) {
2123
- // 已知: @diap/sdk 0.1.10 + hyperswarm 4.x → discovery.update is not a function
2124
- // v3 P2PDirect 是主路径, 此处不阻断
2125
2122
  const msg = String(joinErr?.message || joinErr);
2126
2123
  if (msg.includes('discovery.update') || msg.includes('is not a function')) {
2127
- console.warn(`[v3-legacy] @diap/sdk joinTopic 失败 (已知 hyperswarm 4.x 兼容问题, 已忽略): ${msg}`);
2124
+ console.warn(`[v3-legacy] joinTopic 触发旧版兼容警告: ${msg}`);
2128
2125
  }
2129
2126
  else {
2130
2127
  throw joinErr;
@@ -2223,6 +2220,50 @@ ${goalDesc}
2223
2220
  app.get('/api/tools', (_req, res) => {
2224
2221
  res.json(listTools().map(t => t.id));
2225
2222
  });
2223
+ // 2026-07-28: 用户 DID 身份端点 — 静默生成/加载, 持久化到 ~/.bolloon/identity/user.json
2224
+ let userIdentityCache = null;
2225
+ const IDENTITY_DIR = `${process.env.HOME || '/tmp'}/.bolloon/identity`;
2226
+ async function loadOrCreateUserIdentity() {
2227
+ if (userIdentityCache)
2228
+ return userIdentityCache;
2229
+ try {
2230
+ const { readFileSync, existsSync, mkdirSync, writeFileSync } = await import('fs');
2231
+ const file = `${IDENTITY_DIR}/user.json`;
2232
+ if (existsSync(file)) {
2233
+ const raw = readFileSync(file, 'utf-8');
2234
+ const parsed = JSON.parse(raw);
2235
+ if (parsed.did && parsed.publicKeyHex) {
2236
+ userIdentityCache = parsed;
2237
+ return parsed;
2238
+ }
2239
+ }
2240
+ // 生成新 DID keypair
2241
+ const kp = KeyManager.generate();
2242
+ const didShort = kp.did.split(':').pop()?.substring(0, 8) || 'unknown';
2243
+ const publicKeyHex = Buffer.from(kp.publicKey).toString('hex');
2244
+ const username = getUserName();
2245
+ const identity = {
2246
+ did: kp.did,
2247
+ didShort,
2248
+ publicKeyHex,
2249
+ name: `blln-${username}`,
2250
+ createdAt: new Date().toISOString(),
2251
+ };
2252
+ mkdirSync(IDENTITY_DIR, { recursive: true });
2253
+ writeFileSync(file, JSON.stringify(identity, null, 2), { mode: 0o600 });
2254
+ userIdentityCache = identity;
2255
+ console.log(`[user-identity] ✅ DID: ${kp.did.substring(0, 30)}...`);
2256
+ return identity;
2257
+ }
2258
+ catch (e) {
2259
+ console.warn('[user-identity] 加载失败:', e.message);
2260
+ return { did: '', didShort: 'anon', publicKeyHex: '', name: getUserName() };
2261
+ }
2262
+ }
2263
+ app.get('/api/user/identity', async (_req, res) => {
2264
+ const identity = await loadOrCreateUserIdentity();
2265
+ res.json(identity);
2266
+ });
2226
2267
  // 2026-07-01 (v0.2.6): 前后端分离核心 — 后端切 LLM 输出为结构化 segments
2227
2268
  // - POST /api/segment-reply { reply, knownTools }
2228
2269
  // - 返回 ChatSegment[] (think / text / env_details / tool_call / final)
@@ -822,16 +822,71 @@ body {
822
822
 
823
823
  /* Sidebar Footer */
824
824
  .sidebar-footer {
825
- padding: 16px;
825
+ padding: 12px 16px;
826
826
  border-top: 1px solid var(--border);
827
+ display: flex;
828
+ align-items: center;
829
+ gap: 10px;
827
830
  }
828
831
 
829
- .agent-status {
832
+ /* 用户头像 — DID 身份 */
833
+ .user-avatar {
834
+ width: 32px;
835
+ height: 32px;
836
+ border-radius: 50%;
837
+ background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%);
830
838
  display: flex;
831
839
  align-items: center;
832
- gap: 8px;
840
+ justify-content: center;
841
+ flex-shrink: 0;
842
+ cursor: pointer;
843
+ transition: transform 0.2s, box-shadow 0.2s;
844
+ }
845
+ .user-avatar:hover {
846
+ transform: scale(1.1);
847
+ box-shadow: 0 0 12px var(--accent-glow);
848
+ }
849
+ .avatar-letter {
850
+ font-size: 14px;
851
+ font-weight: 700;
852
+ color: #1a1a18;
853
+ text-transform: uppercase;
854
+ user-select: none;
855
+ }
856
+
857
+ .user-info {
858
+ flex: 1;
859
+ min-width: 0;
860
+ display: flex;
861
+ flex-direction: column;
862
+ gap: 2px;
863
+ }
864
+ .user-name {
833
865
  font-size: 12px;
866
+ font-weight: 600;
867
+ color: var(--text);
868
+ white-space: nowrap;
869
+ overflow: hidden;
870
+ text-overflow: ellipsis;
871
+ line-height: 1.2;
872
+ }
873
+ .user-did {
874
+ font-size: 10px;
875
+ font-family: var(--font-mono);
876
+ color: var(--text-muted);
877
+ white-space: nowrap;
878
+ overflow: hidden;
879
+ text-overflow: ellipsis;
880
+ line-height: 1.2;
881
+ }
882
+
883
+ .agent-status {
884
+ display: flex;
885
+ align-items: center;
886
+ gap: 6px;
887
+ font-size: 11px;
834
888
  color: var(--text-secondary);
889
+ flex-shrink: 0;
835
890
  }
836
891
 
837
892
  .status-dot {