@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
@@ -0,0 +1,302 @@
1
+ /**
2
+ * task-onchain-runner.ts — P4: 把链上 escrow **接进任务链路**的任务级入口
3
+ * =========================================================================
4
+ * 与 `runTask()` (x402/local-dev 支付) 并列的一条任务链路 —— 区别只在钱的走法:
5
+ *
6
+ * 预算闸(M1) → 荐资源 → beginTransaction → [createEscrowV2 → 装+真跑技能 → submitProofV2 → releaseV2]
7
+ * ↑ 每一步都走 verifyPaymentOnChain
8
+ * → 验真门(只有 chainSettled=true 才 verified) → 报告卡
9
+ *
10
+ * 借用的都是任务侧既有件: task-budget / resource-advisor / installBundle /
11
+ * executeContractSkill / resource-contract 保真校验 / report-card / transaction-store。
12
+ * 不重写任何支付判定: 链上判定只有 P3 chain-settlement 一份, 钱的状态只有
13
+ * settlement-state 一份, 链上事实只有 chain-state-store 一份。
14
+ *
15
+ * 三条硬规则 (与 runTask 一致):
16
+ * ① local-dev 永远到不了 fully_settled; ② chainSettled!==true 永远不 verified;
17
+ * ③ 非法迁移拒绝并给原因, 不静默修正。
18
+ * 另加一条 (链上特有): 任何一步链上事实不成立 → 原样停下, **不重付/不自动退款**;
19
+ * 重启时先读 chain-state.json 重建 (recoverOnchainTrade) 再决定续跑。
20
+ */
21
+ import * as fs from 'fs';
22
+ import * as path from 'path';
23
+ import { resolveTaskBudget } from './task-budget.js';
24
+ import { adviseResource } from './resource-advisor.js';
25
+ import { defaultRequestId, installBundle, deriveSkillInput, extractSources, extractConclusion } from './task-runner.js';
26
+ import { buildReportCard, renderReportCard } from './report-card.js';
27
+ import { loadResourceContract, executeContractSkill, validateResourceOutput, validateResourceInput, verifyInstallFidelity, } from '../x402/resource-contract.js';
28
+ import { beginTransaction, readTransaction, updateTransaction } from '../x402/transaction-store.js';
29
+ import { writeDeliveryContent } from '../x402/settlement-state.js';
30
+ import { computeContentHash } from '../x402/paid-info-protocol.js';
31
+ import { runOnchainTradeLoop, resumeOnchainTrade, recoverOnchainTrade, sha256Digest, checkOnchainAmount, onchainTaskKey, applyChainSettlementToTransaction, } from '../chain/onchain-trade.js';
32
+ /**
33
+ * 跑一条"链上托管"的任务: 买(托管) → 真执行 → 卖方上链承诺 → 买方释放 → 验真。
34
+ */
35
+ export async function runTaskOnchain(opts) {
36
+ const stages = [];
37
+ let last = Date.now();
38
+ const mark = (stage, note) => {
39
+ const now = Date.now();
40
+ stages.push({ stage, note, ms: now - last });
41
+ last = now;
42
+ opts.onStage?.(stage, note);
43
+ };
44
+ const decimals = opts.tokenDecimals ?? 6;
45
+ const requestId = opts.requestId || defaultRequestId(opts.task, Number(opts.budget ?? 0.05));
46
+ const taskId = requestId; // 任务键 = 幂等键 (重复跑同一任务不会新开托管)
47
+ const taskKey = onchainTaskKey(taskId);
48
+ const outputIssues = [];
49
+ // ① 预算闸 (M1: 单任务 0.05 / 单次 0.02, 所有层取最小值; 非法 → 不记账不花钱)
50
+ const parsed = resolveTaskBudget({ taskBudget: opts.budget, perPurchase: opts.perPurchase });
51
+ if (!parsed.ok || !parsed.plan) {
52
+ const card = buildReportCard({ task: opts.task, executed: false, paid: false, evidenceRef: {}, blocker: `预算不合法: ${parsed.error}` });
53
+ mark('report', '预算非法 → 没有托管, 没有花钱');
54
+ return {
55
+ ok: false, taskKey, requestId, transactionId: '', card, text: renderReportCard(card), budget: null,
56
+ chain: { verified: false, reason: '预算非法', statuses: [] }, outputIssues: [String(parsed.error)],
57
+ recovery: recoverOnchainTrade({ home: opts.home, taskKey }), stages,
58
+ };
59
+ }
60
+ const budget = parsed.plan;
61
+ mark('prepare', `任务: ${opts.task} · 任务预算 ${budget.taskBudget} / 单次上限 ${budget.perPurchase} USDC`);
62
+ // ② 荐资源 (用户不点名; 没有可执行资源就不买)
63
+ let chosen = opts.choose ? await opts.choose() : null;
64
+ if (!chosen && !opts.choose) {
65
+ const advised = await adviseResource({ task: opts.task, home: opts.home, cwd: opts.cwd, skillPaths: opts.skillPaths });
66
+ if (advised.needed && advised.chosen) {
67
+ chosen = {
68
+ name: advised.chosen.name, version: advised.chosen.version, dir: advised.chosen.dir,
69
+ contract: advised.chosen.contract, why: advised.chosen.why,
70
+ };
71
+ }
72
+ else {
73
+ const card = buildReportCard({ task: opts.task, executed: false, paid: false, evidenceRef: {}, blocker: `没有可用的可执行资源: ${advised.reason}` });
74
+ mark('report', '没有可执行资源 → 不买, 如实报告');
75
+ return {
76
+ ok: false, taskKey, requestId, transactionId: '', card, text: renderReportCard(card), budget,
77
+ chain: { verified: false, reason: '没有可执行资源', statuses: [] }, outputIssues: [advised.reason],
78
+ recovery: recoverOnchainTrade({ home: opts.home, taskKey }), stages,
79
+ };
80
+ }
81
+ }
82
+ if (!chosen) {
83
+ const card = buildReportCard({ task: opts.task, executed: false, paid: false, evidenceRef: {}, blocker: '注入的资源选择器没有给出候选' });
84
+ return {
85
+ ok: false, taskKey, requestId, transactionId: '', card, text: renderReportCard(card), budget,
86
+ chain: { verified: false, reason: '没有候选', statuses: [] }, outputIssues: ['没有候选'],
87
+ recovery: recoverOnchainTrade({ home: opts.home, taskKey }), stages,
88
+ };
89
+ }
90
+ mark('acquire', `选中 ${chosen.name} (${(chosen.why || []).slice(0, 2).join(' / ') || '名字匹配'})`);
91
+ // ②.5 输入预检 (不花钱的前置门): 输入建不出来/不合 inputSchema → **绝不先注资**
92
+ // 教训: 先注资再执行, 执行不达标 → 钱卡在托管里 (只能 dispute+refund 才拿得回)。
93
+ // 契约的 inputSchema 在这里就能判, 所以把这道门放在任何链上写操作之前。
94
+ const preContract = chosen.contract || (await loadResourceContract(chosen.dir)).contract;
95
+ const preInput = opts.input ?? deriveSkillInput(preContract, opts.task);
96
+ const preChk = preContract ? validateResourceInput(preContract, preInput) : { ok: true, issues: [] };
97
+ if (!preChk.ok) {
98
+ const blocker = `输入不符合 inputSchema (${preChk.issues[0] || '未知'}) → 未开托管, 没花钱`;
99
+ const card = buildReportCard({ task: opts.task, executed: false, paid: false, evidenceRef: {}, blocker });
100
+ mark('report', '输入不完整 → 不开托管, 不花钱 (给 --input 或把数字写进任务文本)');
101
+ return {
102
+ ok: false, taskKey, requestId, transactionId: '', card, text: renderReportCard(card), budget,
103
+ skill: { name: chosen.name, version: chosen.version, dir: chosen.dir },
104
+ chain: { verified: false, reason: blocker, statuses: [] }, outputIssues: [...preChk.issues, blocker],
105
+ recovery: recoverOnchainTrade({ home: opts.home, taskKey }), stages,
106
+ };
107
+ }
108
+ // ③ 金额 = 单次购买上限 (与 M1 硬约束一致); 转原子单位
109
+ const amountAtomic = BigInt(Math.round(budget.perPurchase * 10 ** decimals));
110
+ const amountChk = checkOnchainAmount({ amountAtomic, decimals, budget: { taskBudget: budget.taskBudget, perPurchase: budget.perPurchase } });
111
+ if (!amountChk.ok) {
112
+ const card = buildReportCard({ task: opts.task, executed: false, paid: false, evidenceRef: {}, blocker: `预算门拒绝 (${amountChk.layer}): ${amountChk.reason}` });
113
+ return {
114
+ ok: false, taskKey, requestId, transactionId: '', card, text: renderReportCard(card), budget,
115
+ chain: { verified: false, reason: amountChk.reason, statuses: [] }, outputIssues: [amountChk.reason],
116
+ recovery: recoverOnchainTrade({ home: opts.home, taskKey }), stages,
117
+ };
118
+ }
119
+ // ④ 交易记录 (任务链路侧) —— 链上事实与它分开记, 最后一起进报告卡
120
+ const { record: created } = await beginTransaction({
121
+ requestId,
122
+ metadata: { itemId: chosen.name, price: amountChk.amountUsdc, currency: 'USDC', network: opts.network || 'unknown', payTo: opts.agentAddress, providerDid: 'did:key:zLocalSeller' },
123
+ buyerDid: opts.buyerDid || 'did:key:zTaskBuyer',
124
+ }, opts.home);
125
+ const transactionId = created.transactionId;
126
+ if (created.status === 'discovered')
127
+ await updateTransaction(transactionId, { status: 'quoted' }, opts.home);
128
+ await updateTransaction(transactionId, {
129
+ status: 'paying', paymentMode: 'escrow', settlementFact: 'payment_submitted', amount: amountChk.amountUsdc,
130
+ event: { kind: 'escrow:intent', detail: `${amountChk.amountUsdc} USDC → escrow ${opts.client.escrowAddress}` },
131
+ }, opts.home);
132
+ const tradeReq = {
133
+ client: opts.client, home: opts.home, taskId, agentAddress: opts.agentAddress,
134
+ amountAtomic, paymentAsset: opts.paymentAsset,
135
+ termsDigest: sha256Digest(`terms:${taskId}`), quoteDigest: sha256Digest(`quote:${taskId}`),
136
+ inputDigest: sha256Digest(`input:${taskId}`), manifestDigest: sha256Digest(`manifest:${taskId}`),
137
+ proofVersion: opts.proofVersion ?? 1,
138
+ deadline: opts.deadline ?? BigInt(Math.floor(Date.now() / 1000) + 3600),
139
+ confirmationWindow: opts.confirmationWindow ?? 3600,
140
+ budget: { taskBudget: budget.taskBudget, perPurchase: budget.perPurchase },
141
+ network: opts.network || 'unknown', tokenDecimals: decimals,
142
+ buyerSigner: opts.buyerSigner, sellerSigner: opts.sellerSigner,
143
+ env: opts.env, persistState: opts.persistState, verifyOnChain: opts.verifyOnChain,
144
+ settleRetryAttempts: opts.settleRetryAttempts, settleRetryDelayMs: opts.settleRetryDelayMs,
145
+ };
146
+ // ⑤ 幂等/恢复: 已经链上走过的任务**不新开托管** (先读盘重建)
147
+ const before = recoverOnchainTrade({ home: opts.home, taskKey });
148
+ let loopVerified = false, loopReason = '';
149
+ const txHashes = {};
150
+ const statuses = [];
151
+ let execResult;
152
+ let resultDigest;
153
+ let deliveredContent = '';
154
+ let fidelity = { ok: false };
155
+ let outChk = { ok: false };
156
+ let conclusion = '';
157
+ let sources = [];
158
+ let contract = chosen.contract;
159
+ /** 真执行 (装 + 跑 + 契约校验) —— 与 runTask 同一套件, 不下链 */
160
+ const doExecute = async () => {
161
+ const SHARE = await import('../skill-share.js');
162
+ const collected = await SHARE.collectSkillBundle(chosen.dir, { name: chosen.name });
163
+ if (!collected?.ok || !collected?.bundle)
164
+ return { ok: false, reason: `技能打包失败: ${collected?.error || '未知'}` };
165
+ deliveredContent = JSON.stringify(collected.bundle);
166
+ const contentHash = computeContentHash(deliveredContent);
167
+ const installDir = path.join(opts.home, '.bolloon', 'tasks', taskId, 'skills', chosen.name);
168
+ fs.mkdirSync(installDir, { recursive: true });
169
+ const installed = installBundle(deliveredContent, installDir);
170
+ fidelity = installed.ok ? await verifyInstallFidelity({ content: deliveredContent, rec: { contentHash }, installDir }) : { ok: false, issues: [installed.error] };
171
+ if (!installed.ok) {
172
+ outputIssues.push(installed.error || '安装失败');
173
+ return { ok: false, reason: installed.error };
174
+ }
175
+ const loaded = await loadResourceContract(installDir);
176
+ if (loaded.ok && loaded.contract)
177
+ contract = loaded.contract;
178
+ const input = opts.input ?? deriveSkillInput(contract, opts.task);
179
+ const exec = await executeContractSkill({ contract, skillDir: installDir, input, allowedTools: ['skill_exec', 'read_file'], allowCodeExecution: true });
180
+ outChk = validateResourceOutput(contract, exec.output);
181
+ sources = extractSources(exec.output);
182
+ conclusion = extractConclusion(exec.output).conclusion || '';
183
+ const execution = { ok: exec.execution.ok === true && outChk.ok === true, reason: exec.execution.reason };
184
+ // 交易记录补执行证据
185
+ await updateTransaction(transactionId, {
186
+ execution: { ...exec.execution, ok: exec.execution.ok === true, schemaOk: outChk.ok === true },
187
+ resourceOutcome: { installed: fidelity.ok, executed: exec.execution.ok === true, outputContract: outChk.ok ? 'pass' : 'fail', criteriaHit: outChk.ok && sources.length > 0 && !!conclusion },
188
+ event: { kind: 'resource_outcome', detail: `installed=${fidelity.ok} executed=${exec.execution.ok === true} contract=${outChk.ok ? 'pass' : 'fail'}` },
189
+ }, opts.home);
190
+ if (!execution.ok)
191
+ outputIssues.push(...(outChk.issues || []), exec.execution.reason || '执行未达标');
192
+ // ★ 链上结果承诺 = 我手上这份交付内容的摘要 (买方可以自己复算)
193
+ return {
194
+ ok: execution.ok,
195
+ resultDigest: sha256Digest(deliveredContent),
196
+ manifestDigest: sha256Digest(`manifest:${taskId}`),
197
+ reason: execution.reason,
198
+ };
199
+ };
200
+ if (!before.found) {
201
+ const loop = await runOnchainTradeLoop(tradeReq, doExecute);
202
+ txHashes.create = loop.txHashes.create;
203
+ txHashes.proof = loop.txHashes.proof;
204
+ txHashes.release = loop.txHashes.release;
205
+ loopVerified = loop.verified;
206
+ loopReason = loop.verifiedReason;
207
+ execResult = loop.execution ? { ok: loop.execution.ok, reason: loop.execution.reason } : undefined;
208
+ resultDigest = loop.execution?.resultDigest;
209
+ for (const s of loop.steps)
210
+ statuses.push(`${s.method}:${s.chainStatus}`);
211
+ mark('acquire', loop.verified ? '链上闭环成立 (create/proof/release)' : `链上闭环未成立: ${loopReason.slice(0, 60)}`);
212
+ }
213
+ else {
214
+ // 已有链上事实 → 只做安全续跑 (绝不重付)
215
+ mark('acquire', `发现已有链上事实 (nextAction=${before.nextAction}) → 走恢复路径, 不新开托管`);
216
+ const resumed = await resumeOnchainTrade({ ...tradeReq, pendingResultDigest: opts.pendingResultDigest });
217
+ loopVerified = resumed.verified;
218
+ loopReason = resumed.reason;
219
+ for (const s of resumed.steps)
220
+ statuses.push(`${s.method}:${s.chainStatus}`);
221
+ txHashes.create = before.create?.txHash;
222
+ txHashes.proof = before.proof?.txHash;
223
+ txHashes.release = before.release?.txHash;
224
+ }
225
+ // ⑥ 验真门 + 报告卡
226
+ const afterRecovery = recoverOnchainTrade({ home: opts.home, taskKey });
227
+ const releaseRec = afterRecovery.release;
228
+ const payment = {
229
+ mode: 'escrow', chainSettled: loopVerified, trust: loopVerified ? 'verified' : 'self-attested',
230
+ txHash: txHashes.release,
231
+ };
232
+ let gateVerified = false;
233
+ let gateReason = loopReason;
234
+ if (loopVerified && releaseRec?.txHash) {
235
+ // 用**链上判定**重算一遍 (重放判定, 不新写判定逻辑): 释放记录 → 验真门
236
+ const { verifyChainSettlement } = await import('../chain/chain-settlement.js');
237
+ const verdict = await verifyChainSettlement(opts.client, {
238
+ txHash: releaseRec.txHash,
239
+ expect: { kind: 'escrow', taskKey, eventName: 'ReleasedV2', expectEscrowState: 'RELEASED' },
240
+ recorded: { blockNumber: releaseRec.blockNumber, confirmations: releaseRec.confirmations, status: releaseRec.status },
241
+ });
242
+ const recNow = await readTransaction(transactionId, opts.home);
243
+ const delivered = deliveredContent || (() => { try {
244
+ return fs.readFileSync(path.join(opts.home, '.bolloon', 'x402', 'deliveries', `${transactionId}.txt`), 'utf8');
245
+ }
246
+ catch {
247
+ return '';
248
+ } })();
249
+ if (delivered) {
250
+ const contentHash = computeContentHash(delivered);
251
+ const wr = writeDeliveryContent(transactionId, delivered, opts.home);
252
+ await updateTransaction(transactionId, {
253
+ contentHash, deliveryHash: contentHash, deliveryBytesHash: wr.hash, protocolVerified: true,
254
+ event: { kind: 'delivery_hash', detail: `deliveryHash=${contentHash.slice(0, 14)}… (与链上 resultHash 同源)` },
255
+ }, opts.home);
256
+ }
257
+ const gated = await applyChainSettlementToTransaction({
258
+ rec: (await readTransaction(transactionId, opts.home)),
259
+ home: opts.home, verdict, taskKey,
260
+ execution: recNow?.execution,
261
+ goalCriteriaMet: outChk.ok && sources.length > 0 && !!conclusion,
262
+ receipt: `chain-release:${releaseRec.txHash}`,
263
+ });
264
+ gateVerified = gated.verified;
265
+ gateReason = gated.verified ? gated.reason : `${gated.reason} (${gated.blockedBy.join(' | ')})`;
266
+ }
267
+ else if (!loopVerified) {
268
+ gateReason = `${loopReason} —— 链上没确定结算 → 不标 verified`;
269
+ }
270
+ const evidenceComplete = fidelity.ok && (execResult?.ok === true) && outChk.ok && sources.length > 0 && !!conclusion && gateVerified;
271
+ const card = buildReportCard({
272
+ task: opts.task,
273
+ conclusion: evidenceComplete ? (conclusion || '已完成') : '证据不足',
274
+ conclusionDetail: evidenceComplete ? undefined : ((outChk.issues || [])[0] || loopReason.slice(0, 120)),
275
+ skill: { name: chosen.name, version: chosen.version, dir: chosen.dir },
276
+ cost: { amount: amountChk.amountUsdc, currency: 'USDC', network: opts.network || 'unknown' },
277
+ payment,
278
+ sources,
279
+ outputContract: outChk.ok ? '通过' : '未通过',
280
+ resourceVerified: fidelity.ok ? '通过' : '未通过',
281
+ evidenceComplete,
282
+ executed: execResult?.ok === true,
283
+ paid: !!txHashes.create,
284
+ stage: 'report',
285
+ durationMs: stages.reduce((a, s) => a + s.ms, 0),
286
+ evidenceRef: { transactionId },
287
+ budgetLines: [...budget.why, ...amountChk.why],
288
+ blocker: evidenceComplete ? undefined : (loopVerified ? (outChk.ok ? undefined : `输出不符合资源契约: ${(outChk.issues || [])[0] || '未知'}`) : `链上未成立: ${loopReason.slice(0, 140)}`),
289
+ });
290
+ mark('report', card.status);
291
+ return {
292
+ ok: card.status === '已完成',
293
+ taskKey, requestId, transactionId, card, text: renderReportCard(card),
294
+ skill: { name: chosen.name, version: chosen.version, dir: chosen.dir },
295
+ amountUsdc: amountChk.amountUsdc, budget,
296
+ chain: { verified: gateVerified, reason: gateReason, ...txHashes, statuses },
297
+ execution: execResult,
298
+ outputIssues,
299
+ recovery: afterRecovery,
300
+ stages,
301
+ };
302
+ }
@@ -55,6 +55,30 @@ function faultPoint(name) {
55
55
  process.exit(97); // 模拟进程被杀
56
56
  process.kill(process.pid, 'SIGKILL');
57
57
  }
