@bolloon/bolloon-agent 0.4.10 → 0.4.12
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/LICENSE +21 -0
- package/README.md +14 -8
- package/dist/agents/agent-registry.js +151 -0
- package/dist/agents/agent-reputation.js +54 -0
- package/dist/agents/agent-service-client.js +121 -0
- package/dist/agents/economic-policy.js +132 -0
- package/dist/agents/pi-sdk-tools.js +203 -0
- package/dist/pi-ecosystem-a2ui/index.js +81 -0
- package/dist/web/a2ui-client.js +32697 -0
- package/dist/web/mobile.html +4 -0
- package/dist/web/server.js +47 -0
- package/package.json +3 -1
- package/scripts/build-web.ts +15 -0
|
@@ -2243,6 +2243,209 @@ export function registerBuiltinTools(ctx) {
|
|
|
2243
2243
|
}
|
|
2244
2244
|
};
|
|
2245
2245
|
registerUiAgentTools().catch(() => { });
|
|
2246
|
+
// 2026-08-12: A2UI (Agent to UI) 工具 — agent 生成 A2UI 消息 (createSurface/updateComponents),
|
|
2247
|
+
// 经 SSE 广播, 前端用 @a2ui/react renderer 渲染. 见 src/pi-ecosystem-a2ui/.
|
|
2248
|
+
(async () => {
|
|
2249
|
+
try {
|
|
2250
|
+
const a2ui = await import('../pi-ecosystem-a2ui/index.js');
|
|
2251
|
+
for (const t of a2ui.A2UI_TOOL_DEFS) {
|
|
2252
|
+
ctx.tools.set(t.name, {
|
|
2253
|
+
name: t.name,
|
|
2254
|
+
description: t.description,
|
|
2255
|
+
parameters: t.params,
|
|
2256
|
+
execute: async (args) => {
|
|
2257
|
+
const r = a2ui.dispatchA2uiMessage(t.build(args));
|
|
2258
|
+
return r.success ? { success: true, output: r.output } : { success: false, error: r.output };
|
|
2259
|
+
},
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
catch { /* A2UI 工具注册失败静默 */ }
|
|
2264
|
+
})();
|
|
2265
|
+
// 2026-08-13 (Phase E1): Agent 服务 Registry — 注册/发现服务 (Agent Economic Network Discovery 层)
|
|
2266
|
+
(async () => {
|
|
2267
|
+
try {
|
|
2268
|
+
const registry = await import('../agents/agent-registry.js');
|
|
2269
|
+
// 注册自己为服务提供者
|
|
2270
|
+
ctx.tools.set('registry_register', {
|
|
2271
|
+
name: 'registry_register',
|
|
2272
|
+
description: '在 Agent 服务注册表注册自己的服务 (定价/能力/钱包). 让其他 Agent 能发现并调用你. 参数: service_name(如 research), description, price_amount, price_currency(USDC), price_per(如 query), capabilities(数组), wallet(收款地址)',
|
|
2273
|
+
parameters: {
|
|
2274
|
+
service_name: '服务名 (必填, 如 research/coding/data)',
|
|
2275
|
+
description: '服务描述 (必填)',
|
|
2276
|
+
price_amount: '单价金额 (必填, 如 0.05)',
|
|
2277
|
+
price_currency: '计价币种 (默认 USDC)',
|
|
2278
|
+
price_per: '计价单位 (默认 query)',
|
|
2279
|
+
capabilities: '能力数组 (可选)',
|
|
2280
|
+
wallet: '收款钱包地址 (必填)',
|
|
2281
|
+
},
|
|
2282
|
+
execute: async (args) => {
|
|
2283
|
+
const serviceName = String(args.service_name || '').trim();
|
|
2284
|
+
const wallet = String(args.wallet || '').trim();
|
|
2285
|
+
if (!serviceName || !wallet)
|
|
2286
|
+
return { success: false, error: 'service_name 和 wallet 必填' };
|
|
2287
|
+
const reg = registry.getAgentRegistry();
|
|
2288
|
+
const r = await reg.register({
|
|
2289
|
+
agentId: ctx.identity?.did || `did:local:${ctx.identity?.name || 'agent'}`,
|
|
2290
|
+
name: ctx.identity?.name || 'agent',
|
|
2291
|
+
wallet,
|
|
2292
|
+
service: {
|
|
2293
|
+
name: serviceName,
|
|
2294
|
+
description: String(args.description || `${serviceName} 服务`).trim(),
|
|
2295
|
+
price: {
|
|
2296
|
+
amount: String(args.price_amount || '0'),
|
|
2297
|
+
currency: String(args.price_currency || 'USDC').toUpperCase(),
|
|
2298
|
+
per: String(args.price_per || 'query'),
|
|
2299
|
+
},
|
|
2300
|
+
},
|
|
2301
|
+
capabilities: Array.isArray(args.capabilities) ? args.capabilities.map(String) : [serviceName],
|
|
2302
|
+
});
|
|
2303
|
+
return r.ok
|
|
2304
|
+
? { success: true, output: `✅ 已注册服务: ${serviceName} (${args.price_amount || 0} ${String(args.price_currency || 'USDC').toUpperCase()}/${args.price_per || 'query'}) wallet=${wallet.slice(0, 10)}...` }
|
|
2305
|
+
: { success: false, error: r.error };
|
|
2306
|
+
},
|
|
2307
|
+
});
|
|
2308
|
+
// 发现可用的 Agent 服务
|
|
2309
|
+
ctx.tools.set('registry_discover', {
|
|
2310
|
+
name: 'registry_discover',
|
|
2311
|
+
description: '在 Agent 服务注册表发现服务 (按名称/能力/描述). 找到服务后可用 x402 支付调用. query: 搜索词 (如 research/compute/data).',
|
|
2312
|
+
parameters: { query: '搜索词 (可选, 空=列出全部)' },
|
|
2313
|
+
execute: async (args) => {
|
|
2314
|
+
const q = String(args.query || '').trim();
|
|
2315
|
+
const reg = registry.getAgentRegistry();
|
|
2316
|
+
const services = await reg.discover(q);
|
|
2317
|
+
if (services.length === 0)
|
|
2318
|
+
return { success: true, output: '(注册表无匹配服务)' };
|
|
2319
|
+
const lines = services.slice(0, 20).map((s) => ` [${s.service.name}] ${s.name} (${s.service.price.amount} ${s.service.price.currency}/${s.service.price.per}) wallet=${String(s.wallet).slice(0, 12)}... agent=${s.agentId.slice(0, 24)}...`);
|
|
2320
|
+
return { success: true, output: `发现 ${services.length} 个服务:\n${lines.join('\n')}` };
|
|
2321
|
+
},
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
catch { /* registry 工具注册失败静默 */ }
|
|
2325
|
+
})();
|
|
2326
|
+
// 2026-08-13 (Phase E2): service_call — 调用 Agent 服务 (x402 402 自动支付闭环)
|
|
2327
|
+
ctx.tools.set('service_call', {
|
|
2328
|
+
name: 'service_call',
|
|
2329
|
+
description: '调用注册表里的 Agent 服务 (x402 支付闭环). 从 registry_discover 找到服务名后调用. service_name: 服务名; args: 服务参数; 有钱包私钥时自动支付 402.',
|
|
2330
|
+
parameters: {
|
|
2331
|
+
service_name: '服务名 (必填, registry_discover 查到的)',
|
|
2332
|
+
args: '服务参数 JSON (可选)',
|
|
2333
|
+
max_payment_amount: '最大支付金额 (可选)',
|
|
2334
|
+
},
|
|
2335
|
+
execute: async (args) => {
|
|
2336
|
+
const serviceName = String(args.service_name || '').trim();
|
|
2337
|
+
if (!serviceName)
|
|
2338
|
+
return { success: false, error: 'service_name 必填 (先用 registry_discover 查)' };
|
|
2339
|
+
try {
|
|
2340
|
+
const { serviceCall } = await import('./agent-service-client.js');
|
|
2341
|
+
// 尝试取 channel 钱包私钥 (autoPay)
|
|
2342
|
+
let privateKey;
|
|
2343
|
+
try {
|
|
2344
|
+
const wallet = await ctx.getChannelWallet?.();
|
|
2345
|
+
if (wallet?.encryptedPrivateKey)
|
|
2346
|
+
privateKey = wallet.encryptedPrivateKey;
|
|
2347
|
+
}
|
|
2348
|
+
catch { /* 无钱包 */ }
|
|
2349
|
+
let argsObj = {};
|
|
2350
|
+
try {
|
|
2351
|
+
argsObj = JSON.parse(String(args.args || '{}'));
|
|
2352
|
+
}
|
|
2353
|
+
catch {
|
|
2354
|
+
argsObj = {};
|
|
2355
|
+
}
|
|
2356
|
+
const r = await serviceCall({
|
|
2357
|
+
serviceName,
|
|
2358
|
+
args: argsObj,
|
|
2359
|
+
privateKey,
|
|
2360
|
+
maxPaymentAmount: String(args.max_payment_amount || '').trim() || undefined,
|
|
2361
|
+
});
|
|
2362
|
+
if (!r.success)
|
|
2363
|
+
return { success: false, error: r.error, service: r.service?.service?.name };
|
|
2364
|
+
return { success: true, output: r.output, paid: r.paid, txHash: r.txHash };
|
|
2365
|
+
}
|
|
2366
|
+
catch (e) {
|
|
2367
|
+
return { success: false, error: `service_call 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2368
|
+
}
|
|
2369
|
+
},
|
|
2370
|
+
});
|
|
2371
|
+
// 2026-08-13 (Phase E3): Policy Engine — 预算/授权 (安全核心, 私钥不暴露给 LLM)
|
|
2372
|
+
ctx.tools.set('policy_config', {
|
|
2373
|
+
name: 'policy_config',
|
|
2374
|
+
description: '查看/更新支付策略 (预算/白名单). LLM 只能查看预算与授权规则, 私钥由 Policy Engine 隔离保管. 查看: 无参数; 更新: 传要改的字段 (per_transaction_limit/daily_limit/allowed_recipients/allowed_services).',
|
|
2375
|
+
parameters: {
|
|
2376
|
+
per_transaction_limit: '可选: 单笔上限 (数字)',
|
|
2377
|
+
daily_limit: '可选: 每日预算 (数字)',
|
|
2378
|
+
allowed_recipients: '可选: 允许收款方数组',
|
|
2379
|
+
allowed_services: '可选: 允许服务数组',
|
|
2380
|
+
},
|
|
2381
|
+
execute: async (args) => {
|
|
2382
|
+
try {
|
|
2383
|
+
const { getEconomicPolicy } = await import('./economic-policy.js');
|
|
2384
|
+
const policy = getEconomicPolicy();
|
|
2385
|
+
const patch = {};
|
|
2386
|
+
if (args.per_transaction_limit !== undefined)
|
|
2387
|
+
patch.perTransactionLimit = Number(args.per_transaction_limit);
|
|
2388
|
+
if (args.daily_limit !== undefined)
|
|
2389
|
+
patch.dailyLimit = Number(args.daily_limit);
|
|
2390
|
+
if (Array.isArray(args.allowed_recipients))
|
|
2391
|
+
patch.allowedRecipients = args.allowed_recipients.map(String);
|
|
2392
|
+
if (Array.isArray(args.allowed_services))
|
|
2393
|
+
patch.allowedServices = args.allowed_services.map(String);
|
|
2394
|
+
if (Object.keys(patch).length > 0)
|
|
2395
|
+
policy.updateConfig(patch);
|
|
2396
|
+
const spent = await policy.dailySpent();
|
|
2397
|
+
const c = policy.config();
|
|
2398
|
+
return {
|
|
2399
|
+
success: true,
|
|
2400
|
+
output: `支付策略:\n 单笔上限: $${c.perTransactionLimit}\n 每日预算: $${c.dailyLimit} (今日已用 $${spent})\n 允许收款方: ${c.allowedRecipients.length ? c.allowedRecipients.join(', ') : '(全部)'}\n 允许服务: ${c.allowedServices.length ? c.allowedServices.join(', ') : '(全部)'}\n 速率: ${c.rateLimitPerMinute}/min`,
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
catch (e) {
|
|
2404
|
+
return { success: false, error: `policy_config 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2405
|
+
}
|
|
2406
|
+
},
|
|
2407
|
+
});
|
|
2408
|
+
// 2026-08-13 (Phase M4): Reputation — 服务结果记录 + 信誉查询 (Agent Economic Protocol §7)
|
|
2409
|
+
ctx.tools.set('reputation_update', {
|
|
2410
|
+
name: 'reputation_update',
|
|
2411
|
+
description: '记录一次服务结果, 更新服务提供者的信誉 (success/failed/disputed → score). 服务完成后调用. agent_id: 服务提供者 DID; service_name: 服务名; outcome: success|failed|disputed.',
|
|
2412
|
+
parameters: { agent_id: '服务提供者 agent_id (必填)', service_name: '服务名 (必填)', outcome: '结果: success|failed|disputed (必填)' },
|
|
2413
|
+
execute: async (args) => {
|
|
2414
|
+
try {
|
|
2415
|
+
const { recordServiceOutcome } = await import('./agent-reputation.js');
|
|
2416
|
+
const agentId = String(args.agent_id || '').trim();
|
|
2417
|
+
const serviceName = String(args.service_name || '').trim();
|
|
2418
|
+
const outcome = String(args.outcome || '').trim();
|
|
2419
|
+
if (!agentId || !serviceName || !['success', 'failed', 'disputed'].includes(outcome)) {
|
|
2420
|
+
return { success: false, error: 'agent_id/service_name/outcome(success|failed|disputed) 必填' };
|
|
2421
|
+
}
|
|
2422
|
+
const r = await recordServiceOutcome(agentId, serviceName, outcome);
|
|
2423
|
+
if (!r.ok)
|
|
2424
|
+
return { success: false, error: r.error };
|
|
2425
|
+
return { success: true, output: `✅ 已记录 ${outcome}: tasks=${r.reputation?.tasks}, score=${r.reputation?.score}` };
|
|
2426
|
+
}
|
|
2427
|
+
catch (e) {
|
|
2428
|
+
return { success: false, error: `reputation_update 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2429
|
+
}
|
|
2430
|
+
},
|
|
2431
|
+
});
|
|
2432
|
+
ctx.tools.set('reputation_query', {
|
|
2433
|
+
name: 'reputation_query',
|
|
2434
|
+
description: '查询 Agent 的信誉 (成功率/任务数). agent_id: 服务提供者 DID; service_name: 可选. 选择服务前先查信誉.',
|
|
2435
|
+
parameters: { agent_id: '服务提供者 agent_id (必填)', service_name: '服务名 (可选)' },
|
|
2436
|
+
execute: async (args) => {
|
|
2437
|
+
try {
|
|
2438
|
+
const { queryReputation, formatReputation } = await import('./agent-reputation.js');
|
|
2439
|
+
const r = await queryReputation(String(args.agent_id || '').trim(), String(args.service_name || '').trim() || undefined);
|
|
2440
|
+
if (!r.ok)
|
|
2441
|
+
return { success: true, output: r.error || '(无信誉记录)' };
|
|
2442
|
+
return { success: true, output: r.entries.map((e) => ` [${e.service}] ${formatReputation(e.reputation)}`).join('\n') };
|
|
2443
|
+
}
|
|
2444
|
+
catch (e) {
|
|
2445
|
+
return { success: false, error: `reputation_query 失败: ${String(e?.message || e).slice(0, 200)}` };
|
|
2446
|
+
}
|
|
2447
|
+
},
|
|
2448
|
+
});
|
|
2246
2449
|
// ============================================================
|
|
2247
2450
|
// publish_did (2026-08-03) — 把当前 agent 的 DID 发布到 IPFS + IPNS
|
|
2248
2451
|
// 全自动: 自动安装/启动本地 Kubo → 上传 DID 文档 → 发布 IPNS name
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* a2ui.ts — A2UI (Agent to UI) 协议集成 (2026-08-12)
|
|
3
|
+
*
|
|
4
|
+
* bolloon agent 生成 A2UI 消息 (createSurface / updateComponents / updateDataModel / deleteSurface),
|
|
5
|
+
* 经 SSE 广播给前端 (web / 手机端 Capacitor), 前端用 @a2ui/react renderer 渲染.
|
|
6
|
+
*
|
|
7
|
+
* 参考: https://a2ui.org/specification/v1.0-a2ui/ + D:\AI\A2UI (本地 spec 源码)
|
|
8
|
+
*
|
|
9
|
+
* 机制:
|
|
10
|
+
* - agent 工具 (a2ui_create_surface 等) 生成 A2UI JSON 消息
|
|
11
|
+
* - dispatchA2uiMessage → broadcast({ type: 'a2ui', message }) 给前端
|
|
12
|
+
* - 前端 MessageProcessor 接收渲染 (A2uiSurface)
|
|
13
|
+
*/
|
|
14
|
+
/** broadcast 注入点 (server 调用 setA2uiBroadcast 注入, 关联 SSE /events) */
|
|
15
|
+
let a2uiBroadcast = null;
|
|
16
|
+
export function setA2uiBroadcast(fn) {
|
|
17
|
+
a2uiBroadcast = fn;
|
|
18
|
+
}
|
|
19
|
+
/** 广播一条 A2UI 消息给所有前端 */
|
|
20
|
+
export function broadcastA2uiMessage(message) {
|
|
21
|
+
if (!a2uiBroadcast)
|
|
22
|
+
return false;
|
|
23
|
+
a2uiBroadcast({ type: 'a2ui', message });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/** 校验 + 广播一条 A2UI 消息 (agent 工具 execute 调用) */
|
|
27
|
+
export function dispatchA2uiMessage(message) {
|
|
28
|
+
const type = message?.type;
|
|
29
|
+
const surfaceId = String(message?.surfaceId || '').trim();
|
|
30
|
+
if (!type || !['createSurface', 'updateComponents', 'updateDataModel', 'deleteSurface'].includes(type)) {
|
|
31
|
+
return { success: false, output: 'type 必须是 createSurface/updateComponents/updateDataModel/deleteSurface' };
|
|
32
|
+
}
|
|
33
|
+
if (!surfaceId)
|
|
34
|
+
return { success: false, output: 'surfaceId 必填' };
|
|
35
|
+
const ok = broadcastA2uiMessage({ type, surfaceId, ...message });
|
|
36
|
+
return { success: ok, output: ok ? `已广播 A2UI ${type} (surface=${surfaceId})` : 'A2UI 广播未连接' };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* agent 工具注册表 (a2ui-* 工具定义). 由 pi-sdk-tools 注册为 agent 工具.
|
|
40
|
+
*/
|
|
41
|
+
export const A2UI_TOOL_DEFS = [
|
|
42
|
+
{
|
|
43
|
+
name: 'a2ui_create_surface',
|
|
44
|
+
description: '创建 A2UI surface (前端渲染区). surfaceId: 渲染区标识. 用户想要一个动态 UI 面板/表单/卡片时先创建 surface.',
|
|
45
|
+
params: { surfaceId: '渲染区 id (必填)', title: 'surface 标题 (可选)' },
|
|
46
|
+
build: (a) => ({ type: 'createSurface', surfaceId: String(a.surfaceId || ''), title: String(a.title || '') }),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'a2ui_update_components',
|
|
50
|
+
description: '向 A2UI surface 添加/更新组件 (componentTree JSON). 前端用 @a2ui/react 渲染. components: 组件树 JSON 数组 (Text/Column/Button 等 basicCatalog 组件).',
|
|
51
|
+
params: { surfaceId: '渲染区 id (必填)', components: '组件树 JSON (必填, e.g. [{"type":"text","data":{"text":"你好"}}])' },
|
|
52
|
+
build: (a) => {
|
|
53
|
+
let components = a.components;
|
|
54
|
+
if (typeof components === 'string') {
|
|
55
|
+
try {
|
|
56
|
+
components = JSON.parse(components);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
components = [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { type: 'updateComponents', surfaceId: String(a.surfaceId || ''), components };
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'a2ui_update_data',
|
|
67
|
+
description: '更新 A2UI surface 的数据模型. path: 数据路径 (如 /user/name), value: 数据值.',
|
|
68
|
+
params: { surfaceId: '渲染区 id (必填)', path: '数据路径 (必填)', value: '数据值' },
|
|
69
|
+
build: (a) => ({ type: 'updateDataModel', surfaceId: String(a.surfaceId || ''), path: String(a.path || ''), value: a.value }),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'a2ui_delete_surface',
|
|
73
|
+
description: '删除 A2UI surface (移除前端渲染区). surfaceId: 渲染区 id.',
|
|
74
|
+
params: { surfaceId: '渲染区 id (必填)' },
|
|
75
|
+
build: (a) => ({ type: 'deleteSurface', surfaceId: String(a.surfaceId || '') }),
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
/** 工具名 → build 函数 */
|
|
79
|
+
export function a2uiToolDef(name) {
|
|
80
|
+
return A2UI_TOOL_DEFS.find((t) => t.name === name);
|
|
81
|
+
}
|