@bolloon/bolloon-agent 0.4.30 → 0.4.32

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.
Files changed (68) hide show
  1. package/dist/agents/agent-gateway.js +5 -0
  2. package/dist/agents/agent-manifest-protocol.js +29 -0
  3. package/dist/agents/chain/chain-config.js +504 -0
  4. package/dist/agents/chain/chain-index-query.js +135 -0
  5. package/dist/agents/chain/chain-indexer.js +1045 -0
  6. package/dist/agents/chain/chain-settlement.js +284 -0
  7. package/dist/agents/chain/chain-state-store.js +245 -0
  8. package/dist/agents/chain/chain-wallet.js +198 -0
  9. package/dist/agents/chain/escrow-client.js +433 -0
  10. package/dist/agents/chain/index.js +20 -0
  11. package/dist/agents/chain/onchain-trade.js +691 -0
  12. package/dist/agents/economic-policy.js +7 -0
  13. package/dist/agents/gateway-network.js +6 -0
  14. package/dist/agents/local-signer.js +59 -0
  15. package/dist/agents/network-pulse.js +912 -0
  16. package/dist/agents/task/task-onchain-runner.js +284 -0
  17. package/dist/agents/task-contract.js +322 -0
  18. package/dist/agents/task-inbox.js +267 -0
  19. package/dist/agents/task-transport.js +280 -0
  20. package/dist/agents/x402/paid-info-store.js +75 -6
  21. package/dist/agents/x402/transaction-store.js +8 -1
  22. package/dist/cli/commands/agent.js +230 -0
  23. package/dist/cli/commands/chain.js +926 -0
  24. package/dist/cli/commands/index.js +69 -0
  25. package/dist/cli/commands/mcp.js +98 -0
  26. package/dist/cli/commands/network.js +334 -0
  27. package/dist/cli/commands/payment.js +125 -0
  28. package/dist/cli/commands/tasks.js +1236 -0
  29. package/dist/cli/commands/trade.js +229 -0
  30. package/dist/cli/commands/wallet.js +450 -0
  31. package/dist/cli/mcp/bridge.js +105 -0
  32. package/dist/cli/mcp/server.js +188 -0
  33. package/dist/cli/mcp/tools.js +955 -0
  34. package/dist/cli/protocol-envelope.js +358 -0
  35. package/dist/cli-entry.js +146 -17
  36. package/dist/electron-build/agents/agent-gateway.js +5 -0
  37. package/dist/electron-build/agents/agent-gateway.js.map +1 -1
  38. package/dist/electron-build/agents/agent-manifest-protocol.js +62 -0
  39. package/dist/electron-build/agents/agent-manifest-protocol.js.map +1 -1
  40. package/dist/electron-build/agents/chain/chain-config.js +556 -0
  41. package/dist/electron-build/agents/chain/chain-config.js.map +1 -0
  42. package/dist/electron-build/agents/chain/chain-index-query.js +145 -0
  43. package/dist/electron-build/agents/chain/chain-index-query.js.map +1 -0
  44. package/dist/electron-build/agents/chain/chain-indexer.js +1095 -0
  45. package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -0
  46. package/dist/electron-build/agents/chain/chain-settlement.js +291 -0
  47. package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -0
  48. package/dist/electron-build/agents/chain/escrow-client.js +443 -0
  49. package/dist/electron-build/agents/chain/escrow-client.js.map +1 -0
  50. package/dist/electron-build/agents/economic-policy.js +7 -0
  51. package/dist/electron-build/agents/economic-policy.js.map +1 -1
  52. package/dist/electron-build/agents/gateway-network.js +6 -0
  53. package/dist/electron-build/agents/gateway-network.js.map +1 -1
  54. package/dist/electron-build/agents/network-pulse.js +979 -0
  55. package/dist/electron-build/agents/network-pulse.js.map +1 -0
  56. package/dist/electron-build/agents/x402/paid-info-store.js +76 -6
  57. package/dist/electron-build/agents/x402/paid-info-store.js.map +1 -1
  58. package/dist/electron-build/agents/x402/transaction-store.js +8 -1
  59. package/dist/electron-build/agents/x402/transaction-store.js.map +1 -1
  60. package/dist/web/chain-explorer.js +617 -0
  61. package/dist/web/explorer.html +163 -0
  62. package/dist/web/index.html +5 -0
  63. package/dist/web/routes-chain-index.js +108 -0
  64. package/dist/web/routes-onchain-trade.js +99 -0
  65. package/dist/web/server.js +42 -0
  66. package/dist/web/task-frame-server.js +211 -0
  67. package/package.json +2 -1
  68. package/scripts/build-web.ts +20 -1
