@bolloon/bolloon-agent 0.4.31 → 0.4.33

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 (55) hide show
  1. package/dist/agents/chain/chain-config.js +504 -0
  2. package/dist/agents/chain/chain-index-query.js +135 -0
  3. package/dist/agents/chain/chain-indexer.js +1045 -0
  4. package/dist/agents/chain/chain-settlement.js +284 -0
  5. package/dist/agents/chain/chain-state-store.js +245 -0
  6. package/dist/agents/chain/chain-wallet.js +198 -0
  7. package/dist/agents/chain/escrow-client.js +433 -0
  8. package/dist/agents/chain/explorer.js +52 -0
  9. package/dist/agents/chain/index.js +22 -0
  10. package/dist/agents/chain/onchain-trade.js +705 -0
  11. package/dist/agents/network-pulse.js +676 -2
  12. package/dist/agents/task/task-onchain-runner.js +302 -0
  13. package/dist/agents/task/task-runner.js +27 -3
  14. package/dist/agents/task-board.js +702 -0
  15. package/dist/agents/task-group.js +567 -0
  16. package/dist/agents/x402/paid-info-store.js +75 -6
  17. package/dist/cli/commands/chain.js +926 -0
  18. package/dist/cli/commands/index.js +12 -2
  19. package/dist/cli/commands/tasks.js +627 -6
  20. package/dist/cli/mcp/server.js +4 -1
  21. package/dist/cli/mcp/tools.js +348 -5
  22. package/dist/cli/protocol-envelope.js +16 -0
  23. package/dist/cli-entry.js +6 -1
  24. package/dist/electron-build/agents/chain/chain-config.js +556 -0
  25. package/dist/electron-build/agents/chain/chain-config.js.map +1 -0
  26. package/dist/electron-build/agents/chain/chain-index-query.js +145 -0
  27. package/dist/electron-build/agents/chain/chain-index-query.js.map +1 -0
  28. package/dist/electron-build/agents/chain/chain-indexer.js +1095 -0
  29. package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -0
  30. package/dist/electron-build/agents/chain/chain-settlement.js +291 -0
  31. package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -0
  32. package/dist/electron-build/agents/chain/escrow-client.js +443 -0
  33. package/dist/electron-build/agents/chain/escrow-client.js.map +1 -0
  34. package/dist/electron-build/agents/chain/explorer.js +59 -0
  35. package/dist/electron-build/agents/chain/explorer.js.map +1 -0
  36. package/dist/electron-build/agents/local-signer.js +101 -0
  37. package/dist/electron-build/agents/local-signer.js.map +1 -0
  38. package/dist/electron-build/agents/network-pulse.js +693 -3
  39. package/dist/electron-build/agents/network-pulse.js.map +1 -1
  40. package/dist/electron-build/agents/task/task-runner.js +27 -3
  41. package/dist/electron-build/agents/task/task-runner.js.map +1 -1
  42. package/dist/electron-build/agents/task-board.js +761 -0
  43. package/dist/electron-build/agents/task-board.js.map +1 -0
  44. package/dist/electron-build/agents/task-contract.js +382 -0
  45. package/dist/electron-build/agents/task-contract.js.map +1 -0
  46. package/dist/electron-build/agents/x402/paid-info-store.js +76 -6
  47. package/dist/electron-build/agents/x402/paid-info-store.js.map +1 -1
  48. package/dist/web/chain-explorer.js +617 -0
  49. package/dist/web/explorer.html +163 -0
  50. package/dist/web/index.html +5 -0
  51. package/dist/web/routes-chain-index.js +108 -0
  52. package/dist/web/routes-onchain-trade.js +99 -0
  53. package/dist/web/server.js +13 -0
  54. package/package.json +2 -1
  55. package/scripts/build-web.ts +20 -1
@@ -73,7 +73,10 @@ export async function handleMessage(msg) {
73
73
  instructions: 'Bolloon 本机 Agent Runtime 的 MCP 入口。所有 tool 都是 `bolloon <命令组> <子命令>` 的薄包装, ' +
74
74
  '返回统一信封 { ok, code, message, data, evidence, next_action } —— **判据永远是 ok/code, 不是 MCP 的 isError 之外的任何东西**; ' +
75
75
  'ok:false 表示这次调用没有成功 (含"等人工放行"这类正常态, 例如 PAYMENT_REQUIRED + approve_payment)。' +
76
- '本适配层不发付款、不改交易历史、不伪造 verified、不返回私钥; 付款不确定时先 reconcile (绝不重付)。',
76
+ '★★ 链上写 tool (bolloon_chain_trade_create / submit_proof / release) 是**真签名 + 真移钱**的写操作: ' +
77
+ '每个都必须显式携带授权意图 (paymentMode + requestId), 缺任何一个 → NOT_AUTHORIZED (fail-closed, 不默认放行); ' +
78
+ '真签名只由本机唯一放行闸 authorizeWalletSignature 决定, MCP 层既不放行也不旁路。调用方必须自己保证已授权。' +
79
+ '本适配层不代付款、不改交易历史、不伪造 verified、不返回私钥; 付款不确定时先 reconcile (绝不重付)。',
77
80
  });
78
81
  }
79
82
  case 'ping':
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tools.ts — P4 MCP 的 tools / resources 定义 (17 tool + 7 resource)
2
+ * tools.ts — MCP 的 tools / resources 定义 (17 个 P4 tool + 7 个 P6 链只读 tool + 3 个 P6b 链写 tool = 27; 7 + 3 = 10 resource)
3
3
  *
4
4
  * 设计纪律 (P4 任务书 + `docs/wiki/agent-access-layer.md` §1):