58
+ /**
59
+ * 数值字段的锚点词: 字段名本身 + 常见别名 —— 只有「锚点词紧邻数字」才算, 数字不搬家。
60
+ * 为什么需要: 早先的实现把「任务文本里第一个数字」塞给**每一个**数值字段。任务里出现
61
+ * 技术符号 (如 `μ0H_P`) 时第一个数字就是那个 `0` → field_T/tc_K/factor 全被编成 0 →
62
+ * 技能判「输入不合格→未知」→ 输出缺 required 字段 → 托管已注资但拿不到 proof (钱卡住)。
63
+ * 编造数字比缺输入更糟: 宁可让 inputSchema 报「缺字段」, 也不给一个看着像真值的假数字。
64
+ */
65
+ const NUMBER_ANCHORS = {
66
+ budgetusd: ['预算', 'budget'], budget: ['预算', 'budget'],
67
+ price: ['价格', '单价', 'price'], amount: ['金额', 'amount'], usd: ['美元', 'usd'],
68
+ count: ['数量', 'count'], qty: ['数量', 'qty'], quantity: ['数量', 'quantity'],
69
+ tol: ['容差', 'tol'], tolerance: ['容差', 'tolerance'],
70
+ };
71
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
72
+ /** 只在任务里该字段有明确锚点 (字段名/别名 后紧跟数字) 时才取数; 取不到 = 不填, 不编 */
73
+ function anchoredNumber(task, key) {
74
+ const anchors = [key, ...(NUMBER_ANCHORS[key.toLowerCase()] || [])].filter(Boolean);
75
+ for (const a of anchors) {
76
+ const m = task.match(new RegExp(`${escapeRe(a)}[^0-9\\n]{0,6}?(\\d+(?:\\.\\d+)?)`, 'i'));
77
+ if (m)
78
+ return Number(m[1]);
79
+ }
80
+ return undefined;
81
+ }
58
82
  /** 从任务文本 + 契约 inputSchema 推导输入 (确定性; M1 不做语义抽取, 用 --input 兜底) */
59
83
  export function deriveSkillInput(contract, task) {
60
84
  const schema = contract?.inputSchema || {};
@@ -75,9 +99,9 @@ export function deriveSkillInput(contract, task) {
75
99
  const isRequired = required.includes(key);
76
100
  const type = String(p.type || 'string');
77
101
  if (type === 'number' || type === 'integer') {
78
- const n = task.match(/\d+(\.\d+)?/);
79
- if (n)
80
- out[key] = Number(n[0]); // 任务里真有数字才填 (可选字段也不硬塞)
102
+ const n = anchoredNumber(task, key); // 任务里该字段有明确锚点才填 (可选字段也不硬塞)
103
+ if (n !== undefined)
104
+ out[key] = n;
81
105
  continue;
82
106
  }
83
107
  if (/market|国家|市场|地区|country|region/i.test(key)) {