@@ -0,0 +1,211 @@
1
+ /**
2
+ * task-frame-server.ts — bolloon-task/1 任务帧接收端 (P3, 2026-09-21)
3
+ *
4
+ * 把 `task-transport` 发来的帧 (request / accept / reject / result) 接到**本机收件箱与台账**:
5
+ * · 每个信封都**必须过验签** (`verifyTaskEnvelope` + 帧里带来的公钥), 验不过 → 如实拒, 不落盘;
6
+ * · `task_request` 只落**待处理收件箱** (`~/.bolloon/tasks/inbox/`): 不执行、不付款、不自动接单;
7
+ * · 状态推进一律走 `task-contract.checkTaskMove` (非法迁移拒绝, 不静默修正);
8
+ * · 与 `agent-delegate-server` 同一套 express + JSON 形状 (薄; 不含业务判断)。
9
+ *
10
+ * 起法 (E2E / 长跑节点):
11
+ * npx tsx scripts/task-frame-node.ts --port 54901 --home /tmp/nodeA
12
+ *
13
+ * 安全口径 (如实): 收帧端点**不设身份认证** —— 它就是一个"信箱": 收到的东西一律先验签, 再只能落
14
+ * 待处理收件箱 (没有任何执行/付款副作用)。要不要接单是**本机用户**用 `bolloon task accept` 决定的事。
15
+ */
16
+ import express from 'express';
17
+ import { validateTaskRequest, verifyTaskEnvelope, checkTaskMove, } from '../agents/task-contract.js';
18
+ import { verifierFor } from '../agents/local-signer.js';
19
+ import { saveIncomingRequest, readInboxItem, patchInboxItem, readLocalTask, patchLocalTask, saveLocalTask, saveResult, saveBody, listInbox, } from '../agents/task-inbox.js';
20
+ import { parseTaskFrame } from '../agents/task-transport.js';
21
+ /** 校验一帧的信封签名 (帧里带的是公钥 hex; 这是公开材料, 不涉及任何私钥) */
22
+ async function verifyFrame(frame) {
23
+ const v = verifierFor(frame.signer?.publicKeyHex || '');
24
+ if (!v)
25
+ return false;
26
+ return await verifyTaskEnvelope(frame.envelope, v);
27
+ }
28
+ /** 按 14 态状态机逐步推进 (非法就停, 返回停在哪一步) */
29
+ function advance(state, steps) {
30
+ let cur = state;
31
+ const applied = [];
32
+ for (const to of steps) {
33
+ if (cur === to)
34
+ continue;
35
+ const mv = checkTaskMove(cur, to);
36
+ if (!mv.ok)
37
+ return { state: cur, applied, rejected: mv.reason };
38
+ cur = to;
39
+ applied.push(to);
40
+ }
41
+ return { state: cur, applied };
42
+ }
43
+ /**
44
+ * 处理一帧 —— 唯一入口 (HTTP / iroh / 测试都走这里, 语义只有一套)。
45
+ */
46
+ export async function handleTaskFrame(frame, opts = {}) {
47
+ const home = opts.home;
48
+ const env = frame.envelope;
49
+ const verified = await verifyFrame(frame);
50
+ switch (frame.frame) {
51
+ case 'task_request': {
52
+ if (!verified) {
53
+ return { ok: false, error: 'SIGNATURE_INVALID', detail: '任务请求信封验签不过 (用帧里 signer.publicKeyHex 验的), 不落盘' };
54
+ }
55
+ const req = env;
56
+ const v = validateTaskRequest(req, { now: Date.now() });
57
+ if (!v.ok) {
58
+ // 契约层说不行 → 如实拒 (版本/字段/预算/deadline/签名逐条在 issues 里)
59
+ return { ok: false, error: 'INVALID_REQUEST', detail: v.issues.join('; ').slice(0, 400) };
60
+ }
61
+ const saved = saveIncomingRequest(req, {
62
+ home,
63
+ requestVerified: true,
64
+ buyerPublicKeyHex: frame.signer.publicKeyHex,
65
+ replyTo: frame.replyTo || null,
66
+ });
67
+ if (!saved.ok)
68
+ return { ok: false, error: 'INBOX_WRITE_FAILED', detail: saved.error };
69
+ const known = !opts.capabilities?.length || opts.capabilities.includes(req.capability);
70
+ return {
71
+ ok: true,
72
+ duplicate: saved.dup,
73
+ note: saved.dup
74
+ ? `同一 requestId 已在收件箱 (幂等: 不重复接受/不重复执行/不重复收费) — ${saved.reason}`
75
+ : `已落待处理收件箱 (不执行、不付款、不自动接单)${known ? '' : ` · 本机没有声明能力 '${req.capability}' (接单时会被拒)`}`,
76
+ facts: { requestId: req.requestId, taskId: req.taskId, state: saved.item?.state ?? 'pending', receivedAt: saved.item?.receivedAt },
77
+ };
78
+ }
79
+ case 'task_accept': {
80
+ if (!verified)
81
+ return { ok: false, error: 'SIGNATURE_INVALID', detail: '接受回执验签不过' };
82
+ const acc = env;
83
+ const cur = readLocalTask(acc.requestId, home);
84
+ if (!cur) {
85
+ // 没有本地台账: 只存回执本身, 并如实标注"没有对应台账"
86
+ const rec = {
87
+ protocol: 'bolloon-task/1',
88
+ requestId: acc.requestId,
89
+ taskId: acc.taskId,
90
+ capability: '',
91
+ instruction: '',
92
+ buyerDid: '',
93
+ providerDid: acc.providerDid,
94
+ target: frame.signer.did,
95
+ transportKind: 'inbound',
96
+ state: 'accepted',
97
+ sentAt: frame.sentAt,
98
+ updatedAt: Date.now(),
99
+ requestSignature: '',
100
+ accept: acc,
101
+ notes: ['本机没有对应的本地任务台账 (只存了对端回执; 不伪造本地发送记录)'],
102
+ };
103
+ saveLocalTask(rec, home);
104
+ return { ok: true, note: '已存对端接受回执 (本机没有对应的本地台账)', facts: { requestId: acc.requestId, state: 'accepted' } };
105
+ }
106
+ const mv = advance(cur.state, ['accepted']);
107
+ if (mv.rejected) {
108
+ return { ok: false, error: 'TASK_TRANSITION_REJECTED', detail: `对端回执 accepted 与本机状态 ${cur.state} 不合: ${mv.rejected}` };
109
+ }
110
+ patchLocalTask(acc.requestId, { state: mv.state, accept: acc, providerPublicKeyHex: frame.signer.publicKeyHex, notes: [...cur.notes, `收到接受回执 ${new Date(frame.sentAt).toISOString()}`] }, home);
111
+ const inb = readInboxItem(acc.requestId, home);
112
+ if (inb)
113
+ patchInboxItem(acc.requestId, { state: 'accepted', accept: acc, providerPublicKeyHex: frame.signer.publicKeyHex }, home);
114
+ return { ok: true, note: `接受回执已入库 (state=${mv.state})`, facts: { requestId: acc.requestId, state: mv.state } };
115
+ }
116
+ case 'task_reject': {
117
+ if (!verified)
118
+ return { ok: false, error: 'SIGNATURE_INVALID', detail: '拒绝回执验签不过' };
119
+ const rej = env;
120
+ const cur = readLocalTask(rej.requestId, home);
121
+ if (!cur) {
122
+ return { ok: true, note: '收到拒绝对端回执, 本机没有对应台账 (不伪造)', facts: { requestId: rej.requestId, state: 'rejected', reason: rej.reason } };
123
+ }
124
+ const mv = advance(cur.state, ['rejected']);
125
+ if (mv.rejected)
126
+ return { ok: false, error: 'TASK_TRANSITION_REJECTED', detail: `对端回执 rejected 与本机状态 ${cur.state} 不合: ${mv.rejected}` };
127
+ patchLocalTask(rej.requestId, { state: mv.state, reject: rej, notes: [...cur.notes, `被拒: ${String(rej.reason).slice(0, 200)}`] }, home);
128
+ const inb = readInboxItem(rej.requestId, home);
129
+ if (inb)
130
+ patchInboxItem(rej.requestId, { state: 'rejected', reject: rej }, home);
131
+ return { ok: true, note: `拒绝回执已入库 (state=${mv.state})`, facts: { requestId: rej.requestId, state: mv.state, reason: rej.reason } };
132
+ }
133
+ case 'task_result': {
134
+ if (!verified)
135
+ return { ok: false, error: 'SIGNATURE_INVALID', detail: '结果信封验签不过 (不落地未验签的结果)' };
136
+ const res = env;
137
+ // ① 存结果信封 (真实内容哈希/CID + 签名)
138
+ const w = saveResult(res, home);
139
+ if (!w.ok)
140
+ return { ok: false, error: 'RESULT_WRITE_FAILED', detail: w.error };
141
+ // ② 正文 (若随帧带来): 落**私有一层**, 并用签名里的 contentHash 复核
142
+ let bodyFacts = { bodyPresent: false };
143
+ if (typeof frame.body === 'string' && frame.body.length) {
144
+ const saved = saveBody(res.taskId, frame.body, home);
145
+ const matches = !!saved.contentHash && !!res.contentHash && saved.contentHash === res.contentHash;
146
+ bodyFacts = {
147
+ bodyPresent: true,
148
+ bodyBytes: saved.bytes,
149
+ bodyHashMatches: matches,
150
+ bodyNote: matches ? '正文哈希与签名里的 contentHash 一致' : '正文哈希与签名里的 contentHash **不一致** (如实记录)',
151
+ };
152
+ }
153
+ // ③ 状态推进: submitted/accepted → running → delivered (逐条过 checkTaskMove)
154
+ const cur = readLocalTask(res.requestId, home);
155
+ if (cur) {
156
+ const mv = advance(cur.state, ['accepted', 'running', 'delivered']);
157
+ if (mv.rejected)
158
+ return { ok: false, error: 'TASK_TRANSITION_REJECTED', detail: `结果回执与本机状态 ${cur.state} 不合: ${mv.rejected}` };
159
+ patchLocalTask(res.requestId, {
160
+ state: mv.state, result: res, resultVerified: verified,
161
+ providerPublicKeyHex: frame.signer.publicKeyHex,
162
+ notes: [...cur.notes, `收到结果 ${new Date(frame.sentAt).toISOString()} (contentHash=${(res.contentHash || '').slice(0, 12)}…)`],
163
+ }, home);
164
+ const inb = readInboxItem(res.requestId, home);
165
+ if (inb)
166
+ patchInboxItem(res.requestId, { state: 'delivered', result: res, resultVerified: verified, providerPublicKeyHex: frame.signer.publicKeyHex }, home);
167
+ return { ok: true, note: `结果已入库 (state=${mv.state}, 签名验过=${verified})`, facts: { requestId: res.requestId, taskId: res.taskId, state: mv.state, protocolVerified: verified, ...bodyFacts } };
168
+ }
169
+ const inb = readInboxItem(res.requestId, home);
170
+ if (inb) {
171
+ patchInboxItem(res.requestId, { state: 'delivered', result: res, resultVerified: verified, providerPublicKeyHex: frame.signer.publicKeyHex }, home);
172
+ return { ok: true, note: `结果已入库到收件箱条目 (state=delivered)`, facts: { requestId: res.requestId, taskId: res.taskId, protocolVerified: verified, ...bodyFacts } };
173
+ }
174
+ return { ok: true, note: '结果信封已存, 但本机没有对应的请求/台账 (不伪造状态)', facts: { taskId: res.taskId, protocolVerified: verified, ...bodyFacts } };
175
+ }
176
+ default:
177
+ return { ok: false, error: 'UNKNOWN_FRAME', detail: `未知帧类型: ${String(frame.frame)}` };
178
+ }
179
+ }
180
+ /** express app: POST /api/task/frame + GET /api/task/health */
181
+ export function createTaskFrameApp(opts = {}) {
182
+ const app = express();
183
+ app.use(express.json({ limit: '2mb' }));
184
+ app.get('/api/task/health', (_req, res) => {
185
+ res.json({
186
+ ok: true,
187
+ schema: 'bolloon-task-frame/1',
188
+ selfDid: opts.selfDid || null,
189
+ capabilities: opts.capabilities || [],
190
+ inboxPending: listInbox(opts.home).filter((i) => i.state === 'pending').length,
191
+ note: '任务帧端点 (只落待处理收件箱; 不执行、不付款)',
192
+ });
193
+ });
194
+ app.post('/api/task/frame', async (req, res) => {
195
+ const raw = req.body?.frame;
196
+ if (!raw)
197
+ return res.status(400).json({ ok: false, error: 'BAD_FRAME', detail: '缺 frame' });
198
+ const parsed = parseTaskFrame(typeof raw === 'string' ? raw : JSON.stringify(raw));
199
+ if (!parsed.ok)
200
+ return res.status(400).json({ ok: false, error: 'BAD_FRAME', detail: parsed.error });
201
+ try {
202
+ const reply = await handleTaskFrame(parsed.frame, opts);
203
+ // 帧级失败用 200 + ok:false 表达 (与 CLI 信封同一条纪律: 失败也结构化, 不靠 HTTP 状态猜)
204
+ return res.status(200).json({ ok: reply.ok, ...(reply.duplicate !== undefined ? { duplicate: reply.duplicate } : {}), ...(reply.note ? { note: reply.note } : {}), ...(reply.error ? { error: reply.error } : {}), ...(reply.detail ? { detail: reply.detail } : {}), ...(reply.facts ? { facts: reply.facts } : {}) });
205
+ }
206
+ catch (e) {
207
+ return res.status(500).json({ ok: false, error: 'HANDLER_THREW', detail: String(e?.message || e).slice(0, 300) });
208
+ }
209
+ });
210
+ return app;
211
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.4.30",
3
+ "version": "0.4.32",
4
4
  "type": "module",
5
5
  "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli-entry.js",
@@ -91,6 +91,7 @@
91
91
  "@x402/fetch": "^2.25.0",
92
92
  "b4a": "^1.8.1",
93
93
  "dotenv": "^17.4.2",
94
+ "ethers": "^6.17.0",
94
95
  "express": "^5.2.1",
95
96
  "helia": "^7.1.10",
96
97
  "ink": "^7.1.1",
@@ -14,7 +14,7 @@ async function main() {
14
14
 
15
15
  // 重要: 不能 rm -rf 整个 dist/web/, 否则会删掉 build:main 编译出来的
16
16
  // dist/web/server.js. 只清理 web 静态资源, 保留 server.js.
17
- for (const f of ['index.html', 'api-config.html', 'style.css', 'client.js', 'mobile.html', 'mobile.css', 'mobile.js', 'mobile-core.js', 'components']) {
17
+ for (const f of ['index.html', 'api-config.html', 'explorer.html', 'style.css', 'client.js', 'chain-explorer.js', 'mobile.html', 'mobile.css', 'mobile.js', 'mobile-core.js', 'components']) {
18
18
  await fs.rm(path.join(DIST_WEB, f), { recursive: true, force: true });
19
19
  }
20
20
  await fs.mkdir(DIST_WEB, { recursive: true });
@@ -119,9 +119,28 @@ async function main() {
119
119
  loader: { '.tsx': 'tsx' },
120
120
  });
121
121
 
122
+ // 2026-09-22 (P7): 编译链上浏览器页面 (chain-explorer.ts → dist/web/chain-explorer.js)
123
+ // 独立入口, 与主界面 client.ts 完全隔离 (只吃 P5 的只读路由);
124
+ // esm + bundle: explorer.html 以 <script type="module"> 加载; 模块内目前不 import 任何东西,
125
+ // 将来真加了 import 也不会踩 client.ts 那个 require 的坑。
126
+ console.log('[build-web] 编译 chain-explorer.ts...');
127
+ await esbuild.build({
128
+ entryPoints: [path.join(ROOT, 'src/web/chain-explorer.ts')],
129
+ outfile: path.join(DIST_WEB, 'chain-explorer.js'),
130
+ format: 'esm',
131
+ target: 'es2022',
132
+ platform: 'browser',
133
+ minify: false,
134
+ bundle: true,
135
+ // charset utf8: 产物里保留中文文案与 '…' 字面量 (默认 ascii 会把非 ASCII 全转义成 \uXXXX,
136
+ // 功能一样但人读不了, 也让"短写省略号存在"这类断言没法从产物里核)
137
+ charset: 'utf8',
138
+ });
139
+
122
140
  // 复制静态文件
123
141
  console.log('[build-web] 复制静态文件...');
124
142
  await fs.copyFile(path.join(ROOT, 'src/web/index.html'), path.join(DIST_WEB, 'index.html'));
143
+ await fs.copyFile(path.join(ROOT, 'src/web/explorer.html'), path.join(DIST_WEB, 'explorer.html'));
125
144
  await fs.copyFile(path.join(ROOT, 'src/web/api-config.html'), path.join(DIST_WEB, 'api-config.html'));
126
145
  await fs.copyFile(path.join(ROOT, 'src/web/style.css'), path.join(DIST_WEB, 'style.css'));
127
146
  // 2026-08-12: 手机端 UI (微信风格, Capacitor webview 加载) — 纯静态复制