@bolloon/bolloon-agent 0.4.30 → 0.4.31

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 (38) 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/economic-policy.js +7 -0
  4. package/dist/agents/gateway-network.js +6 -0
  5. package/dist/agents/local-signer.js +59 -0
  6. package/dist/agents/network-pulse.js +450 -0
  7. package/dist/agents/task-contract.js +322 -0
  8. package/dist/agents/task-inbox.js +267 -0
  9. package/dist/agents/task-transport.js +280 -0
  10. package/dist/agents/x402/transaction-store.js +8 -1
  11. package/dist/cli/commands/agent.js +230 -0
  12. package/dist/cli/commands/index.js +61 -0
  13. package/dist/cli/commands/mcp.js +98 -0
  14. package/dist/cli/commands/network.js +334 -0
  15. package/dist/cli/commands/payment.js +125 -0
  16. package/dist/cli/commands/tasks.js +1236 -0
  17. package/dist/cli/commands/trade.js +229 -0
  18. package/dist/cli/commands/wallet.js +450 -0
  19. package/dist/cli/mcp/bridge.js +105 -0
  20. package/dist/cli/mcp/server.js +185 -0
  21. package/dist/cli/mcp/tools.js +612 -0
  22. package/dist/cli/protocol-envelope.js +346 -0
  23. package/dist/cli-entry.js +141 -17
  24. package/dist/electron-build/agents/agent-gateway.js +5 -0
  25. package/dist/electron-build/agents/agent-gateway.js.map +1 -1
  26. package/dist/electron-build/agents/agent-manifest-protocol.js +62 -0
  27. package/dist/electron-build/agents/agent-manifest-protocol.js.map +1 -1
  28. package/dist/electron-build/agents/economic-policy.js +7 -0
  29. package/dist/electron-build/agents/economic-policy.js.map +1 -1
  30. package/dist/electron-build/agents/gateway-network.js +6 -0
  31. package/dist/electron-build/agents/gateway-network.js.map +1 -1
  32. package/dist/electron-build/agents/network-pulse.js +504 -0
  33. package/dist/electron-build/agents/network-pulse.js.map +1 -0
  34. package/dist/electron-build/agents/x402/transaction-store.js +8 -1
  35. package/dist/electron-build/agents/x402/transaction-store.js.map +1 -1
  36. package/dist/web/server.js +29 -0
  37. package/dist/web/task-frame-server.js +211 -0
  38. package/package.json +1 -1
@@ -8221,6 +8221,35 @@ fetchState();
8221
8221
  console.warn('[24h] Failed to load heartbeat modules:', err);
8222
8222
  }
8223
8223
  // 健康检查端点
8224
+ // 2026-09-18: 网络脉冲公开只读投影 (给 bolloon-UI 网关页; 无认证, 短缓存, 只有聚合)
8225
+ // 永远不返回原始事件 / registry / DID / peerId / IP / 钱包 / 任务正文。
8226
+ app.get('/api/public/network/progress', async (req, res) => {
8227
+ try {
8228
+ const np = await import('../agents/network-pulse.js');
8229
+ const snap = await np.getNetworkPulse({});
8230
+ // agent_sites = 本节点**显式发布**的公开指针 (IPNS); 没发布就是空数组, 不猜
8231
+ const body = JSON.stringify({ ...snap, status: np.snapshotStatus(snap), agent_sites: np.readAgentSites() });
8232
+ const { createHash } = await import('crypto');
8233
+ const etag = '"' + createHash('sha256').update(body).digest('hex').slice(0, 32) + '"';
8234
+ res.setHeader('Cache-Control', 'public, max-age=15, stale-while-revalidate=15');
8235
+ res.setHeader('ETag', etag);
8236
+ if (req.headers['if-none-match'] === etag) {
8237
+ res.status(304).end();
8238
+ return;
8239
+ }
8240
+ res.type('application/json').send(body);
8241
+ }
8242
+ catch (e) {
8243
+ // 观察层不可用 → 如实 unavailable (不是 500 空体, 也不是"网络为空")
8244
+ try {
8245
+ const np = await import('../agents/network-pulse.js');
8246
+ res.type('application/json').send(JSON.stringify(await np.getNetworkPulse({ unavailable: true })));
8247
+ }
8248
+ catch {
8249
+ res.status(503).json({ status: 'unavailable', error: 'network pulse unavailable' });
8250
+ }
8251
+ }
8252
+ });
8224
8253
  app.get('/api/health', async (req, res) => {
8225
8254
  try {
8226
8255
  if (!healthMonitor) {
@@ -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.31",
4
4
  "type": "module",
5
5
  "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli-entry.js",