@bolloon/bolloon-agent 0.4.18 → 0.4.20
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/dist/agents/agent-registry.js +24 -0
- package/dist/agents/gateway-network.js +192 -14
- package/dist/agents/network-link.js +33 -0
- package/dist/agents/pi-sdk-tools.js +255 -0
- package/dist/agents/resource-store.js +168 -0
- package/dist/agents/resource-token.js +77 -0
- package/dist/agents/resource-wallet.js +46 -0
- package/dist/index.js +68 -0
- package/dist/web/mobile-agent.js +6 -0
- package/dist/web/mobile-core.js +12533 -149
- package/dist/web/mobile-gateway.js +191 -0
- package/dist/web/mobile.html +7 -0
- package/dist/web/mobile.js +52 -1
- package/dist/web/qr.js +49 -0
- package/package.json +6 -1
|
@@ -13,6 +13,8 @@ import * as path from 'path';
|
|
|
13
13
|
import { getCIDDatabase } from '../orbitdb/cid-database.js';
|
|
14
14
|
const home = () => process.env.HOME || os.homedir() || '/tmp';
|
|
15
15
|
export const REGISTRY_ORBIT_KEY = 'services';
|
|
16
|
+
/** 网络启动包 meta 键 (networkId/name/version/capacityOfMembers/sharedContextCid) */
|
|
17
|
+
export const REGISTRY_ORBIT_META_KEY = 'meta';
|
|
16
18
|
/** OrbitDB store 名 (按 did 短名确定性) */
|
|
17
19
|
export function registryStoreName(did) {
|
|
18
20
|
const short = String(did || 'local').replace(/[^a-zA-Z0-9-]/g, '_').slice(0, 40) || 'local';
|
|
@@ -118,6 +120,28 @@ export class OrbitDBAgentRegistry {
|
|
|
118
120
|
return orbit;
|
|
119
121
|
return this.loadLocalFromDisk();
|
|
120
122
|
}
|
|
123
|
+
async writeMeta(meta) {
|
|
124
|
+
if (!this.ready || !this._store)
|
|
125
|
+
return { ok: false, error: 'registry store 未就绪 (离线模式)' };
|
|
126
|
+
try {
|
|
127
|
+
await this._store.put(REGISTRY_ORBIT_META_KEY, meta);
|
|
128
|
+
return { ok: true };
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
return { ok: false, error: String(e?.message || e).slice(0, 120) };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async readMeta() {
|
|
135
|
+
if (!this.ready || !this._store)
|
|
136
|
+
return null;
|
|
137
|
+
try {
|
|
138
|
+
const v = await this._store.get(REGISTRY_ORBIT_META_KEY);
|
|
139
|
+
return v && typeof v === 'object' ? v : null;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
121
145
|
async discover(query) {
|
|
122
146
|
const all = await this.list();
|
|
123
147
|
const q = String(query || '').trim().toLowerCase();
|
|
@@ -182,30 +182,34 @@ export async function joinNetwork(link, deps) {
|
|
|
182
182
|
// 合并到本地 registry (按 agentId + service.name 去重)
|
|
183
183
|
const registry = deps?.registry ?? getAgentRegistry();
|
|
184
184
|
const local = await registry.list();
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const exists = local.some((l) => l.agentId === svc.agentId && l.service?.name === svc.service?.name);
|
|
190
|
-
if (!exists) {
|
|
185
|
+
const remoteServices = Array.isArray(remote) ? remote : [];
|
|
186
|
+
const { merged, joined } = mergeRemoteServices(remoteServices, local);
|
|
187
|
+
for (const svc of merged) {
|
|
188
|
+
if (!local.some((l) => l.agentId === svc.agentId && l.service?.name === svc.service?.name)) {
|
|
191
189
|
await registry.register(svc).catch(() => { });
|
|
192
|
-
joined++;
|
|
193
190
|
}
|
|
194
191
|
}
|
|
195
|
-
//
|
|
192
|
+
// ① 网络启动包 (meta): 拉网络声明 (networkId/名称/版本/容量/共享context CID), 尽力而为
|
|
193
|
+
const meta = await fetchNetworkMeta(parsed).catch(() => null);
|
|
194
|
+
const bootstrap = buildNetworkBootstrap(meta);
|
|
195
|
+
// 记录成员身份 (持久化 → 重启后自动恢复) + 网络启动包信息
|
|
196
196
|
await saveNetworks([
|
|
197
197
|
...existing,
|
|
198
198
|
{
|
|
199
199
|
link: norm,
|
|
200
200
|
linkKey: identityKey,
|
|
201
201
|
kind: parsed.kind,
|
|
202
|
-
name: parsed.networkName,
|
|
202
|
+
name: bootstrap.name || parsed.networkName,
|
|
203
203
|
joinedAt: new Date().toISOString(),
|
|
204
|
-
serviceCount:
|
|
204
|
+
serviceCount: remoteServices.length,
|
|
205
205
|
lastSyncAt: new Date().toISOString(),
|
|
206
|
+
networkId: bootstrap.networkId,
|
|
207
|
+
version: bootstrap.version,
|
|
208
|
+
capacityOfMembers: bootstrap.capacityOfMembers,
|
|
209
|
+
sharedContextCid: bootstrap.sharedContextCid,
|
|
206
210
|
},
|
|
207
211
|
]);
|
|
208
|
-
return { ok: true, joined, total:
|
|
212
|
+
return { ok: true, joined, total: remoteServices.length, linkKind: parsed.kind, networkName: bootstrap.name || parsed.networkName, networkId: bootstrap.networkId, sharedContextCid: bootstrap.sharedContextCid };
|
|
209
213
|
}
|
|
210
214
|
/** 启动恢复: 重拉所有已加入网络 (失败静默, 保留记录). 返回恢复统计. */
|
|
211
215
|
export async function restoreJoinedNetworks() {
|
|
@@ -228,6 +232,156 @@ export async function restoreJoinedNetworks() {
|
|
|
228
232
|
}
|
|
229
233
|
return { restored, failed, total: nets.length };
|
|
230
234
|
}
|
|
235
|
+
/** 把任意 meta 对象规整成 NetworkBootstrap (纯函数, 容错) */
|
|
236
|
+
export function buildNetworkBootstrap(meta) {
|
|
237
|
+
const m = meta && typeof meta === 'object' ? meta : {};
|
|
238
|
+
const str = (v) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
|
|
239
|
+
return {
|
|
240
|
+
networkId: str(m.networkId),
|
|
241
|
+
name: str(m.name),
|
|
242
|
+
version: str(m.version),
|
|
243
|
+
capacityOfMembers: typeof m.capacityOfMembers === 'number' ? m.capacityOfMembers : undefined,
|
|
244
|
+
sharedContextCid: str(m.sharedContextCid),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/** 拉取网络 meta (尽力而为, 失败返回 null): orbitdb('meta' 键) / ipns(network.json) / http(doc.meta) */
|
|
248
|
+
async function fetchNetworkMeta(parsed) {
|
|
249
|
+
try {
|
|
250
|
+
if (parsed.kind === 'http') {
|
|
251
|
+
const r = await fetch(parsed.url, { signal: AbortSignal.timeout(15000) });
|
|
252
|
+
if (!r.ok)
|
|
253
|
+
return null;
|
|
254
|
+
const d = await r.json();
|
|
255
|
+
return d?.meta ?? null;
|
|
256
|
+
}
|
|
257
|
+
if (parsed.kind === 'ipns') {
|
|
258
|
+
const r = await fetch(`http://127.0.0.1:8080/ipns/${parsed.name}/network.json`, { signal: AbortSignal.timeout(15000) });
|
|
259
|
+
if (!r.ok)
|
|
260
|
+
return null;
|
|
261
|
+
const d = await r.json();
|
|
262
|
+
return d?.meta ?? d;
|
|
263
|
+
}
|
|
264
|
+
if (parsed.kind === 'orbitdb') {
|
|
265
|
+
const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
|
|
266
|
+
const db = getCIDDatabase();
|
|
267
|
+
const store = await db.openStoreByAddress(parsed.address, 'keyvalue');
|
|
268
|
+
if (!store)
|
|
269
|
+
return null;
|
|
270
|
+
const v = await store.get('meta').catch(() => null);
|
|
271
|
+
return v ?? null;
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/** 纯函数: 远端服务并入本地 (按 agentId + service.name 去重), 返回合并结果 + 新增数 */
|
|
280
|
+
export function mergeRemoteServices(remote, local) {
|
|
281
|
+
const out = local.slice();
|
|
282
|
+
let joined = 0;
|
|
283
|
+
for (const svc of remote) {
|
|
284
|
+
if (!svc?.agentId || !svc?.service?.name)
|
|
285
|
+
continue;
|
|
286
|
+
const exists = out.some((l) => l.agentId === svc.agentId && l.service?.name === svc.service?.name);
|
|
287
|
+
if (!exists) {
|
|
288
|
+
out.push(svc);
|
|
289
|
+
joined++;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { merged: out, joined };
|
|
293
|
+
}
|
|
294
|
+
/** ② 拉取网络画像 (只读, 不落盘): 供 on-join 告知 agent "网络里有什么" */
|
|
295
|
+
export async function pullNetworkProfile(link) {
|
|
296
|
+
const parsed = parseNetworkLink(link);
|
|
297
|
+
if (!parsed)
|
|
298
|
+
return null;
|
|
299
|
+
let services = null;
|
|
300
|
+
if (parsed.kind === 'http')
|
|
301
|
+
services = await fetchRemoteRegistry(parsed.url);
|
|
302
|
+
else if (parsed.kind === 'ipns')
|
|
303
|
+
services = await fetchIpnsRegistry(parsed.name);
|
|
304
|
+
else if (parsed.kind === 'orbitdb')
|
|
305
|
+
services = await fetchOrbitdbRegistry(parsed.address);
|
|
306
|
+
const meta = await fetchNetworkMeta(parsed).catch(() => null);
|
|
307
|
+
if (!services || services.length === 0)
|
|
308
|
+
return null;
|
|
309
|
+
return { bootstrap: buildNetworkBootstrap(meta), members: services };
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* ③ 成员自描述广播: 把本机服务声明(list .agentId 匹配 self)写回共享网络 store ('services' 合并 self).
|
|
313
|
+
* ① ipns/http 无回写 → 只本地登记 (返回 note); ② orbitdb replica 只读时写穿失败 → 非致命 (返回 note).
|
|
314
|
+
* opts.members: 本机要广播的成员声明 (通常 = 自己注册的 AgentService 列表).
|
|
315
|
+
*/
|
|
316
|
+
export async function networkShareSelf(link, members, opts) {
|
|
317
|
+
if (!members || members.length === 0)
|
|
318
|
+
return { ok: true, note: '无本机成员声明可广播' };
|
|
319
|
+
const parsed = parseNetworkLink(link);
|
|
320
|
+
if (!parsed)
|
|
321
|
+
return { ok: false, error: '链接无法解析' };
|
|
322
|
+
if (parsed.kind !== 'orbitdb') {
|
|
323
|
+
return { ok: true, note: `${parsed.kind} 无回写能力, 已本地登记 (发起方自托管 registry 应含本机)` };
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
|
|
327
|
+
const db = getCIDDatabase();
|
|
328
|
+
const store = await db.openStoreByAddress(parsed.address, 'keyvalue');
|
|
329
|
+
if (!store)
|
|
330
|
+
return { ok: false, error: '网络 store 不可达' };
|
|
331
|
+
const cur = (await store.get('services').catch(() => null));
|
|
332
|
+
const arr = Array.isArray(cur) ? cur.slice() : [];
|
|
333
|
+
for (const m of members) {
|
|
334
|
+
const i = arr.findIndex((s) => s.agentId === m.agentId && s.service?.name === m.service?.name);
|
|
335
|
+
const entry = { ...m, updatedAt: new Date().toISOString(), registeredAt: m.registeredAt || new Date().toISOString() };
|
|
336
|
+
if (i >= 0)
|
|
337
|
+
arr[i] = entry;
|
|
338
|
+
else
|
|
339
|
+
arr.push(entry);
|
|
340
|
+
}
|
|
341
|
+
await store.put('services', arr);
|
|
342
|
+
return { ok: true, note: `已广播 ${members.length} 条本机声明到网络` };
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
return { ok: false, error: `写穿失败(疑似只读 replica): ${String(e?.message || e).slice(0, 120)}`, note: '已在本地登记, 可在你的分享链接中带上本机' };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* ① 拉取共享 context (由启动包 sharedContextCid 指向): 从本地 IPFS 网关读文本返回,
|
|
350
|
+
* 调用方负责注入本地 context (了解"所有信息"的最后一段).
|
|
351
|
+
*/
|
|
352
|
+
export async function pullNetworkSharedContext(cid) {
|
|
353
|
+
try {
|
|
354
|
+
const r = await fetch(`http://127.0.0.1:8080/ipfs/${cid}`, { signal: AbortSignal.timeout(15000) });
|
|
355
|
+
if (!r.ok)
|
|
356
|
+
return null;
|
|
357
|
+
return await r.text();
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* ① 发布共享 context 到 OrbitDB (内容寻址), 返回 CID — 供 shareNetworkLink 的 sharedContextCid,
|
|
365
|
+
* 手机/PC 入网后拉取即"近期上下文同步". opts.share=true 时把块放入 helia blockstore 供网络拉取.
|
|
366
|
+
*/
|
|
367
|
+
export async function publishNetworkSharedContext(text, opts) {
|
|
368
|
+
try {
|
|
369
|
+
const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
|
|
370
|
+
const db = getCIDDatabase();
|
|
371
|
+
const rec = await db.save({ agentId: opts?.agentId || 'bolloon-network', type: 'context', content: text });
|
|
372
|
+
let cid = rec.id;
|
|
373
|
+
if (opts?.share !== false) {
|
|
374
|
+
try {
|
|
375
|
+
cid = await db.share(cid);
|
|
376
|
+
}
|
|
377
|
+
catch { /* 分享失败仍可用 CID 读 */ }
|
|
378
|
+
}
|
|
379
|
+
return { ok: true, cid };
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
return { ok: false, error: String(e?.message || e).slice(0, 140) };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
231
385
|
// ============ 分享链接 ============
|
|
232
386
|
/**
|
|
233
387
|
* 生成本机可分享的网络链接: orbitdb://<registry storeAddress>?name=<网络名>.
|
|
@@ -244,8 +398,24 @@ export async function shareNetworkLink(opts) {
|
|
|
244
398
|
if (!registry.ready || !registry.storeAddress) {
|
|
245
399
|
return { ok: false, error: 'OrbitDB registry 未就绪 (离线模式). 备选: 把 registry 列表发布成 https://.../registry 端点分享' };
|
|
246
400
|
}
|
|
247
|
-
|
|
248
|
-
|
|
401
|
+
// ① 全量启动包: 把 networkId/名称/版本/容量/共享context CID 写进共享 store (尽力而为, 失败仍分享)
|
|
402
|
+
const networkId = opts?.meta?.networkId || registry.storeName;
|
|
403
|
+
const name = String(opts?.name || opts?.meta?.name || registry.storeName || 'bolloon-network');
|
|
404
|
+
const metaObj = {
|
|
405
|
+
networkId,
|
|
406
|
+
name,
|
|
407
|
+
version: opts?.meta?.version,
|
|
408
|
+
capacityOfMembers: opts?.meta?.capacityOfMembers,
|
|
409
|
+
sharedContextCid: opts?.meta?.sharedContextCid,
|
|
410
|
+
};
|
|
411
|
+
let note;
|
|
412
|
+
if (registry.writeMeta) {
|
|
413
|
+
const w = await registry.writeMeta(metaObj).catch(() => ({ ok: false, error: 'writeMeta 异常' }));
|
|
414
|
+
if (!w.ok)
|
|
415
|
+
note = `meta 写共享库失败 (${w.error || '未知'}) — 读方仍可拉到服务, 但拿不到启动包`;
|
|
416
|
+
}
|
|
417
|
+
const encodedName = encodeURIComponent(name);
|
|
418
|
+
return { ok: true, link: `orbitdb://${registry.storeAddress}?name=${encodedName}`, note };
|
|
249
419
|
}
|
|
250
420
|
catch (e) {
|
|
251
421
|
return { ok: false, error: `生成分享链接失败: ${String(e?.message || e).slice(0, 160)}` };
|
|
@@ -268,7 +438,15 @@ export async function maybeAutoJoinGateway(text, deps) {
|
|
|
268
438
|
return null; // 已在网络, 静默
|
|
269
439
|
if (r.ok) {
|
|
270
440
|
const netName = r.networkName ? `「${r.networkName}」` : ''; // URLSearchParams 已 decode
|
|
271
|
-
|
|
441
|
+
// ② on-join 广播: 写入自己的服务声明 (orbitdb 可回写时), 非致命
|
|
442
|
+
let selfNote = '';
|
|
443
|
+
if (deps?.self && deps.self.length > 0) {
|
|
444
|
+
const s = await networkShareSelf(link, deps.self, deps).catch(() => null);
|
|
445
|
+
selfNote = s?.ok ? ' · 已广播本机声明' : (s?.note ? ` · ${s.note}` : ' · 本机声明广播失败');
|
|
446
|
+
}
|
|
447
|
+
const bootPart = r.networkId ? ` (net=${String(r.networkId).slice(0, 16)})` : '';
|
|
448
|
+
const ctxPart = r.sharedContextCid ? ` · 共享ctx:${String(r.sharedContextCid).slice(0, 12)}` : '';
|
|
449
|
+
return `🆕 已自动加入 Agent 网络${netName}${bootPart} (${r.linkKind}): 拉取 ${r.total} 个服务, 新增 ${r.joined} 个${ctxPart}${selfNote}。用 gateway_status 查看, gateway_call 调用网络里的服务。`;
|
|
272
450
|
}
|
|
273
451
|
return `⚠️ 检测到 Agent 网络链接 (${r.linkKind || 'unknown'}) 但加入失败: ${r.error}`;
|
|
274
452
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// ─── 网络链接纯函数 (无 Node 依赖, 桌面/手机浏览器共用) ────────────────────
|
|
2
|
+
// parseNetworkLink / detectGatewayLink 从 gateway-network.ts 抽出,
|
|
3
|
+
// 供 browser 端 (mobile-gateway) 复用, 不拖 OrbitDB/fs 进 WebView.
|
|
4
|
+
/** 解析链接字符串 → ParsedNetworkLink (剥离 ?name= query) */
|
|
5
|
+
export function parseNetworkLink(link) {
|
|
6
|
+
const l = String(link || '').trim();
|
|
7
|
+
if (!l)
|
|
8
|
+
return null;
|
|
9
|
+
const [base, query] = l.split('?');
|
|
10
|
+
let networkName;
|
|
11
|
+
try {
|
|
12
|
+
networkName = query ? (new URLSearchParams(query).get('name') || undefined) : undefined;
|
|
13
|
+
}
|
|
14
|
+
catch { /* 忽略坏 query */ }
|
|
15
|
+
if (base.startsWith('ipns://'))
|
|
16
|
+
return { kind: 'ipns', value: base.slice('ipns://'.length), url: base, networkName };
|
|
17
|
+
if (base.startsWith('orbitdb://')) {
|
|
18
|
+
let address = base.slice('orbitdb://'.length);
|
|
19
|
+
if (!address.startsWith('/'))
|
|
20
|
+
address = `/${address}`;
|
|
21
|
+
return { kind: 'orbitdb', value: address, url: base, networkName };
|
|
22
|
+
}
|
|
23
|
+
if (base.startsWith('http://') || base.startsWith('https://'))
|
|
24
|
+
return { kind: 'http', value: base, url: base, networkName };
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/** 从消息文本检测 gateway 链接 (自动加入触发器用) */
|
|
28
|
+
export function detectGatewayLink(text) {
|
|
29
|
+
const t = String(text || '');
|
|
30
|
+
const re = /(orbitdb:\/\/\/?orbitdb\/[^\s)'"<>,。;]+|ipns:\/\/[^\s)'"<>,。;]+|https?:\/\/[^\s)'"<>,。;]*\/registry(?:\?[^\s)'"<>,。;]*)?)/;
|
|
31
|
+
const m = re.exec(t);
|
|
32
|
+
return m ? m[1].trim() : null;
|
|
33
|
+
}
|
|
@@ -2083,6 +2083,261 @@ export function registerBuiltinTools(ctx) {
|
|
|
2083
2083
|
}
|
|
2084
2084
|
});
|
|
2085
2085
|
// ============================================================
|
|
2086
|
+
// 数字资源资产化 (Stage 1-4): 注册→运营(发现/匹配)→交易(x402)→清算(信誉)
|
|
2087
|
+
// ============================================================
|
|
2088
|
+
ctx.tools.set('resource_register', {
|
|
2089
|
+
name: 'resource_register',
|
|
2090
|
+
description: '注册一个数字资源 (A 轻版: 内容CID寻址 + 元数据同步网络 registry; 预留 chain=evm 可升级 tokenURI). type: data|art_product|product_image|tx_link; 内容寻址存 content, 定价/授权/交易链接/收款钱包进资源记录, 供网络内智能体 resource_discover/match/purchase 发现并交易.',
|
|
2091
|
+
parameters: {
|
|
2092
|
+
type: 'data | art_product | product_image | tx_link',
|
|
2093
|
+
title: '资源标题',
|
|
2094
|
+
content: '资源内容 (文本/JSON; 图片 base64 dataURL)',
|
|
2095
|
+
wallet: '可选收款钱包地址 (Stage 3 交易用)',
|
|
2096
|
+
price: '可选 {amount,currency:USDC|token,token?,per?} 字符串或JSON',
|
|
2097
|
+
license: '可选授权 (personal/commercial)',
|
|
2098
|
+
txLink: '可选交易链接',
|
|
2099
|
+
chain: '可选 none|evm (预留升级)',
|
|
2100
|
+
tokenUriTemplate: '可选 evm 时 tokenURI 模板, 如 {base}/{id}',
|
|
2101
|
+
},
|
|
2102
|
+
execute: async (args) => {
|
|
2103
|
+
try {
|
|
2104
|
+
const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
|
|
2105
|
+
const db = getCIDDatabase();
|
|
2106
|
+
const cidd = {
|
|
2107
|
+
save: async (t) => (await db.save({ agentId: String(ctx.agentId || ''), type: 'knowledge', content: t })).id,
|
|
2108
|
+
load: async (c) => { const rec = await db.load(c); return rec ? String(rec.content ?? '') : null; },
|
|
2109
|
+
};
|
|
2110
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2111
|
+
const { registerResource } = await import('./resource-store.js');
|
|
2112
|
+
let price;
|
|
2113
|
+
if (args.price) {
|
|
2114
|
+
try {
|
|
2115
|
+
price = typeof args.price === 'string' ? JSON.parse(args.price) : args.price;
|
|
2116
|
+
}
|
|
2117
|
+
catch {
|
|
2118
|
+
price = undefined;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
const { loadOrCreateWallet } = await import('./resource-wallet.js');
|
|
2122
|
+
const nw = await loadOrCreateWallet().catch(() => null);
|
|
2123
|
+
const walletAddr = nw?.address || '';
|
|
2124
|
+
const r = await registerResource({
|
|
2125
|
+
ownerDid: String(ctx.agentId || ''),
|
|
2126
|
+
type: String(args.type || ''),
|
|
2127
|
+
title: String(args.title || ''),
|
|
2128
|
+
content: String(args.content || ''),
|
|
2129
|
+
wallet: args.wallet ? String(args.wallet) : walletAddr || undefined,
|
|
2130
|
+
price,
|
|
2131
|
+
license: args.license ? String(args.license) : undefined,
|
|
2132
|
+
txLink: args.txLink ? String(args.txLink) : undefined,
|
|
2133
|
+
chain: args.chain,
|
|
2134
|
+
tokenUriTemplate: args.tokenUriTemplate ? String(args.tokenUriTemplate) : undefined,
|
|
2135
|
+
}, { cid: cidd, registry: getAgentRegistry() });
|
|
2136
|
+
if (!r.ok)
|
|
2137
|
+
return { success: false, error: r.error };
|
|
2138
|
+
const res = r.resource;
|
|
2139
|
+
return { success: true, output: `✅ 资源已注册 (A 轻版上链): ${res.resourceId}\n ${res.title} · ${res.type} · CID: ${res.contentCid}${res.wallet ? ` · 钱包 ${res.wallet}` : ''}${res.price ? ` · 价格 ${res.price.amount} ${res.price.currency}${res.price.token ? `(${res.price.token})` : ''}` : ''}${res.license ? ` · ${res.license}` : ''}${res.txLink ? ` · 交易:${res.txLink}` : ''}${res.chain === 'evm' ? ` · ${res.chain}` : ''}\n已同步网络 registry: 智能体可用 resource_discover/match 找到, resource_purchase 交易/访问。` };
|
|
2140
|
+
}
|
|
2141
|
+
catch (e) {
|
|
2142
|
+
return { success: false, error: `resource_register 失败: ${String(e).slice(0, 200)}` };
|
|
2143
|
+
}
|
|
2144
|
+
},
|
|
2145
|
+
});
|
|
2146
|
+
ctx.tools.set('resource_discover', {
|
|
2147
|
+
name: 'resource_discover',
|
|
2148
|
+
description: '发现数字资源 (本机 + 网络 registry): 按 type 或 owner 列出 (谁注册了什么, 定价/授权/交易链接), 供分配/交易决策.',
|
|
2149
|
+
parameters: { type: '可选过滤 type', owner: '可选过滤 owner DID' },
|
|
2150
|
+
execute: async (args) => {
|
|
2151
|
+
try {
|
|
2152
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2153
|
+
const { listResources } = await import('./resource-store.js');
|
|
2154
|
+
const list = await listResources({
|
|
2155
|
+
type: args.type ? String(args.type) : undefined,
|
|
2156
|
+
owner: args.owner ? String(args.owner) : undefined,
|
|
2157
|
+
}, { registry: getAgentRegistry() });
|
|
2158
|
+
if (!list.length)
|
|
2159
|
+
return { success: true, output: '暂无资源 (用 resource_register 注册)' };
|
|
2160
|
+
return { success: true, output: list.map((r) => `${r.resourceId} · ${r.title} · ${r.type}${r.price ? ` · ${r.price.amount} ${r.price.currency}${r.price.token ? `(${r.price.token})` : ''}${r.price.per ? `/${r.price.per}` : ''}` : ''}${r.license ? ` · ${r.license}` : ''}${r.txLink ? ` · 🔗${r.txLink}` : ''}${r.wallet ? ` · 钱包 ${String(r.wallet).slice(0, 10)}…` : ''}${r.chain === 'evm' ? ' · evm' : ''}`).join('\n') + '\n(resource_purchase <id> 交易/访问)' };
|
|
2161
|
+
}
|
|
2162
|
+
catch (e) {
|
|
2163
|
+
return { success: false, error: `resource_discover 失败: ${String(e).slice(0, 200)}` };
|
|
2164
|
+
}
|
|
2165
|
+
},
|
|
2166
|
+
});
|
|
2167
|
+
ctx.tools.set('resource_match', {
|
|
2168
|
+
name: 'resource_match',
|
|
2169
|
+
description: '自动分配匹配: 按需求关键词对资源打分 (标题/类型/授权 + 提供者信誉加权), 返回推荐排序. 用于智能体自动分配资源.',
|
|
2170
|
+
parameters: { query: '需求关键词' },
|
|
2171
|
+
execute: async (args) => {
|
|
2172
|
+
try {
|
|
2173
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2174
|
+
const { queryReputation } = await import('./agent-reputation.js');
|
|
2175
|
+
const registry = getAgentRegistry();
|
|
2176
|
+
const { matchResources } = await import('./resource-store.js');
|
|
2177
|
+
const repQuery = async (owner, type) => {
|
|
2178
|
+
try {
|
|
2179
|
+
const q = await queryReputation(owner, type, registry);
|
|
2180
|
+
const e = q.entries?.[0];
|
|
2181
|
+
return { score: e?.reputation?.score ?? 0, tasks: e?.reputation?.tasks ?? 0, success: e?.reputation?.success ?? 0, failed: e?.reputation?.failed ?? 0 };
|
|
2182
|
+
}
|
|
2183
|
+
catch {
|
|
2184
|
+
return { score: 0, tasks: 0, success: 0, failed: 0 };
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
const scored = await matchResources(String(args.query || ''), { registry, repQuery });
|
|
2188
|
+
if (!scored.length)
|
|
2189
|
+
return { success: true, output: '无匹配资源' };
|
|
2190
|
+
return { success: true, output: scored.map((s) => `${s.resource.resourceId} · ${s.resource.title} · ${s.resource.type} · 得分 ${Math.round(s.score * 10) / 10}${s.resource.license ? ` · ${s.resource.license}` : ''}`).join('\n') };
|
|
2191
|
+
}
|
|
2192
|
+
catch (e) {
|
|
2193
|
+
return { success: false, error: `resource_match 失败: ${String(e).slice(0, 200)}` };
|
|
2194
|
+
}
|
|
2195
|
+
},
|
|
2196
|
+
});
|
|
2197
|
+
ctx.tools.set('resource_purchase', {
|
|
2198
|
+
name: 'resource_purchase',
|
|
2199
|
+
description: '交易/访问资源: 付费资源走 x402 支付后解锁内容 (结算更新提供者信誉), 免费资源直接取回内容.',
|
|
2200
|
+
parameters: { resourceId: '资源 id' },
|
|
2201
|
+
execute: async (args) => {
|
|
2202
|
+
try {
|
|
2203
|
+
const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
|
|
2204
|
+
const db = getCIDDatabase();
|
|
2205
|
+
const cidd = {
|
|
2206
|
+
save: async (t) => (await db.save({ agentId: String(ctx.agentId || ''), type: 'knowledge', content: t })).id,
|
|
2207
|
+
load: async (c) => { const rec = await db.load(c); return rec ? String(rec.content ?? '') : null; },
|
|
2208
|
+
};
|
|
2209
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2210
|
+
const { queryReputation } = await import('./agent-reputation.js');
|
|
2211
|
+
const registry = getAgentRegistry();
|
|
2212
|
+
const { purchaseResource } = await import('./resource-store.js');
|
|
2213
|
+
const pay = async (spec) => {
|
|
2214
|
+
try {
|
|
2215
|
+
const { x402Pay } = await import('./x402/x402Pay.js');
|
|
2216
|
+
const { loadOrCreateWallet } = await import('./resource-wallet.js');
|
|
2217
|
+
const w = await loadOrCreateWallet().catch(() => null);
|
|
2218
|
+
if (!w?.privateKey)
|
|
2219
|
+
return { success: false, error: '需自动生成 x402 钱包 (~/.bolloon/wallet.json) 才能购买付费资源' };
|
|
2220
|
+
const r = await x402Pay({ privateKey: w.privateKey, amount: String(spec.amount), to: spec.recipient, currency: spec.currency === 'USDC' ? 'USDC' : 'ETH', memo: spec.memo });
|
|
2221
|
+
return { success: r.success, txHash: r.txHash, error: r.error };
|
|
2222
|
+
}
|
|
2223
|
+
catch (e) {
|
|
2224
|
+
return { success: false, error: `x402 支付失败: ${String(e?.message || e).slice(0, 120)}` };
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
const repQuery = async (owner, type) => {
|
|
2228
|
+
try {
|
|
2229
|
+
const q = await queryReputation(owner, type, registry);
|
|
2230
|
+
const e = q.entries?.[0];
|
|
2231
|
+
return { score: e?.reputation?.score ?? 0, tasks: e?.reputation?.tasks ?? 0, success: e?.reputation?.success ?? 0, failed: e?.reputation?.failed ?? 0 };
|
|
2232
|
+
}
|
|
2233
|
+
catch {
|
|
2234
|
+
return { score: 0, tasks: 0, success: 0, failed: 0 };
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
const onSettle = (outcome, resource) => {
|
|
2238
|
+
void import('./agent-reputation.js').then((m) => m.recordServiceOutcome(resource.ownerDid, `resource:${resource.type}`, outcome, registry)).catch(() => { });
|
|
2239
|
+
};
|
|
2240
|
+
const r = await purchaseResource(String(args.resourceId || ''), { cid: cidd, pay, repQuery, onSettle });
|
|
2241
|
+
if (!r.ok)
|
|
2242
|
+
return { success: false, error: r.error || (r.needPay ? '需要支付' : '失败') };
|
|
2243
|
+
return { success: true, output: `${r.txHash ? `💰 已支付 (tx ${String(r.txHash).slice(0, 16)}…) · ` : ''}📄 ${r.resource.title} (${r.resource.type})\n${String(r.content || '').slice(0, 3000)}` };
|
|
2244
|
+
}
|
|
2245
|
+
catch (e) {
|
|
2246
|
+
return { success: false, error: `resource_purchase 失败: ${String(e).slice(0, 200)}` };
|
|
2247
|
+
}
|
|
2248
|
+
},
|
|
2249
|
+
});
|
|
2250
|
+
ctx.tools.set('resource_reputation', {
|
|
2251
|
+
name: 'resource_reputation',
|
|
2252
|
+
description: '清算/信誉: 查资源提供者信誉 (成交记录+score), 供自动分配加权决策.',
|
|
2253
|
+
parameters: { owner: 'owner DID (缺省=本机)' },
|
|
2254
|
+
execute: async (args) => {
|
|
2255
|
+
try {
|
|
2256
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2257
|
+
const { queryReputation } = await import('./agent-reputation.js');
|
|
2258
|
+
const registry = getAgentRegistry();
|
|
2259
|
+
const owner = String(args.owner || ctx.agentId || '');
|
|
2260
|
+
const q = await queryReputation(owner, undefined, registry);
|
|
2261
|
+
if (!q.entries?.length)
|
|
2262
|
+
return { success: true, output: `${owner.slice(0, 16)}… 暂无信誉记录` };
|
|
2263
|
+
return { success: true, output: q.entries.map((e) => ` ${e.service} · ${e.reputation.score} (${e.reputation.success}/${e.reputation.tasks} 成交)`).join('\n') };
|
|
2264
|
+
}
|
|
2265
|
+
catch (e) {
|
|
2266
|
+
return { success: false, error: `resource_reputation 失败: ${String(e).slice(0, 200)}` };
|
|
2267
|
+
}
|
|
2268
|
+
},
|
|
2269
|
+
});
|
|
2270
|
+
// ── Stage 1-B: EVM 资产合约 (ERC-721, CID 作 tokenURI) 铸造/流转/查询 ──
|
|
2271
|
+
ctx.tools.set('resource_mint', {
|
|
2272
|
+
name: 'resource_mint',
|
|
2273
|
+
description: '把已注册数字资源铸成 ERC-721 token (内容 CID 作 tokenURI, 不可变随 token 流转). 需配置 EVM executor (部署 contracts/ResourceERC721.sol + 设 __bolloonEvmExecutor 或用 ethers/viem).',
|
|
2274
|
+
parameters: { resourceId: '资源 id' },
|
|
2275
|
+
execute: async (args) => {
|
|
2276
|
+
try {
|
|
2277
|
+
const { getResource } = await import('./resource-store.js');
|
|
2278
|
+
const { mintResourceToken } = await import('./resource-token.js');
|
|
2279
|
+
const r = await getResource(String(args.resourceId || ''));
|
|
2280
|
+
if (!r)
|
|
2281
|
+
return { success: false, error: '资源不存在' };
|
|
2282
|
+
const evm = globalThis.__bolloonEvmExecutor;
|
|
2283
|
+
if (!evm)
|
|
2284
|
+
return { success: false, error: '需配置 EVM executor (__bolloonEvmExecutor) 才能铸造. 部署 contracts/ResourceERC721.sol + 配置 ~/.bolloon/evm-config.json / ethers.' };
|
|
2285
|
+
const { walletAddress } = await import('./resource-wallet.js');
|
|
2286
|
+
const naddr = await walletAddress().catch(() => '');
|
|
2287
|
+
const to = r.wallet || naddr;
|
|
2288
|
+
if (!to)
|
|
2289
|
+
return { success: false, error: '无收款钱包 (部署后设 __bolloonEvmExecutor + 资源 wallet)' };
|
|
2290
|
+
const m = await mintResourceToken({ resourceId: r.resourceId, ownerDid: r.ownerDid, contentCid: r.contentCid, wallet: to, title: r.title }, { evm });
|
|
2291
|
+
if (!m.ok)
|
|
2292
|
+
return { success: false, error: m.error || (m.needConfig ? '需配置 EVM' : '铸造失败') };
|
|
2293
|
+
return { success: true, output: `✅ 已铸造 tokenId=${m.tokenId} · tokenURI=ipfs://${r.contentCid} · 资源 ${r.resourceId}\n(resource_transfer <tokenId> <to> 流转 · resource_token <tokenId> 查询)` };
|
|
2294
|
+
}
|
|
2295
|
+
catch (e) {
|
|
2296
|
+
return { success: false, error: `resource_mint 失败: ${String(e).slice(0, 200)}` };
|
|
2297
|
+
}
|
|
2298
|
+
},
|
|
2299
|
+
});
|
|
2300
|
+
ctx.tools.set('resource_transfer', {
|
|
2301
|
+
name: 'resource_transfer',
|
|
2302
|
+
description: '流转 ERC-721 token 所有权 (safeTransferFrom) 到新地址.',
|
|
2303
|
+
parameters: { tokenId: 'token id', to: '接收 EVM 地址' },
|
|
2304
|
+
execute: async (args) => {
|
|
2305
|
+
try {
|
|
2306
|
+
const { transferResourceToken } = await import('./resource-token.js');
|
|
2307
|
+
const evm = globalThis.__bolloonEvmExecutor;
|
|
2308
|
+
if (!evm)
|
|
2309
|
+
return { success: false, error: '需配置 EVM executor (__bolloonEvmExecutor)' };
|
|
2310
|
+
const r = await transferResourceToken(String(args.tokenId || ''), String(args.to || ''), { evm });
|
|
2311
|
+
if (!r.ok)
|
|
2312
|
+
return { success: false, error: r.error || '流转失败' };
|
|
2313
|
+
return { success: true, output: `✅ 已流转 token ${r.tokenId} → ${args.to}` };
|
|
2314
|
+
}
|
|
2315
|
+
catch (e) {
|
|
2316
|
+
return { success: false, error: `resource_transfer 失败: ${String(e).slice(0, 200)}` };
|
|
2317
|
+
}
|
|
2318
|
+
},
|
|
2319
|
+
});
|
|
2320
|
+
ctx.tools.set('resource_token', {
|
|
2321
|
+
name: 'resource_token',
|
|
2322
|
+
description: '查询 ERC-721 token: owner + tokenURI (内容 CID).',
|
|
2323
|
+
parameters: { tokenId: 'token id' },
|
|
2324
|
+
execute: async (args) => {
|
|
2325
|
+
try {
|
|
2326
|
+
const { queryResourceToken } = await import('./resource-token.js');
|
|
2327
|
+
const evm = globalThis.__bolloonEvmExecutor;
|
|
2328
|
+
if (!evm)
|
|
2329
|
+
return { success: false, error: '需配置 EVM executor (__bolloonEvmExecutor)' };
|
|
2330
|
+
const r = await queryResourceToken(String(args.tokenId || ''), { evm });
|
|
2331
|
+
if (!r.ok)
|
|
2332
|
+
return { success: false, error: r.error || '查询失败' };
|
|
2333
|
+
return { success: true, output: `🔎 token ${args.tokenId}\n owner: ${r.owner || 'n/a'}\n tokenURI: ${r.tokenUri || 'n/a'}` };
|
|
2334
|
+
}
|
|
2335
|
+
catch (e) {
|
|
2336
|
+
return { success: false, error: `resource_token 失败: ${String(e).slice(0, 200)}` };
|
|
2337
|
+
}
|
|
2338
|
+
},
|
|
2339
|
+
});
|
|
2340
|
+
// ============================================================
|
|
2086
2341
|
// MCP 工具 (2026-08-03) — 外部 MCP server 接入 agent 工具系统
|
|
2087
2342
|
// 配置: ~/.mcp.json (mcpServers), 启动时 initializeMcpAdapter 自动握手发现工具
|
|
2088
2343
|
// mcp_list_tools: 列出已发现的 MCP 工具
|