5
5
  * ① **一个 tool = 一条 P3 子命令**。这里只做两件事: 校验入参 (白名单 + 类型)、
@@ -7,9 +7,13 @@
7
7
  * ② **客户端给的是具名参数, 不是 argv** —— 参数名走白名单, 未知参数一律 `INVALID_ARGUMENT`。
8
8
  * 由此它**不可能**注入 `--private-key` / `--mode` / `--force` 之类选项去绕过政策 (§六条禁止)。
9
9
  * ③ 值里不许以 `-` 开头 (防"值 = 选项"注入)。
10
- * ④ 没实现的 P3 子命令 (`task send|inbox|accept|reject|complete|cancel` · `network leave`)
10
+ * ④ 没实现的 P3 子命令 (`task send|inbox|accept|reject|complete|cancel` · `network leave` ·
11
+ * `chain trade expire`)
11
12
  * **不暴露成 tool**: 暴露了就得在 MCP 层假装成功, 那正好违反"失败不得变成功"。
12
13
  * 需要它们的能力时, MCP 侧应直接读 P3 的 `C_NOT_IMPLEMENTED` 说明 (见 §NOT_EXPOSED)。
14
+ * ⑤ ★ 链上**写** tool (`bolloon_chain_trade_create|submit_proof|release`) 是真签名 + 真移钱,
15
+ * 所以额外有两条: **授权意图参数必须显式携带** (缺 → `NOT_AUTHORIZED`, 见 `requireWriteIntent`),
16
+ * 以及**真签名只能由本机唯一放行闸 `authorizeWalletSignature` 产生** —— MCP 层不复制、不旁路。
13
17
  */
14
18
  import * as fs from 'fs';
15
19
  import * as path from 'path';
@@ -17,6 +21,8 @@ import { fileURLToPath } from 'url';
17
21
  import { failEnvelope } from '../protocol-envelope.js';
18
22
  import { currentPackageRoot } from '../../utils/version-info.js';
19
23
  import { callP3 } from './bridge.js';
24
+ // 授权意图词表**只有一份** (task-contract 的冻结口径) —— 这里 import 它, 不另抄一份
25
+ import { PAYMENT_MODES, isPaymentMode } from '../../agents/task-contract.js';
20
26
  // ── 入参校验 (唯一入口; 客户端**不能**直接给 argv) ────────────────────────────
21
27
  const MAX_LEN = 4000;
