@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.
@@ -0,0 +1,168 @@
1
+ // ─── 数字资源资产化 (Stage 1-4) ────────────────────────────────────────────
2
+ // 经济循环: 注册资源 → 运营(发现/自动分配) → 交易(x402 付费) → 清算(信誉).
3
+ // A 轻版: content 内容寻址(CID) + 元数据(可同步网络 registry) + DID 签名身份;
4
+ // 预留 B: chain='evm' + tokenUriTemplate → 元数据可升级为 tokenURI.
5
+ // 四类统一 schema: data | art_product | product_image | tx_link.
6
+ // 复用现有件: cid_database(内容寻址) / agent-registry(跨机发现/定价/信誉) / x402(交易) / reputation(清算).
7
+ function memStore() {
8
+ let m = [];
9
+ return { get: () => m, set: (x) => { m = x; } };
10
+ }
11
+ /** Stage2: 把资源元数据同步成网络 registry 可发现条目 (AgentService 包装) */
12
+ export function serializeForRegistry(r) {
13
+ return {
14
+ agentId: r.ownerDid,
15
+ name: r.title,
16
+ wallet: r.wallet || '0x0',
17
+ service: {
18
+ name: `resource:${r.type}`,
19
+ description: `数字资源 ${r.type} — ${r.title} (CID ${r.contentCid})`,
20
+ price: r.price ? { amount: r.price.amount, currency: r.price.currency, per: r.price.per || 'access' } : { amount: '0', currency: 'USDC', per: 'access' },
21
+ },
22
+ capabilities: [`resource:${r.type}`, r.resourceId],
23
+ };
24
+ }
25
+ /** Stage2: 跨机可见 — 注册资源时同步到网络 registry (尽力而为) */
26
+ export async function syncResourceToRegistry(r, deps) {
27
+ if (!deps.registry)
28
+ return;
29
+ try {
30
+ await deps.registry.register(serializeForRegistry(r));
31
+ }
32
+ catch { /* 同步失败不致命 */ }
33
+ }
34
+ export async function registerResource(opts, deps = {}) {
35
+ if (!opts.ownerDid || !opts.type || !opts.title || !opts.content)
36
+ return { ok: false, error: 'ownerDid/type/title/content 必填' };
37
+ const VALID = ['data', 'art_product', 'product_image', 'tx_link'];
38
+ if (!VALID.includes(opts.type))
39
+ return { ok: false, error: `type 必须是 ${VALID.join('|')}` };
40
+ try {
41
+ const cid = deps.cid;
42
+ if (!cid)
43
+ return { ok: false, error: '缺 cid 依赖 (内容寻址不可用)' };
44
+ const contentCid = await cid.save(opts.content);
45
+ const now = deps.now ? deps.now() : new Date().toISOString();
46
+ const rid = deps.rid ? deps.rid() : `res_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
47
+ const resource = {
48
+ resourceId: rid, ownerDid: opts.ownerDid, type: opts.type, title: opts.title, contentCid,
49
+ wallet: opts.wallet, price: opts.price, license: opts.license, txLink: opts.txLink, meta: opts.meta,
50
+ chain: opts.chain || 'none', tokenUriTemplate: opts.chain === 'evm' ? opts.tokenUriTemplate : undefined,
51
+ createdAt: now, updatedAt: now,
52
+ };
53
+ (deps.store || memStore()).set([...(deps.store || memStore()).get(), resource]);
54
+ deps.onRegister?.(resource);
55
+ await syncResourceToRegistry(resource, deps); // Stage2 跨机可见
56
+ return { ok: true, resource };
57
+ }
58
+ catch (e) {
59
+ return { ok: false, error: `注册失败: ${String(e?.message || e).slice(0, 140)}` };
60
+ }
61
+ }
62
+ /** Stage2 运营: 按 type/owner 过滤 (本机索引 + 网络 registry 合并) */
63
+ export async function listResources(filter = {}, deps = {}) {
64
+ const local = (deps.store || memStore()).get()
65
+ .filter((r) => (!filter.type || r.type === filter.type) && (!filter.owner || r.ownerDid === filter.owner));
66
+ // 合并网络 registry (跨机资源), 按 resourceId 去重
67
+ if (deps.registry) {
68
+ try {
69
+ const remote = await deps.registry.discover(filter.type ? `resource:${filter.type}` : 'resource:');
70
+ const known = new Set(local.map((r) => r.resourceId));
71
+ for (const r of remote) {
72
+ const rid = r?.capabilities?.find((c) => String(c).startsWith('res_'));
73
+ if (!rid || known.has(rid))
74
+ continue;
75
+ const price = r?.service?.price;
76
+ local.push({
77
+ resourceId: rid, ownerDid: r?.agentId || 'unknown', type: String(r?.service?.name).replace('resource:', '') || 'data',
78
+ title: r?.name || rid, contentCid: '', wallet: r?.wallet, price: price ? { amount: String(price.amount), currency: price.currency || 'USDC', per: price.per } : undefined,
79
+ chain: 'none', meta: { remote: true },
80
+ });
81
+ }
82
+ }
83
+ catch { /* 网络合并失败忽略 */ }
84
+ }
85
+ return local;
86
+ }
87
+ export async function getResource(resourceId, deps = {}) {
88
+ return (deps.store || memStore()).get().find((r) => r.resourceId === resourceId) ?? null;
89
+ }
90
+ /** 免费/预览访问: 直接按 contentCid 取回内容 (付费资源应走 purchaseResource) */
91
+ export async function accessResource(resourceId, deps = {}) {
92
+ const r = await getResource(resourceId, deps);
93
+ if (!r)
94
+ return { ok: false, error: `资源不存在: ${resourceId}` };
95
+ if (!deps.cid)
96
+ return { ok: false, error: '缺 cid 依赖', resource: r };
97
+ const content = await deps.cid.load(r.contentCid);
98
+ if (content === null)
99
+ return { ok: false, error: '内容拉取失败 (CID 不可达)', resource: r };
100
+ return { ok: true, content, resource: r };
101
+ }
102
+ /** Stage2 自动分配匹配器: 关键词打分 + 信誉加权, 返回排序资源 */
103
+ export async function matchResources(query, deps = {}) {
104
+ const all = await listResources({}, deps);
105
+ const q = String(query || '').trim().toLowerCase();
106
+ const scored = [];
107
+ for (const r of all) {
108
+ let s = 0;
109
+ if (q) {
110
+ if (r.title?.toLowerCase().includes(q))
111
+ s += 3;
112
+ if (r.type.toLowerCase().includes(q))
113
+ s += 2;
114
+ if (r.license?.toLowerCase().includes(q))
115
+ s += 1;
116
+ if (String(r.ownerDid).toLowerCase().includes(q))
117
+ s += 1;
118
+ }
119
+ else
120
+ s = 1;
121
+ if (deps.repQuery) {
122
+ try {
123
+ const rep = await deps.repQuery(r.ownerDid, r.type);
124
+ s += rep.score * 5;
125
+ }
126
+ catch { /* 无信誉 */ }
127
+ }
128
+ scored.push({ resource: r, score: s });
129
+ }
130
+ return scored.filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
131
+ }
132
+ /** Stage3 交易 + Stage4 清算: 付费档走注入 pay(x402), 免费直接 access; 结果 onSettle(reputation) */
133
+ export async function purchaseResource(resourceId, deps = {}) {
134
+ const r = await getResource(resourceId, deps);
135
+ if (!r)
136
+ return { ok: false, error: `资源不存在: ${resourceId}` };
137
+ const amount = r.price ? Number(r.price.amount) : 0;
138
+ if (amount > 0) {
139
+ if (!deps.pay)
140
+ return { ok: false, needPay: true, error: '付费资源需要注入 pay (x402)', resource: r };
141
+ if (!r.wallet)
142
+ return { ok: false, needPay: true, error: '资源缺收款钱包 (wallet)', resource: r };
143
+ const p = await deps.pay({ recipient: r.wallet, amount: String(amount), currency: r.price.currency, token: r.price.token, memo: `purchase ${r.resourceId}` });
144
+ if (!p.success) {
145
+ deps.onSettle?.('failed', r);
146
+ return { ok: false, needPay: true, error: p.error || '支付失败', resource: r };
147
+ }
148
+ deps.onSettle?.('success', r);
149
+ // 付费后解锁访问 (内容寻址)
150
+ const content = deps.cid ? await deps.cid.load(r.contentCid) : null;
151
+ return { ok: true, content: content ?? undefined, txHash: p.txHash, resource: r };
152
+ }
153
+ // 免费资源直接访问
154
+ const content = deps.cid ? await deps.cid.load(r.contentCid) : null;
155
+ return { ok: true, content: content ?? undefined, resource: r };
156
+ }
157
+ /** Stage4 清算: 查资源提供者信誉 */
158
+ export async function resourceReputation(ownerDid, deps = {}) {
159
+ if (!deps.repQuery)
160
+ return { ok: false, error: '缺 repQuery 依赖' };
161
+ try {
162
+ const rep = await deps.repQuery(ownerDid);
163
+ return { ok: true, reputation: rep };
164
+ }
165
+ catch (e) {
166
+ return { ok: false, error: String(e?.message || e).slice(0, 120) };
167
+ }
168
+ }
@@ -0,0 +1,77 @@
1
+ // ─── 数字资源资产化 Stage 1-B: EVM 资产合约 (ERC-721) 铸造/流转/查询 ─────────
2
+ // 合约: contracts/ResourceERC721.sol (mint(to,id,tokenUri), safeTransferFrom).
3
+ // 内容寻址 CID 作 tokenURI (不可变, 随 token 流转); 铸造/转移/查询全部走注入的
4
+ // EVM executor (默认接 ethers/viem 或节点; 未配置明确提示), 单测用 fake.
5
+ // 复用: cid_database(内容寻址) / agent-registry(注册) / gateway(跨机可见) — Stage1 已有.
6
+ function memStore() {
7
+ let m = [];
8
+ return { get: () => m, set: (x) => { m = x; } };
9
+ }
10
+ /** 从 ~/.bolloon/evm-config.json 或 env 读 EVM 配置 (合约地址/RPC/chainId). */
11
+ export async function loadEvmConfig(src) {
12
+ try {
13
+ if (src)
14
+ return JSON.parse(src);
15
+ const home = globalThis.process?.env?.HOME || '';
16
+ const { readFile } = await import('fs/promises');
17
+ const raw = await readFile(`${home}/.bolloon/evm-config.json`, 'utf-8').catch(() => null);
18
+ if (raw)
19
+ return JSON.parse(raw);
20
+ return null;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ /** 铸造: 内容 CID 作 tokenURI 铸 ERC-721, 记 token 账本 */
27
+ export async function mintResourceToken(resource, deps = {}) {
28
+ if (!deps.evm)
29
+ return { ok: false, needConfig: true, error: '需配置 EVM executor (ethers/viem 或注入) 才能铸造' };
30
+ if (!resource.wallet)
31
+ return { ok: false, needConfig: true, error: '资源缺收款钱包地址 (EVM 接收地址)' };
32
+ if (!resource.contentCid)
33
+ return { ok: false, error: '资源缺 contentCid (内容寻址)' };
34
+ try {
35
+ const tokenUri = `ipfs://${resource.contentCid}`; // CID 作 tokenURI (内容寻址, 不可变)
36
+ const r = await deps.evm.mint(resource.wallet, tokenUri, `mint ${resource.resourceId}`);
37
+ const now = deps.now ? deps.now() : new Date().toISOString();
38
+ const record = { tokenId: r.tokenId, resourceId: resource.resourceId, ownerDid: resource.ownerDid, contract: deps.config?.contractAddress || '', mintedAt: now };
39
+ const st = deps.store || memStore();
40
+ st.set([...st.get(), record]);
41
+ return { ok: true, tokenId: r.tokenId, record };
42
+ }
43
+ catch (e) {
44
+ return { ok: false, error: `铸造失败: ${String(e?.message || e).slice(0, 140)}` };
45
+ }
46
+ }
47
+ /** 流转: safeTransferFrom 转移所有权 */
48
+ export async function transferResourceToken(tokenId, to, deps = {}) {
49
+ if (!deps.evm)
50
+ return { ok: false, needConfig: true, error: '需配置 EVM executor 才能流转' };
51
+ try {
52
+ const r = await deps.evm.transfer(tokenId, to);
53
+ if (r.error)
54
+ return { ok: false, error: r.error };
55
+ return { ok: true, tokenId };
56
+ }
57
+ catch (e) {
58
+ return { ok: false, error: `流转失败: ${String(e?.message || e).slice(0, 140)}` };
59
+ }
60
+ }
61
+ /** 查询: owner + tokenURI */
62
+ export async function queryResourceToken(tokenId, deps = {}) {
63
+ if (!deps.evm)
64
+ return { ok: false, error: '需配置 EVM executor 才能查询' };
65
+ try {
66
+ const owner = await deps.evm.ownerOf(tokenId);
67
+ const uri = await deps.evm.tokenURI(tokenId);
68
+ return { ok: true, owner: owner ?? undefined, tokenUri: uri ?? undefined };
69
+ }
70
+ catch (e) {
71
+ return { ok: false, error: `查询失败: ${String(e?.message || e).slice(0, 140)}` };
72
+ }
73
+ }
74
+ /** 列出本机已铸 token */
75
+ export function listResourceTokens(deps = {}) {
76
+ return (deps.store || memStore()).get();
77
+ }
@@ -0,0 +1,46 @@
1
+ // ─── 资源级 x402 钱包自动配置 ─────────────────────────────────────────────
2
+ // 首次调用自动生成一个 EVM 钱包 (viem/accounts, 私钥+地址), 持久化到
3
+ // ~/.bolloon/wallet.json (mode 0600). 之后幂等加载同一钱包.
4
+ // 资源自动绑定到它: 卖家收款地址(resource.wallet) / 铸造接收地址 / 购买付款签名.
5
+ // x402 支付复用该私钥签名; 资金由用户向 address 充值 (自动配置=密钥/绑定, 不代发币).
6
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
7
+ async function defaultRead() {
8
+ try {
9
+ const { readFile } = await import('fs/promises');
10
+ const home = globalThis.process?.env?.HOME || '';
11
+ return await readFile(`${home}/.bolloon/wallet.json`, 'utf8');
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ async function defaultWrite(payload) {
18
+ const { writeFile, mkdir } = await import('fs/promises');
19
+ const home = globalThis.process?.env?.HOME || '';
20
+ await mkdir(`${home}/.bolloon`, { recursive: true });
21
+ await writeFile(`${home}/.bolloon/wallet.json`, payload, { mode: 0o600 });
22
+ }
23
+ /** 加载或创建钱包 (幂等): 有则返回, 无则 viem 生成 + 落盘 0600. */
24
+ export async function loadOrCreateWallet(deps = {}) {
25
+ const read = deps.read ?? defaultRead;
26
+ const write = deps.write ?? defaultWrite;
27
+ const existing = await read();
28
+ if (existing) {
29
+ try {
30
+ const w = JSON.parse(existing);
31
+ if (w && w.privateKey && w.address)
32
+ return { privateKey: String(w.privateKey), address: String(w.address) };
33
+ }
34
+ catch { /* corrupted → regenerate */ }
35
+ }
36
+ const privateKey = generatePrivateKey();
37
+ const account = privateKeyToAccount(privateKey);
38
+ const rec = { privateKey, address: account.address };
39
+ await write(JSON.stringify(rec));
40
+ return rec;
41
+ }
42
+ /** 便捷: 只取地址 (绑定资源/铸造接收). */
43
+ export async function walletAddress(deps = {}) {
44
+ const w = await loadOrCreateWallet(deps);
45
+ return w.address;
46
+ }
package/dist/index.js CHANGED
@@ -1122,6 +1122,74 @@ async function processInput(input, comm) {
1122
1122
  }
1123
1123
  // ==================== 2026-08-06: 系统命令组 (/model /now /ipfs /memory ...) ====================
1124
1124
  const cmd = trimmed.toLowerCase();
1125
+ // /net — Agent 网络快捷命令 (join/status/ctx, 2026-09-08)
1126
+ if (cmd === '/net' || cmd.startsWith('/net ')) {
1127
+ const arg = trimmed.slice(4).trim();
1128
+ if (arg.toLowerCase().startsWith('join ')) {
1129
+ const link = arg.slice(5).trim();
1130
+ const { joinNetwork, pullNetworkProfile, pullNetworkSharedContext, networkShareSelf } = await import('./agents/gateway-network.js');
1131
+ const r = await joinNetwork(link);
1132
+ if (!r.ok) {
1133
+ appendLine(`${C_ERROR}加入失败: ${r.error}${RESET}`);
1134
+ return;
1135
+ }
1136
+ appendLine(r.already ? `${C_DIM}已在网络 (${r.linkKind})${RESET}` : `${C_OK}已加入${RESET} (${r.linkKind}) · ${r.total} 服务 · 新增 ${r.joined}${r.networkId ? ` · ${C_DIM}net=${String(r.networkId).slice(0, 16)}${RESET}` : ''}`);
1137
+ const profile = await pullNetworkProfile(link).catch(() => null);
1138
+ if (profile?.members?.length) {
1139
+ appendLine(`${C_ACCENT}网络画像:${RESET}`);
1140
+ for (const m of profile.members.slice(0, 8))
1141
+ appendLine(` ${m.name} ${C_DIM}(${String(m.agentId).slice(0, 16)}…) · ${m.service?.name || ''}${RESET}`);
1142
+ }
1143
+ if (profile?.bootstrap?.sharedContextCid) {
1144
+ const ctx = await pullNetworkSharedContext(profile.bootstrap.sharedContextCid).catch(() => null);
1145
+ if (ctx)
1146
+ appendLine(`${C_DIM}📡 共享context: ${ctx.slice(0, 140).replace(/\n/g, ' ')}${RESET}`);
1147
+ }
1148
+ try {
1149
+ const self = { agentId: cliAgentId || 'cli-agent', name: cliAgentName || 'bolloon', wallet: '0x0', service: { name: 'agent', description: 'bolloon cli node', price: { amount: '0', currency: 'USDC', per: 'query' } } };
1150
+ const s = await networkShareSelf(link, [self]);
1151
+ if (s?.ok && s.note)
1152
+ appendLine(`${C_DIM}${s.note}${RESET}`);
1153
+ }
1154
+ catch { /* 广播失败不致命 */ }
1155
+ return;
1156
+ }
1157
+ if (arg.toLowerCase() === 'status' || arg === '') {
1158
+ const { listJoinedNetworks } = await import('./agents/gateway-network.js');
1159
+ const nets = await listJoinedNetworks();
1160
+ appendLine(nets.length
1161
+ ? `🔗 ${C_ACCENT}已加入网络:${RESET}\n` + nets.map((n) => ` ${n.name || n.kind} ${C_DIM}(${n.serviceCount}S${n.networkId ? ` · ${String(n.networkId).slice(0, 16)}` : ''})${RESET}`).join('\n')
1162
+ : `${C_DIM}未加入任何网络 — /net join <链接>${RESET}`);
1163
+ return;
1164
+ }
1165
+ if (arg.toLowerCase() === 'qr') {
1166
+ const { shareNetworkLink } = await import('./agents/gateway-network.js');
1167
+ const sh = await shareNetworkLink({ name: cliAgentName || 'bolloon' });
1168
+ if (!sh.link) {
1169
+ appendLine(`${C_ERROR}生成链接失败: ${sh.error}${RESET}`);
1170
+ return;
1171
+ }
1172
+ const { buildQrPayload, encodeQrTerminal } = await import('./web/qr.js');
1173
+ const payload = buildQrPayload({ link: sh.link, version: '1' });
1174
+ const qr = await encodeQrTerminal(payload);
1175
+ if (qr) {
1176
+ appendLine(renderMessageBox({ title: '📷 扫码入网', body: `${qr}\n\n${C_DIM}链接 (手机粘贴也可): ${payload}${RESET}`, color: C_ACCENT, maxLines: 0 }));
1177
+ }
1178
+ else {
1179
+ appendLine(`${C_ERROR}二维码生成失败 — 链接: ${payload}${RESET}`);
1180
+ }
1181
+ return;
1182
+ }
1183
+ if (arg.toLowerCase().startsWith('ctx ')) {
1184
+ const text = arg.slice(4).trim();
1185
+ const { publishNetworkSharedContext } = await import('./agents/gateway-network.js');
1186
+ const p = await publishNetworkSharedContext(text);
1187
+ appendLine(p.ok ? `${C_OK}共享context已发布:${RESET} ${p.cid}` : `${C_ERROR}发布失败: ${p.error}${RESET}`);
1188
+ return;
1189
+ }
1190
+ appendLine(`${C_DIM}用法: /net join <链接> | /net status | /net ctx <文本>${RESET}`);
1191
+ return;
1192
+ }
1125
1193
  // /model — 模型供应商选择器 (ink 交互渲染, 复用 MentionPopup)
1126
1194
  if (cmd === '/model') {
1127
1195
  try {
@@ -13,6 +13,8 @@
13
13
  // ============ 身份 (WebCrypto) ============
14
14
  const IDENTITY_DB = 'bolloon-mobile';
15
15
  let _identity = null;
16
+ // #2 手机自动入网 (browser-safe gateway, 与桌面同一协议): 检测到链接自动 join
17
+ import { mobileAutoJoinGateway } from './mobile-gateway.js';
16
18
  async function generateDID() {
17
19
  const bytes = crypto.getRandomValues(new Uint8Array(32));
18
20
  const digest = await crypto.subtle.digest('SHA-256', bytes);
@@ -269,6 +271,10 @@ export async function handleIncomingAgentMessage(type, payload, fromPeer) {
269
271
  try {
270
272
  const { text, channelId } = JSON.parse(payload);
271
273
  notifyInboundChat(text || '', channelId || '', fromPeer);
274
+ // #2 手机自动入网: 消息里带 network 链接 → 自动 join (browser-safe, 不阻塞回复)
275
+ if (text && /orbitdb:\/\/|ipns:\/\/|https?:\/\/[^\s]*\/registry/.test(text)) {
276
+ void mobileAutoJoinGateway(text).catch(() => { });
277
+ }
272
278
  const reply = await runLocalAgent(text || '');
273
279
  await _send('agent.chat.reply', JSON.stringify({ channelId, text: reply, fromPublicKey: _ownDid }), fromPeer);
274
280
  }