@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.
- package/dist/agents/agent-gateway.js +5 -0
- package/dist/agents/agent-manifest-protocol.js +29 -0
- package/dist/agents/economic-policy.js +7 -0
- package/dist/agents/gateway-network.js +6 -0
- package/dist/agents/local-signer.js +59 -0
- package/dist/agents/network-pulse.js +450 -0
- package/dist/agents/task-contract.js +322 -0
- package/dist/agents/task-inbox.js +267 -0
- package/dist/agents/task-transport.js +280 -0
- package/dist/agents/x402/transaction-store.js +8 -1
- package/dist/cli/commands/agent.js +230 -0
- package/dist/cli/commands/index.js +61 -0
- package/dist/cli/commands/mcp.js +98 -0
- package/dist/cli/commands/network.js +334 -0
- package/dist/cli/commands/payment.js +125 -0
- package/dist/cli/commands/tasks.js +1236 -0
- package/dist/cli/commands/trade.js +229 -0
- package/dist/cli/commands/wallet.js +450 -0
- package/dist/cli/mcp/bridge.js +105 -0
- package/dist/cli/mcp/server.js +185 -0
- package/dist/cli/mcp/tools.js +612 -0
- package/dist/cli/protocol-envelope.js +346 -0
- package/dist/cli-entry.js +141 -17
- package/dist/electron-build/agents/agent-gateway.js +5 -0
- package/dist/electron-build/agents/agent-gateway.js.map +1 -1
- package/dist/electron-build/agents/agent-manifest-protocol.js +62 -0
- package/dist/electron-build/agents/agent-manifest-protocol.js.map +1 -1
- package/dist/electron-build/agents/economic-policy.js +7 -0
- package/dist/electron-build/agents/economic-policy.js.map +1 -1
- package/dist/electron-build/agents/gateway-network.js +6 -0
- package/dist/electron-build/agents/gateway-network.js.map +1 -1
- package/dist/electron-build/agents/network-pulse.js +504 -0
- package/dist/electron-build/agents/network-pulse.js.map +1 -0
- package/dist/electron-build/agents/x402/transaction-store.js +8 -1
- package/dist/electron-build/agents/x402/transaction-store.js.map +1 -1
- package/dist/web/server.js +29 -0
- package/dist/web/task-frame-server.js +211 -0
- package/package.json +1 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trade.ts — P3 `bolloon trade list|show|events|reconcile`
|
|
3
|
+
*
|
|
4
|
+
* 薄包装 (P1 §4.4 说的 /tx 面 —— 今天是 HTTP 接口, 这里包同一份落盘记录):
|
|
5
|
+
* list → `x402/transaction-store.listTransactions()`
|
|
6
|
+
* show → `readTransaction()` / `findByRequestId()`
|
|
7
|
+
* events → `replayTransaction()` (审计回放, 事件链)
|
|
8
|
+
* reconcile→ 单笔: `x402/payment-recovery.planTransactionRecovery()` (**唯一决策点**, 只出计划)
|
|
9
|
+
* 全部: `x402/transaction-store.reconcilePendingTransactions()` (重启对账的同一实现, 只钉事实/不放钱)
|
|
10
|
+
*
|
|
11
|
+
* 红线: 不绕过交易状态机、不重实现恢复逻辑、**绝不代付款**; `local-dev` 一律标 `local-dev` (不冒充链上)。
|
|
12
|
+
*/
|
|
13
|
+
import * as os from 'os';
|
|
14
|
+
import { okEnvelope, failEnvelope, envelopeForState, line, title, hint, plain, } from '../protocol-envelope.js';
|
|
15
|
+
import { transactionSummary, judgeTransaction } from './tasks.js';
|
|
16
|
+
export const TRADE_USAGE = `
|
|
17
|
+
${title('bolloon trade')}
|
|
18
|
+
bolloon trade list [--json] 交易一览 (状态 + 结算事实 + 结算口径)
|
|
19
|
+
bolloon trade show <transactionId|requestId> [--json] 单笔交易原文 (链上/联调口径分开写)
|
|
20
|
+
bolloon trade events <transactionId> [--json] 事件链 (审计回放)
|
|
21
|
+
bolloon trade reconcile [<transactionId>] [--json] 恢复计划 / 重启对账 (不付款)
|
|
22
|
+
|
|
23
|
+
选项: --json · --quiet · --request-id <id> · --timeout <ms>
|
|
24
|
+
`;
|
|
25
|
+
export async function tradeCommand(flags) {
|
|
26
|
+
const sub = flags.positionals[0];
|
|
27
|
+
switch (sub) {
|
|
28
|
+
case 'list': return tradeList(flags);
|
|
29
|
+
case 'show': return tradeShow(flags);
|
|
30
|
+
case 'events': return tradeEvents(flags);
|
|
31
|
+
case 'reconcile': return tradeReconcile(flags);
|
|
32
|
+
default:
|
|
33
|
+
return {
|
|
34
|
+
envelope: failEnvelope('INVALID_ARGUMENT', sub ? `未知 trade 子命令: ${sub}` : '缺少 trade 子命令', { usage: plain(TRADE_USAGE.trim()) }, [], 'needs_human'),
|
|
35
|
+
human: TRADE_USAGE,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function loadAll() {
|
|
40
|
+
const { listTransactions } = await import('../../agents/x402/transaction-store.js');
|
|
41
|
+
return await listTransactions(os.homedir());
|
|
42
|
+
}
|
|
43
|
+
async function tradeList(flags) {
|
|
44
|
+
const all = await loadAll();
|
|
45
|
+
const filtered = flags.requestId ? all.filter((t) => t.requestId === flags.requestId) : all;
|
|
46
|
+
const data = {
|
|
47
|
+
count: filtered.length,
|
|
48
|
+
trades: filtered.map(transactionSummary),
|
|
49
|
+
dir: '~/.bolloon/transactions',
|
|
50
|
+
settlementNote: 'settlement: chain = 链上真结算 · local-dev = 本机联调 (链上一分钱没动) · none = 没到结算',
|
|
51
|
+
};
|
|
52
|
+
const human = [
|
|
53
|
+
title('bolloon trade list'),
|
|
54
|
+
line('交易数', `${filtered.length}${flags.requestId ? ` (requestId=${flags.requestId})` : ''}`),
|
|
55
|
+
...filtered.slice(0, 25).map((t) => {
|
|
56
|
+
const s = transactionSummary(t);
|
|
57
|
+
return `\n ${s.transactionId} [${s.lifecycle}/${s.settlementFact}] ${s.settlement}${s.success ? ' ✓成功' : ''}\n ${s.publicStatus} · requestId ${s.requestId}${s.txHash ? `\n txHash ${s.txHash}` : ''}`;
|
|
58
|
+
}),
|
|
59
|
+
filtered.length ? '' : `\n 还没有交易记录`,
|
|
60
|
+
].filter(Boolean).join('\n');
|
|
61
|
+
return {
|
|
62
|
+
envelope: okEnvelope('OK', `交易 ${filtered.length} 笔`, data, filtered.map((t) => t.transactionId), null),
|
|
63
|
+
human,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function resolveOne(flags) {
|
|
67
|
+
const id = flags.positionals[1] || flags.requestId;
|
|
68
|
+
if (!id)
|
|
69
|
+
return { id: '', rec: null };
|
|
70
|
+
const { readTransaction, findByRequestId } = await import('../../agents/x402/transaction-store.js');
|
|
71
|
+
const home = os.homedir();
|
|
72
|
+
// tx- 前缀 → 直接当 transactionId; 其它先当 requestId 查 (任意 requestId 都能命中), 再退回 transactionId
|
|
73
|
+
const rec = /^tx-/.test(id)
|
|
74
|
+
? await readTransaction(id, home)
|
|
75
|
+
: ((await findByRequestId(id, home)) ?? (await readTransaction(id, home)));
|
|
76
|
+
return { id, rec };
|
|
77
|
+
}
|
|
78
|
+
async function tradeShow(flags) {
|
|
79
|
+
const { id, rec } = await resolveOne(flags);
|
|
80
|
+
if (!id) {
|
|
81
|
+
return {
|
|
82
|
+
envelope: failEnvelope('INVALID_ARGUMENT', '缺少 transactionId (或 --request-id)', { usage: plain(TRADE_USAGE.trim()) }, [], 'needs_human'),
|
|
83
|
+
human: TRADE_USAGE,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (!rec) {
|
|
87
|
+
return {
|
|
88
|
+
envelope: failEnvelope('NOT_FOUND', `找不到交易: ${id}`, { id, dir: '~/.bolloon/transactions' }, [], 'retry_same_request'),
|
|
89
|
+
human: `${title(`bolloon trade show ${id}`)}\n 没找到 (落盘目录 ~/.bolloon/transactions)\n\n${hint('下一步: bolloon trade list 看本地有哪些交易')}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const s = transactionSummary(rec);
|
|
93
|
+
const j = judgeTransaction(rec);
|
|
94
|
+
const data = {
|
|
95
|
+
...s,
|
|
96
|
+
events: (rec.events || []).map((e) => ({ at: e.at, kind: e.kind, detail: e.detail || null })),
|
|
97
|
+
responsibility: rec.responsibility || null,
|
|
98
|
+
dispute: rec.dispute || null,
|
|
99
|
+
policyDecision: rec.policyDecision || null,
|
|
100
|
+
resourceOutcome: rec.resourceOutcome || null,
|
|
101
|
+
verificationTrust: rec.verificationTrust || null,
|
|
102
|
+
protocolVerified: rec.protocolVerified === true,
|
|
103
|
+
paymentReceiptPresent: !!rec.paymentReceipt,
|
|
104
|
+
receiptHash: rec.receiptHash || null,
|
|
105
|
+
failureReason: rec.failureReason || null,
|
|
106
|
+
bodyPrinted: false,
|
|
107
|
+
note: '交付正文/私钥不在交易记录里 (也不进 stdout); txHash 是公开链上数据',
|
|
108
|
+
};
|
|
109
|
+
const human = [
|
|
110
|
+
title(`bolloon trade show ${rec.transactionId}`),
|
|
111
|
+
line('生命周期', `${s.lifecycle} (对外: ${s.publicStatus})`),
|
|
112
|
+
line('结算事实', s.settlementFact),
|
|
113
|
+
line('结算口径', s.settlement + (s.settlement === 'local-dev' ? ' —— 链上没动钱 (协议闭环通过, 不是支付成功)' : '')),
|
|
114
|
+
line('链上结算', s.chainSettled ? '是' : '否'),
|
|
115
|
+
line('算不算成功', s.success ? '算' : '不算'),
|
|
116
|
+
line('付款模式', s.paymentMode),
|
|
117
|
+
line('金额/网络', `${s.amount || '?'} ${s.currency || ''} @ ${s.network || '?'}`),
|
|
118
|
+
line('requestId', s.requestId),
|
|
119
|
+
...(s.txHash ? [line('txHash', s.txHash)] : []),
|
|
120
|
+
line('验真分档', data.verificationTrust || 'unverified'),
|
|
121
|
+
...(rec.responsibility ? [line('责任候选', `${rec.responsibility.type} — ${rec.responsibility.reason}`)] : []),
|
|
122
|
+
...(rec.failureReason ? [line('失败原因', rec.failureReason)] : []),
|
|
123
|
+
`\n 事件链 (${data.events.length} 条):`,
|
|
124
|
+
...data.events.slice(-12).map((e) => ` ${e.at} ${e.kind}${e.detail ? ` — ${e.detail.slice(0, 90)}` : ''}`),
|
|
125
|
+
].join('\n');
|
|
126
|
+
return {
|
|
127
|
+
envelope: envelopeForState(j.ok, j.code, `交易 ${rec.transactionId}: ${s.lifecycle}/${s.settlementFact} (${s.settlement})`, data, [rec.transactionId, rec.requestId, ...(rec.txHash ? [rec.txHash] : [])], j.next),
|
|
128
|
+
human,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function tradeEvents(flags) {
|
|
132
|
+
const { id, rec } = await resolveOne(flags);
|
|
133
|
+
if (!id) {
|
|
134
|
+
return {
|
|
135
|
+
envelope: failEnvelope('INVALID_ARGUMENT', '缺少 transactionId', { usage: plain(TRADE_USAGE.trim()) }, [], 'needs_human'),
|
|
136
|
+
human: TRADE_USAGE,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (!rec) {
|
|
140
|
+
return {
|
|
141
|
+
envelope: failEnvelope('NOT_FOUND', `找不到交易: ${id}`, { id }, [], 'retry_same_request'),
|
|
142
|
+
human: `${title(`bolloon trade events ${id}`)}\n 没找到`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const { replayTransaction } = await import('../../agents/x402/transaction-store.js');
|
|
146
|
+
const lines = await replayTransaction(rec.transactionId, os.homedir());
|
|
147
|
+
const data = { transactionId: rec.transactionId, count: lines.length, events: lines, settlementFact: String(rec.settlementFact || ''), settlement: transactionSummary(rec).settlement };
|
|
148
|
+
return {
|
|
149
|
+
envelope: okEnvelope('OK', `交易 ${rec.transactionId} 的事件链 ${lines.length} 条`, data, [rec.transactionId], null),
|
|
150
|
+
human: `${title(`bolloon trade events ${rec.transactionId}`)}\n${lines.map((l) => ` ${l}`).join('\n')}`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function tradeReconcile(flags) {
|
|
154
|
+
const id = flags.positionals[1] || flags.requestId;
|
|
155
|
+
if (!id) {
|
|
156
|
+
// 全部对账: 用重启对账的同一实现 (只钉事实 + 释放死掉的付款权, 一分钱不放)
|
|
157
|
+
const { reconcilePendingTransactions } = await import('../../agents/x402/transaction-store.js');
|
|
158
|
+
const r = await reconcilePendingTransactions(os.homedir());
|
|
159
|
+
const data = {
|
|
160
|
+
scope: 'all',
|
|
161
|
+
requeued: r.requeued,
|
|
162
|
+
mustNotRepay: r.mustNotRepay,
|
|
163
|
+
notes: r.notes,
|
|
164
|
+
source: 'x402/transaction-store.reconcilePendingTransactions (重启对账的同一实现)',
|
|
165
|
+
paid: false,
|
|
166
|
+
note: '对账只钉事实/清死锁, **不付款**; 有支付证据的一律进 mustNotRepay',
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
envelope: okEnvelope('RECOVERY_PLANNED', `对账完成: 可安全重试 ${r.requeued.length} 笔, 绝不重付 ${r.mustNotRepay.length} 笔`, data, [...r.requeued, ...r.mustNotRepay], r.requeued.length ? 'approve_payment' : null),
|
|
170
|
+
human: [
|
|
171
|
+
title('bolloon trade reconcile (全部)'),
|
|
172
|
+
line('可安全重试', `${r.requeued.length} 笔${r.requeued.length ? ': ' + r.requeued.join(', ') : ''}`),
|
|
173
|
+
line('绝不重付', `${r.mustNotRepay.length} 笔${r.mustNotRepay.length ? ': ' + r.mustNotRepay.join(', ') : ''}`),
|
|
174
|
+
line('本次付款', '没有 (对账不放钱)'),
|
|
175
|
+
...r.notes.slice(0, 12).map((n) => ` · ${n}`),
|
|
176
|
+
`\n ${data.note}`,
|
|
177
|
+
].join('\n'),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const { readTransaction, findByRequestId } = await import('../../agents/x402/transaction-store.js');
|
|
181
|
+
const home = os.homedir();
|
|
182
|
+
const rec = /^tx-/.test(id)
|
|
183
|
+
? await readTransaction(id, home)
|
|
184
|
+
: ((await findByRequestId(id, home)) ?? (await readTransaction(id, home)));
|
|
185
|
+
if (!rec) {
|
|
186
|
+
return {
|
|
187
|
+
envelope: failEnvelope('NOT_FOUND', `找不到交易: ${id}`, { id }, [], 'retry_same_request'),
|
|
188
|
+
human: `${title(`bolloon trade reconcile ${id}`)}\n 没找到`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// ★ 唯一决策点: planTransactionRecovery (不自己 if-else, 也不绕过去付款)
|
|
192
|
+
const { planTransactionRecovery } = await import('../../agents/x402/payment-recovery.js');
|
|
193
|
+
const p = planTransactionRecovery(rec);
|
|
194
|
+
const next = p.action === 'reconcile' ? 'reconcile'
|
|
195
|
+
: p.action === 'retry_payment' ? (p.mustNotRepay ? `x402_payment_retry:${rec.transactionId}` : 'approve_payment')
|
|
196
|
+
: p.action === 'deliver' || p.action === 'verify' ? `x402_continue:${rec.transactionId}`
|
|
197
|
+
: p.action === 'wait' ? 'wait'
|
|
198
|
+
: p.action === 'complete' ? null : 'needs_human';
|
|
199
|
+
const data = {
|
|
200
|
+
scope: 'single',
|
|
201
|
+
transactionId: rec.transactionId,
|
|
202
|
+
requestId: rec.requestId,
|
|
203
|
+
action: p.action,
|
|
204
|
+
mustNotRepay: p.mustNotRepay,
|
|
205
|
+
needsResponsibility: p.needsResponsibility,
|
|
206
|
+
settlementFact: p.settlementFact,
|
|
207
|
+
settlement: transactionSummary(rec).settlement,
|
|
208
|
+
chainSettled: rec.chainSettled === true,
|
|
209
|
+
txHash: rec.txHash || null,
|
|
210
|
+
reason: p.reason,
|
|
211
|
+
lifecycle: String(rec.status),
|
|
212
|
+
decisionSource: 'x402/payment-recovery.planTransactionRecovery',
|
|
213
|
+
paid: false,
|
|
214
|
+
note: '先 reconcile, 再决定 retry (顺序不许颠倒); 付款只能由持钱包的一方走同一 requestId 的幂等路径',
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
envelope: envelopeForState(p.action === 'complete', 'RECOVERY_PLANNED', `对账计划: ${p.action}${p.mustNotRepay ? ' (绝不重付)' : ''}`, data, [rec.transactionId, rec.requestId, ...(rec.txHash ? [rec.txHash] : [])], next),
|
|
218
|
+
human: [
|
|
219
|
+
title(`bolloon trade reconcile ${rec.transactionId}`),
|
|
220
|
+
line('动作', p.action),
|
|
221
|
+
line('原因', p.reason),
|
|
222
|
+
line('结算事实', p.settlementFact),
|
|
223
|
+
line('结算口径', data.settlement),
|
|
224
|
+
line('绝不重付', p.mustNotRepay ? 'true (已有支付证据 → 只继续交付/验真)' : 'false (确认没付过, 可从同一 requestId 安全重发)'),
|
|
225
|
+
line('本次付款', '没有 (只出计划)'),
|
|
226
|
+
`\n ${hint('下一步: ' + String(next ?? '无需额外动作'))}`,
|
|
227
|
+
].join('\n'),
|
|
228
|
+
};
|
|
229
|
+
}
|