22
28
  class Params {
@@ -54,8 +60,8 @@ class Params {
54
60
  return v;
55
61
  }
56
62
  /** 正数 (金额/限额这类: 只收**字符串**形式的十进制, 与 P3 的原子单位口径一致) */
57
- strNumber(key) {
58
- const v = this.str(key);
63
+ strNumber(key, opts = {}) {
64
+ const v = this.str(key, opts);
59
65
  if (v === undefined)
60
66
  return undefined;
61
67
  if (!/^\d+(\.\d+)?$/.test(v)) {
@@ -76,6 +82,18 @@ class Params {
76
82
  }
77
83
  return Math.floor(v);
78
84
  }
85
+ /** 正整数 (秒/计数这类非毫秒的整数参数) */
86
+ intPos(key) {
87
+ this.touch(key);
88
+ const v = this.args[key];
89
+ if (v === undefined || v === null)
90
+ return undefined;
91
+ if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) {
92
+ this.bad.push(`参数 '${key}' 必须是正整数`);
93
+ return undefined;
94
+ }
95
+ return Math.floor(v);
96
+ }
79
97
  bool(key) {
80
98
  this.touch(key);
81
99
  const v = this.args[key];
@@ -158,6 +176,53 @@ function plan(p, group, argv) {
158
176
  }
159
177
  const TIMEOUT_KEY = 'timeoutMs';
160
178
  const TIMEOUT_PROP = { type: 'integer', minimum: 1, description: '本次调用的硬超时 (毫秒); 超时返回 P3 信封 code=TIMEOUT (不是"打印一行错误就退出")' };
179
+ // ── ★ 链上**写** tool 的授权意图 (硬要求 ②) ──────────────────────────────────
180
+ const PAYMENT_MODE_LIST = PAYMENT_MODES.join(' | ');
181
+ /**
182
+ * 写 tool (`bolloon_chain_trade_create|submit_proof|release`) 的**授权意图声明**校验。
183
+ *
184
+ * 硬要求: 调用方必须**显式**给出 `paymentMode` **与** `requestId`
185
+ * · 缺任何一个 → `NOT_AUTHORIZED` (fail-closed, **绝不默认放行**, 也不替调用方猜)
186
+ * · 给了但值非法 → `INVALID_ARGUMENT`
187
+ *
188
+ * ★ 为什么这不等于"在 MCP 层另开一道闸": 这里只校验"声明齐不齐 / 词表合不合法",
189
+ * **不判**"能不能签"。`paymentMode` 只被翻译成 CLI 的 `--payment-mode`, 真正放不放行
190
+ * 仍由本机唯一放行闸 `authorizeWalletSignature` (fail-closed) 决定 ——
191
+ * 它的 `modeIsAutonomous` 只认 `autonomous` / `agent-authorized`, 所以声明
192
+ * `manual` / `policy` 会被闸直接拒。即: 声明**只能收紧, 不可能放权**。
193
+ */
194
+ function requireWriteIntent(args) {
195
+ const mode = args.paymentMode;
196
+ const rid = args.requestId;
197
+ const missing = [];
198
+ if (mode === undefined || mode === null || mode === '')
199
+ missing.push('paymentMode');
200
+ if (rid === undefined || rid === null || rid === '')
201
+ missing.push('requestId');
202
+ if (missing.length) {
203
+ return {
204
+ ok: false,
205
+ envelope: failEnvelope('NOT_AUTHORIZED', `链上写操作必须显式声明授权意图, 缺: ${missing.join(' + ')}。MCP 层 fail-closed —— 声明缺失不默认放行。`, {
206
+ missing,
207
+ requiresExplicitAuthorization: true,
208
+ acceptedPaymentModes: [...PAYMENT_MODES],
209
+ why: '链上写 = 真签名 + 真移钱: 调用方必须先自己确认本机钱包已授权按该意图签名; MCP 不代你假定已授权',
210
+ }, [], 'needs_human'),
211
+ };
212
+ }
213
+ if (typeof mode !== 'string' || !isPaymentMode(mode.trim())) {
214
+ return {
215
+ ok: false,
216
+ envelope: failEnvelope('INVALID_ARGUMENT', `paymentMode 非法: ${JSON.stringify(mode).slice(0, 60)} (要 ${PAYMENT_MODE_LIST})`, { paymentMode: typeof mode === 'string' ? mode.slice(0, 60) : typeof mode, accepted: [...PAYMENT_MODES] }, [], 'needs_human'),
217
+ };
218
+ }
219
+ return { ok: true };
220
+ }
221
+ /** 3 个链上写 tool 共用的参数说明 (口径一致, 不各写一套) */
222
+ const WRITE_INTENT_PROPS = {
223
+ paymentMode: { type: 'string', enum: [...PAYMENT_MODES], description: `★ 必填: 授权意图 —— 声明按哪种支付模式签这笔链上写 (${PAYMENT_MODE_LIST})。缺它 → NOT_AUTHORIZED (fail-closed)。manual/policy 会被本机放行闸直接拒` },
224
+ requestId: { type: 'string', description: '★ 必填: 显式幂等/授权键 (本次声明的 id)。它参与放行闸 requestId 的确定性派生: 同一个 requestId 重复声明 → 闸按 notDuplicate 拒 (同一次意图只签一次)' },
225
+ };
161
226
  // ── 17 个 tool ──────────────────────────────────────────────────────────────
162
227
  export const TOOLS = [
163
228
  {
@@ -501,11 +566,271 @@ export const TOOLS = [
501
566
  return plan(p, 'trade', id ? ['reconcile', id] : ['reconcile']);
502
567
  },
503
568
  },
569
+ // ── P6: 链上能力 (7 个只读/本地缓存 tool; 全部薄包装 `bolloon chain ...`) ──────
570
+ {
571
+ name: 'bolloon_chain_status',
572
+ title: '链配置 + 可达性 + 钱包可用性 (只读)',
573
+ description: '只读: chainId / RPC / escrow 与 token 地址 / 确认数门槛 (confirmed=1, finalized=12) / RPC 是否可达 / ' +
574
+ '合约 bytecode / 钱包是否可用 (只给**公开地址**与余额, 私钥永不返回、永不打印)。' +
575
+ '★ 链未配置 → code=CHAIN_NOT_CONFIGURED (列出缺哪些); RPC 不可达 → CHAIN_UNAVAILABLE (绝不报成 0 余额/空状态)。' +
576
+ '★ local-dev (chainId 31337) 永不产出 fully_settled。',
577
+ inputSchema: { type: 'object', additionalProperties: false, properties: { timeoutMs: TIMEOUT_PROP } },
578
+ build(args) {
579
+ const p = new Params(args, [TIMEOUT_KEY]);
580
+ return plan(p, 'chain', ['status']);
581
+ },
582
+ },
583
+ {
584
+ name: 'bolloon_chain_escrow_show',
585
+ title: '读链上 escrow (只读)',
586
+ description: '只读: 按 taskKey 读 `AgentEscrow` 里的 escrow 19 字段 (state = ACTIVE|RELEASED|DISPUTED|REFUNDED, 金额, buyer/agent, ' +
587
+ '各 hash, deadline, proofVersion)。' +
588
+ '★ 「链上没有」(ESCROW_NOT_FOUND) 与「读不到」(CHAIN_UNAVAILABLE) 是两个码 —— 读不到绝不当成不存在。' +
589
+ '★ `state=RELEASED` 只是链上事实, 不等于本机已 verified (判据是 receipt + 事件 + 确认数)。',
590
+ inputSchema: {
591
+ type: 'object',
592
+ additionalProperties: false,
593
+ required: ['taskKey'],
594
+ properties: { taskKey: { type: 'string', description: '链上 taskKey (0x + 64 hex, keccak256 域标签派生)' }, timeoutMs: TIMEOUT_PROP },
595
+ },
596
+ build(args) {
597
+ const p = new Params(args, ['taskKey', TIMEOUT_KEY]);
598
+ const k = p.str('taskKey', { required: true, maxLen: 80 });
599
+ return plan(p, 'chain', k ? ['escrow', 'show', k] : ['escrow', 'show']);
600
+ },
601
+ },
602
+ {
603
+ name: 'bolloon_chain_timeline',
604
+ title: 'taskKey 链上时间线 (只读)',
605
+ description: '只读: 本机索引里该 taskKey 的**链上事件时间线** (块号/logIndex 升序, 每条带 finality: ' +
606
+ 'observed(确认数 < confirmed) / confirmed(≥1) / finalized(≥12)) + 本机视角 (chain-state.json 的 create/proof/release)。' +
607
+ '据此可重建 create → proof → release。' +
608
+ '★ 有被回退的记录 → code=REORG_SUSPECTED (绝不报成功); 索引里没有 → ESCROW_NOT_FOUND (先 `bolloon_chain_index_sync`)。',
609
+ inputSchema: {
610
+ type: 'object',
611
+ additionalProperties: false,
612
+ required: ['taskKey'],
613
+ properties: { taskKey: { type: 'string', description: '链上 taskKey (0x + 64 hex)' }, timeoutMs: TIMEOUT_PROP },
614
+ },
615
+ build(args) {
616
+ const p = new Params(args, ['taskKey', TIMEOUT_KEY]);
617
+ const k = p.str('taskKey', { required: true, maxLen: 80 });
618
+ return plan(p, 'chain', k ? ['timeline', k] : ['timeline']);
619
+ },
620
+ },
621
+ {
622
+ name: 'bolloon_chain_index_status',
623
+ title: '索引高度 / 最后同步 (只读)',
624
+ description: '只读: 本机链上事件索引的高度 (lastSyncedBlock)、最后同步时间、事件数/suspect 数、索引起点 (部署块与来源)、' +
625
+ '确认数门槛、reorgDepth。**不发 RPC** (只读本机索引文件); 从未同步时 synced=false (不假装有数据)。',
626
+ inputSchema: { type: 'object', additionalProperties: false, properties: { timeoutMs: TIMEOUT_PROP } },
627
+ build(args) {
628
+ const p = new Params(args, [TIMEOUT_KEY]);
629
+ return plan(p, 'chain', ['index', 'status']);
630
+ },
631
+ },
632
+ {
633
+ name: 'bolloon_chain_index_stats',
634
+ title: '链上事件统计 (只读)',
635
+ description: '只读: tasks / created / proof / released / refunded / disputed / expired 计数 + finality 分档 + 事件名直方图。' +
636
+ '★ suspect (被回退) 的记录**不计入**业务计数, 单独报出 —— 统计不是链上事实, 判据永远是 receipt/事件/确认数。',
637
+ inputSchema: { type: 'object', additionalProperties: false, properties: { timeoutMs: TIMEOUT_PROP } },
638
+ build(args) {
639
+ const p = new Params(args, [TIMEOUT_KEY]);
640
+ return plan(p, 'chain', ['index', 'stats']);
641
+ },
642
+ },
643
+ {
644
+ name: 'bolloon_chain_index_sync',
645
+ title: '增量同步链上事件到本机索引 (只写本地缓存)',
646
+ description: '★ 只做一件事: 从**本机上次同步高度 +1** 到当前 head 用 `eth_getLogs` 分页扫 v2 事件, 落 `~/.bolloon/chain/index.json`。' +
647
+ '**不动钱、不碰私钥、不改交易记录、不写结算事实** (索引是可删可重建的缓存, 不是事实源)。' +
648
+ '起点来自部署 manifest 的 deployment block (不猜 0); 检出重组 → 记录标 suspect 且 code=REORG_SUSPECTED (不静默丢弃)。' +
649
+ '★ 索引身份 (chainId + escrowAddress + 部署块) 与当前链不一致 (换过合约部署/anvil 重启) → **不扫不写**, ' +
650
+ '报 `INDEX_IDENTITY_CHANGED` + next_action=needs_human (这不是重组); 修法 `bolloon chain index rebuild` (干净重建)。',
651
+ inputSchema: {
652
+ type: 'object',
653
+ additionalProperties: false,
654
+ properties: { fromBlock: { type: 'string', description: '可选: 从这一块起补扫 (十进制块号, 不给=增量)' }, timeoutMs: TIMEOUT_PROP },
655
+ },
656
+ build(args) {
657
+ const p = new Params(args, ['fromBlock', TIMEOUT_KEY]);
658
+ const argv = ['index', 'sync'];
659
+ const from = p.strNumber('fromBlock');
660
+ if (from)
661
+ argv.push('--from-block', from);
662
+ return plan(p, 'chain', argv);
663
+ },
664
+ },
665
+ {
666
+ name: 'bolloon_chain_trade_recover',
667
+ title: '链上交易恢复状态 (只读, 不发交易)',
668
+ description: '★ **纯读盘** (~/.bolloon/chain/chain-state.json): 按 taskId / taskKey 重建本机链上事实, 给出 nextAction ' +
669
+ '(create_escrow / submit_proof / release / verify_only / done / needs_human) 与 mustNotRepay。' +
670
+ '**不发交易、不重付、不自动退款、不碰私钥** (链上写操作一律必须由本机持钱包的一方执行)。' +
671
+ '★ 有被标可疑的记录 → REORG_SUSPECTED; 结论未定 → CHAIN_UNCERTAIN + next_action=reconcile (不确定绝不报成功)。',
672
+ inputSchema: {
673
+ type: 'object',
674
+ additionalProperties: false,
675
+ properties: {
676
+ taskId: { type: 'string', description: '任务 id (与 create 时同一个; 派生 taskKey)' },
677
+ taskKey: { type: 'string', description: '或直接给链上 taskKey (0x + 64 hex)' },
678
+ timeoutMs: TIMEOUT_PROP,
679
+ },
680
+ },
681
+ build(args) {
682
+ const p = new Params(args, ['taskId', 'taskKey', TIMEOUT_KEY]);
683
+ const taskId = p.str('taskId', { maxLen: 512 });
684
+ const taskKey = p.str('taskKey', { maxLen: 80 });
685
+ const argv = ['trade', 'recover'];
686
+ if (taskId)
687
+ argv.push('--task-id', taskId);
688
+ else if (taskKey)
689
+ argv.push('--task-key', taskKey);
690
+ return plan(p, 'chain', argv);
691
+ },
692
+ },
693
+ // ── P6b: 链上**写** tool (3 个) —— 与只读链 tool 的区别只有一个: 它们**真签名 + 真移钱** ──
694
+ // 薄包装同一批 P3/P4 服务函数 (`chain trade create|submit-proof|release` → onchain-trade 的
695
+ // createEscrowStep / submitProofStep / releaseStep), **不复制任何业务逻辑**;
696
+ // 授权意图 (`paymentMode` + `requestId`) 必须显式携带 (见 requireWriteIntent), 失败按信封原样返回。
697
+ {
698
+ name: 'bolloon_chain_trade_create',
699
+ title: '★ 真签名真移钱: 建链上 escrow (买方付款进托管)',
700
+ description: '★★★ **这是真写操作: 会用本机钱包私钥真签名, 并把真 USDC 从买方打进链上 escrow 托管 (链上真 txHash)**。' +
701
+ '等价于 `bolloon chain trade create --task-id <id> --agent <addr> --amount <USDC> --payment-mode <mode> --request-id <rid>`。' +
702
+ '**调用方必须自己保证已授权** —— 本 tool 只声明意图, 不授予任何权限: 真签名只由本机唯一放行闸 ' +
703
+ '`authorizeWalletSignature` (fail-closed, 9 项) 决定, 私钥永不返回、永不打印。' +
704
+ '★ **必填** `paymentMode` 与 `requestId` (授权意图): 缺 → `NOT_AUTHORIZED`; 词表外的值 → `INVALID_ARGUMENT`; ' +
705
+ '`manual`/`policy` → 被放行闸拒 (modeIsAutonomous); 同一个 `requestId` 重复声明 → 被闸按 notDuplicate 拒。' +
706
+ '金额上限沿用 M1 硬约束 (单任务 0.05 / 单次购买 0.02 USDC, 与 economic-policy 三层取最小); 超了给 `BUDGET_EXCEEDED` 并指明哪一层, **不发交易**。' +
707
+ '失败按 P3 信封原样返回 (`isError=true`), 绝不变成 MCP 成功; 链上结论未定 → `CHAIN_UNCERTAIN` + reconcile, 不确定绝不报成功。',
708
+ inputSchema: {
709
+ type: 'object',
710
+ additionalProperties: false,
711
+ required: ['taskId', 'agent', 'amount', 'paymentMode', 'requestId'],
712
+ properties: {
713
+ taskId: { type: 'string', description: '任务 id —— 链上 taskKey 由它确定性派生 (keccak256 域标签 bolloon.task.v1)' },
714
+ agent: { type: 'string', description: '卖方收款地址 (0x + 40 hex), escrow 释放后钱真打到它' },
715
+ amount: { type: 'string', description: '托管金额 (十进制 USDC 串, 如 "0.02"); M1 单次购买上限 0.02' },
716
+ deadline: { type: 'number', description: '托管截止时间 (unix 秒); 不给 = max(链上最新块时间, 本机时间) + 确认窗口' },
717
+ confirmationWindow: { type: 'number', description: '买方确认窗口 (秒, 默认 3600); 过后卖方才能 claimAfterTimeout' },
718
+ proofVersion: { type: 'number', description: 'proof 版本 (默认 1, 参与 proofHash 口径)' },
719
+ ...WRITE_INTENT_PROPS,
720
+ timeoutMs: TIMEOUT_PROP,
721
+ },
722
+ },
723
+ build(args) {
724
+ const intent = requireWriteIntent(args);
725
+ if (!intent.ok)
726
+ return { ok: false, envelope: intent.envelope };
727
+ const p = new Params(args, ['taskId', 'agent', 'amount', 'deadline', 'confirmationWindow', 'proofVersion', 'paymentMode', 'requestId', TIMEOUT_KEY]);
728
+ const taskId = p.str('taskId', { required: true, maxLen: 512 });
729
+ const agent = p.str('agent', { required: true, maxLen: 80 });
730
+ const amount = p.strNumber('amount', { required: true });
731
+ const paymentMode = p.str('paymentMode', { required: true, maxLen: 40 });
732
+ const argv = ['trade', 'create'];
733
+ if (taskId)
734
+ argv.push('--task-id', taskId);
735
+ if (agent)
736
+ argv.push('--agent', agent);
737
+ if (amount)
738
+ argv.push('--amount', amount);
739
+ if (paymentMode)
740
+ argv.push('--payment-mode', paymentMode);
741
+ const deadline = p.intPos('deadline');
742
+ if (deadline !== undefined)
743
+ argv.push('--deadline', String(deadline));
744
+ const win = p.intPos('confirmationWindow');
745
+ if (win !== undefined)
746
+ argv.push('--confirmation-window', String(win));
747
+ const pv = p.intPos('proofVersion');
748
+ if (pv !== undefined)
749
+ argv.push('--proof-version', String(pv));
750
+ return plan(p, 'chain', argv);
751
+ },
752
+ },
753
+ {
754
+ name: 'bolloon_chain_trade_submit_proof',
755
+ title: '★ 真签名真上链: 提交交付证明 (seller 侧)',
756
+ description: '★★★ **这是真写操作: 会用**本机钱包**私钥真签名, 把交付结果摘要 (`resultHash`) 真上链 (`submitProofV2`, 链上真 txHash)**。' +
757
+ '等价于 `bolloon chain trade submit-proof --task-id <id> --result <正文|sha256:hex> --payment-mode <mode> --request-id <rid>`。' +
758
+ '常由**卖方节点**执行 (合约要求 `msg.sender == escrow.agent`); 只有当调用方就是本机钱包持有人时才可能成功。' +
759
+ '**调用方必须自己保证已授权**; 缺 `paymentMode`/`requestId` → `NOT_AUTHORIZED`, 放行闸拒 → `NOT_AUTHORIZED` 且**不发交易、不碰私钥**。' +
760
+ '★ 这一步**不动钱** (escrow 仍在 ACTIVE) —— 别把它的成功读成"已结算"。' +
761
+ '失败按信封原样返回 (`isError=true`); 未定 → `CHAIN_UNCERTAIN` (不确定绝不报成功)。',
762
+ inputSchema: {
763
+ type: 'object',
764
+ additionalProperties: false,
765
+ required: ['taskId', 'result', 'paymentMode', 'requestId'],
766
+ properties: {
767
+ taskId: { type: 'string', description: '任务 id (与 create 时必须同一个; 派生 taskKey)' },
768
+ result: { type: 'string', description: '交付结果正文或 "sha256:<hex>" 摘要 (链上 resultHash 的来源, 只上链摘要不含正文)' },
769
+ manifestDigest: { type: 'string', description: '可选: manifest 摘要 (正文 或 "sha256:<hex>")' },
770
+ ...WRITE_INTENT_PROPS,
771
+ timeoutMs: TIMEOUT_PROP,
772
+ },
773
+ },
774
+ build(args) {
775
+ const intent = requireWriteIntent(args);
776
+ if (!intent.ok)
777
+ return { ok: false, envelope: intent.envelope };
778
+ const p = new Params(args, ['taskId', 'result', 'manifestDigest', 'paymentMode', 'requestId', TIMEOUT_KEY]);
779
+ const taskId = p.str('taskId', { required: true, maxLen: 512 });
780
+ const result = p.str('result', { required: true, maxLen: MAX_LEN });
781
+ const paymentMode = p.str('paymentMode', { required: true, maxLen: 40 });
782
+ const argv = ['trade', 'submit-proof'];
783
+ if (taskId)
784
+ argv.push('--task-id', taskId);
785
+ if (result)
786
+ argv.push('--result', result);
787
+ const manifest = p.str('manifestDigest', { maxLen: MAX_LEN });
788
+ if (manifest)
789
+ argv.push('--manifest-digest', manifest);
790
+ if (paymentMode)
791
+ argv.push('--payment-mode', paymentMode);
792
+ return plan(p, 'chain', argv);
793
+ },
794
+ },
795
+ {
796
+ name: 'bolloon_chain_trade_release',
797
+ title: '★ 真签名真移钱: 释放托管 (钱真打到 seller)',
798
+ description: '★★★ **这是真写操作: 会用本机钱包私钥真签名, 把 escrow 里的 USDC 真释放给 seller (`releaseV2`, 链上真 txHash, 钱真到账)**。' +
799
+ '等价于 `bolloon chain trade release --task-id <id> --payment-mode <mode> --request-id <rid>`。' +
800
+ '**调用方必须自己保证已授权** —— 声明只是声明, 真签名只由本机放行闸 `authorizeWalletSignature` (fail-closed) 决定; ' +
801
+ '缺 `paymentMode`/`requestId` → `NOT_AUTHORIZED` (不默认放行), 闸拒 → `NOT_AUTHORIZED` 且钱**没动**。' +
802
+ '★ 只有全过 (事件对上 + 合约 RELEASED + 确认数达标) 才 `grantsVerified=true`; 没过一律 `CHAIN_UNCERTAIN` (不许标 verified)。' +
803
+ '失败按信封原样返回 (`isError=true`), 绝不变成 MCP 成功。',
804
+ inputSchema: {
805
+ type: 'object',
806
+ additionalProperties: false,
807
+ required: ['taskId', 'paymentMode', 'requestId'],
808
+ properties: {
809
+ taskId: { type: 'string', description: '任务 id (与 create 时必须同一个; 派生 taskKey)' },
810
+ ...WRITE_INTENT_PROPS,
811
+ timeoutMs: TIMEOUT_PROP,
812
+ },
813
+ },
814
+ build(args) {
815
+ const intent = requireWriteIntent(args);
816
+ if (!intent.ok)
817
+ return { ok: false, envelope: intent.envelope };
818
+ const p = new Params(args, ['taskId', 'paymentMode', 'requestId', TIMEOUT_KEY]);
819
+ const taskId = p.str('taskId', { required: true, maxLen: 512 });
820
+ const paymentMode = p.str('paymentMode', { required: true, maxLen: 40 });
821
+ const argv = ['trade', 'release'];
822
+ if (taskId)
823
+ argv.push('--task-id', taskId);
824
+ if (paymentMode)
825
+ argv.push('--payment-mode', paymentMode);
826
+ return plan(p, 'chain', argv);
827
+ },
828
+ },
504
829
  ];
505
830
  /** tool 名 → 定义 */
506
831
  export const TOOL_BY_NAME = new Map(TOOLS.map((t) => [t.name, t]));
507
832
  /**
508
- * **刻意不暴露**的 P3 子命令 (暴露 = 必须在 MCP 层假装成功, 违反"失败不得变成功"):
833
+ * **刻意不暴露**的 P3/P6 子命令 (暴露 = 必须在 MCP 层假装成功, 违反"失败不得变成功"):
509
834
  * task complete · task cancel → P3 里如实报 `C_NOT_IMPLEMENTED` (没有可写的任务状态存储/交易层无 cancelled)
510
835
  * task send · task inbox · task accept · task reject → **写操作** (DID 签名 + 落本机台账 + 对外发帧),
511
836
  * 不在本轮的 17 个冻结清单里; 要纳入 MCP 必须单独裁决 (它会改变本机对外承诺, 不是只读能力)
@@ -513,6 +838,19 @@ export const TOOL_BY_NAME = new Map(TOOLS.map((t) => [t.name, t]));
513
838
  * network init / peers · agent inspect · trade events · wallet policy(只读) / set-policy
514
839
  * → 前几个是"本机节点生命周期/本地诊断", 不该给远端 Agent 调;
515
840
  * **wallet set-policy 尤其不暴露** —— 让远端 Agent 改支付策略 = 绕过 payment policy (六条禁止之一)。
841
+ * ★ P6 追加: `bolloon chain trade create|submit-proof|release` 曾是「不暴露」——
842
+ * 现已改为**暴露为 MCP 写 tool (3 个)**, 但代价条件全部保留并加固 (2026-09-22 leo 拍板):
843
+ * · 唯一放行闸不变: 仍然只走 `sendChainTxGuarded` → `authorizeWalletSignature` (fail-closed),
844
+ * MCP 层**没有**任何旁路, 也不复制判钱/签发逻辑;
845
+ * · 每个写 tool **强制显式携带授权意图** (`paymentMode` + `requestId`): 缺 → `NOT_AUTHORIZED`,
846
+ * 非法 → `INVALID_ARGUMENT` (绝不默认放行);
847
+ * · 失败一律按 P3 信封原样返回 (`isError=true`), 不洗成 MCP 成功;
848
+ * · 私钥/任务正文不打印、不落盘; 审计仍写 `~/.bolloon/wallet-signatures.jsonl`;
849
+ * · 金额上限沿用 M1 硬约束 (0.05 / 0.02 / 0.10, 与 economic-policy 三层取最小)。
850
+ * `bolloon chain trade expire` **不暴露**: 仓库里没有这条子命令 (合约侧有 expireV2, CLI 未接) ——
851
+ * 暴露了就得在 MCP 层假装成功。
852
+ * `bolloon chain index sync` 例外地暴露: 它只读链 (eth_getLogs) 并刷新本机**可删可重建**的索引缓存,
853
+ * 不动钱、不碰私钥、不改交易记录/结算事实 —— 不构成"本机运维写动作"。
516
854
  */
517
855
  export const NOT_EXPOSED = [
518
856
  { command: 'bolloon task complete|cancel', reason: 'P3 如实报 C_NOT_IMPLEMENTED (没有可写的任务状态存储 / 交易层无 cancelled); 暴露会逼 MCP 层假装成功' },
@@ -522,6 +860,7 @@ export const NOT_EXPOSED = [
522
860
  { command: 'bolloon network init|peers', reason: '本机节点生命周期 / 本进程 peer 列表, 不是给远端 Agent 的能力' },
523
861
  { command: 'bolloon wallet set-policy', reason: '远端改支付策略 = 绕过 payment policy (六条禁止); 必须由本机用户执行' },
524
862
  { command: 'bolloon agent inspect / trade events', reason: '诊断/审计视角; 等有明确外部需求再按同一薄包装方式加' },
863
+ { command: 'bolloon chain trade expire', reason: '仓库里**没有**这条子命令 (合约侧有 permissionless expireV2, CLI 未接) —— 暴露会逼 MCP 层假装成功; 要暴露先把 CLI 子命令实现出来' },
525
864
  ];
526
865
  /** 一个"信封资源": 内容 = P3 信封原样 JSON (含 ok/code/next_action, 绝不改写成 MCP 成功) */
527
866
  async function envelopeResource(uri, group, argv, timeoutMs) {
@@ -595,6 +934,10 @@ export const RESOURCES = [
595
934
  { uri: 'bolloon://tasks/recent', name: '最近任务/交易', description: '本地交易 + Goal (只给 id/状态/结算口径, 不含任务正文)', mimeType: 'application/json', load: () => envelopeResource('bolloon://tasks/recent', 'task', ['list']) },
596
935
  { uri: 'bolloon://trades/recent', name: '最近交易', description: '交易一览 (chain / local-dev / none 口径分开写)', mimeType: 'application/json', load: () => envelopeResource('bolloon://trades/recent', 'trade', ['list']) },
597
936
  { uri: 'bolloon://wallet/policy', name: '支付策略', description: '单笔/日限额 + 白名单 + 速率 + 今日已用 (只读, 无私钥)', mimeType: 'application/json', load: () => envelopeResource('bolloon://wallet/policy', 'wallet', ['policy']) },
937
+ // ── P6: 链上能力 (信封原样; 链未配置时信封里就是 CHAIN_NOT_CONFIGURED, 不当成 MCP 成功) ──
938
+ { uri: 'bolloon://chain/status', name: '链配置 + 可达性', description: 'chainId / RPC / 合约地址 / 确认数门槛 / 钱包可用性 (只给公开地址, 无私钥)', mimeType: 'application/json', load: () => envelopeResource('bolloon://chain/status', 'chain', ['status']) },
939
+ { uri: 'bolloon://chain/index', name: '链上索引高度', description: '索引高度 / 最后同步时间 / 事件数 / suspect 数 (只读本机索引文件, 不发 RPC)', mimeType: 'application/json', load: () => envelopeResource('bolloon://chain/index', 'chain', ['index', 'status']) },
940
+ { uri: 'bolloon://chain/index/stats', name: '链上事件统计', description: 'tasks/created/proof/released/refunded/disputed/expired + finality 分档 (suspect 不计入)', mimeType: 'application/json', load: () => envelopeResource('bolloon://chain/index/stats', 'chain', ['index', 'stats']) },
598
941
  { uri: 'bolloon://skill/current', name: 'bolloon-network Skill', description: 'skills/bolloon-network/SKILL.md 原文 (对外唯一入口说明)', mimeType: 'text/markdown', load: () => skillPayload('bolloon://skill/current') },
599
942
  ];
600
943
  export const RESOURCE_BY_URI = new Map(RESOURCES.map((r) => [r.uri, r]));
@@ -39,6 +39,13 @@ export const CLI_CODES = [
39
39
  'TASK_REJECTED', 'TASK_FAILED', 'TASK_CANCELLED',
40
40
  // 传输层 (§1.3「新增错误码不 bump 版本」): 帧没送到 / 没有可用传输
41
41
  'TRANSPORT_UNAVAILABLE', 'TRANSPORT_FAILED',
42
+ // P6 链命令组的新增码 (append-only; 表里确实没有对应语义才加):
43
+ // 配置/可达性/结论未定/链上回滚/找不到 escrow/资金不足/未授权/重组可疑
44
+ 'CHAIN_NOT_CONFIGURED', 'CHAIN_UNAVAILABLE', 'CHAIN_UNCERTAIN', 'CHAIN_TX_REVERTED',
45
+ 'ESCROW_NOT_FOUND', 'INSUFFICIENT_FUNDS', 'NOT_AUTHORIZED', 'REORG_SUSPECTED',
46
+ // 2026-09-22 (P5 修正): **索引身份变更** (换合约部署 / anvil 重启换链实例) —— 这**不是重组**,
47
+ // 混进 REORG_SUSPECTED 会把人引到错的方向 (去查分叉, 而真因是索引属于另一个合约/另一条链)。
48
+ 'INDEX_IDENTITY_CHANGED',
42
49
  // 通用失败码
43
50
  'C_NOT_IMPLEMENTED', 'INVALID_ARGUMENT', 'NOT_FOUND', 'TIMEOUT', 'INTERNAL_ERROR',
44
51
  ];
@@ -59,6 +66,15 @@ const OPTIONS_WITH_VALUE = new Set([
59
66
  // 2026-09-21 (P3 收尾): bolloon-task/1 真收发 + wallet sign 用到的选项
60
67
  '--payload', '--message', '--mode', '--task-id', '--deadline', '--salt', '--reply-to',
61
68
  '--peer', '--via', '--eta', '--deliver',
69
+ // 2026-09-22 (P6): `bolloon chain` 用的选项 (只加链命令组自己的名字, 不改既有布尔开关的解析)
70
+ '--task-key', '--agent', '--asset', '--result', '--manifest-digest',
71
+ '--confirmation-window', '--proof-version', '--from-block', '--gate',
72
+ // 2026-09-22 (P6b): 链上**写**操作的**授权意图**声明 (MCP 写 tool 必填; 只可能收紧, 不可能放权)
73
+ '--payment-mode',
74
+ // 2026-09-23 (C1): 任务公告板 `bolloon task publish|board|claim` 的显式公告号
75
+ '--announcement-id',
76
+ // 2026-09-23 (C7): 群聊通道 `bolloon task announce|trail|post` 的群链接/groupId + 极短事实字段
77
+ '--group', '--round', '--criteria', '--kind', '--hash', '--bytes', '--checks', '--verdict', '--limit', '--from',
62
78
  ]);
63
79
  export function parseFlags(args) {
64
80
  const flags = { json: false, quiet: false, positionals: [], options: new Map(), raw: args };
package/dist/cli-entry.js CHANGED
@@ -30,7 +30,7 @@ import { legacyJson } from './cli/protocol-envelope.js';
30
30
  import { createRequire } from 'module';
31
31
  const _require = createRequire(import.meta.url);
32
32
  /** P3 `bolloon task <子命令>` 的已知子命令 —— 其它一律当 M1 任务正文 (既有体验不动) */
33
- const TASK_SUBCOMMANDS = new Set(['send', 'list', 'status', 'cancel', 'retry', 'result', 'inbox', 'accept', 'reject', 'run', 'complete']);
33
+ const TASK_SUBCOMMANDS = new Set(['send', 'list', 'status', 'cancel', 'retry', 'result', 'inbox', 'accept', 'reject', 'run', 'complete', 'publish', 'board', 'claim']);
34
34
  const isWindows = process.platform === 'win32';
35
35
  // ANSI 颜色 — Bolloon Web UI 配色 truecolor
36
36
  function _fg(r, g, b) { return `\x1b[38;2;${r};${g};${b}m`; }
@@ -193,6 +193,9 @@ function parseArgs() {
193
193
  return { mode: 'payment', args: args.slice(1) };
194
194
  case 'trade':
195
195
  return { mode: 'trade', args: args.slice(1) };
196
+ // 2026-09-22 (P6): 链命令组 (`bolloon chain status|escrow|timeline|index|trade`; 复用 P3/P4/P5)
197
+ case 'chain':
198
+ return { mode: 'chain', args: args.slice(1) };
196
199
  // 2026-09-21: P4 MCP 适配层 (`bolloon mcp serve` = stdio MCP server; 只调 P3 服务层)
197
200
  case 'mcp':
198
201
  return { mode: 'mcp', args: args.slice(1) };
@@ -870,11 +873,13 @@ async function main() {
870
873
  await handleTaskCommand(args);
871
874
  break;
872
875
  // 2026-09-21 (P3): 命令组 (network/agent/wallet/payment/trade) —— 统一信封 + 薄包装现有服务
876
+ // 2026-09-22 (P6): 追加 chain (链上能力: 状态/escrow/时间线/索引/交易)
873
877
  case 'network':
874
878
  case 'agent':
875
879
  case 'wallet':
876
880
  case 'payment':
877
881
  case 'trade':
882
+ case 'chain':
878
883
  process.exit(await runServiceGroup(mode, args));
879
884
  break;
880
885
  // 2026-09-21 (P4): MCP 适配层 (`bolloon mcp serve` = stdio MCP server; tools/resources 只调 P3 服务层)