@lawrenceliang-btc/atel-sdk 1.1.28 → 1.1.30

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 1.1.29 - 2026-04-06
4
+
5
+ ### Improved
6
+ - Clarified TokenHub CLI help so API key creation and account commands are easier to discover.
7
+ - Tightened English terminology across README, quick-start guides, and SKILL references.
8
+ - Standardized the distinction between DID-signed platform requests and TokenHub API-key requests.
9
+ - Updated swap guidance to describe `pending_verification` precisely.
10
+
11
+ ### Fixed
12
+ - `atel key create` now falls back to DID-signed bootstrap when a saved hub API key is invalid or revoked.
13
+ - `atel hub swap` now reports `pending_verification` as a submitted settlement state instead of implying final success.
package/README.md CHANGED
@@ -51,10 +51,18 @@ ATEL provides the cryptographic primitives and protocol building blocks that ena
51
51
 
52
52
  ### Installation
53
53
 
54
+ Install the CLI globally if you want the `atel` command:
55
+
54
56
  ```bash
55
57
  npm install -g @lawrenceliang-btc/atel-sdk
56
58
  ```
57
59
 
60
+ Install the package locally if you want to embed the SDK in your own runtime or app:
61
+
62
+ ```bash
63
+ npm install @lawrenceliang-btc/atel-sdk
64
+ ```
65
+
58
66
  ### Initialize Your Agent
59
67
 
60
68
  ```bash
@@ -63,6 +71,16 @@ atel register "My Agent" "assistant,research"
63
71
  atel start 3100
64
72
  ```
65
73
 
74
+ ### Bootstrap TokenHub Access
75
+
76
+ ```bash
77
+ atel key create --name my-agent-key
78
+ atel key list
79
+ atel key use
80
+ ```
81
+
82
+ `atel key use` prints the OpenAI-compatible environment exports for the currently selected TokenHub API key. The grouped alias `atel hub key ...` is still supported, but `atel key ...` is the recommended first-run path.
83
+
66
84
  If you want to support paid Platform orders on EVM chains, configure at least one paid-order chain key before or after registering:
67
85
 
68
86
  ```bash
@@ -90,6 +108,39 @@ For custom runtimes, point `ATEL_EXECUTOR_URL` at your own service.
90
108
 
91
109
  For paid orders, do not hardcode Base as the only chain. Runtime actions that touch escrow, release, refund, milestone anchoring, chain-record inspection, or balance interpretation must follow `order.chain`.
92
110
 
111
+
112
+ ## TokenHub and AI Gateway
113
+
114
+ ATEL currently exposes two related but distinct integration surfaces:
115
+
116
+ - **DID-signed platform requests** under `/account/v1/...`
117
+ These are used by the CLI for account actions such as balance lookup, swaps, transfers, and account history.
118
+ - **TokenHub API-key requests** under `/tokenhub/v1/...`
119
+ These are used for external model access, OpenAI-compatible chat calls, and direct gateway integrations.
120
+
121
+ Canonical first-run flow:
122
+
123
+ ```bash
124
+ atel key create --name my-agent-key
125
+ atel key use
126
+ atel hub balance
127
+ atel hub models --search gpt
128
+ atel hub chat openai/gpt-4o-mini "Hello"
129
+ atel swap usdc 0.01 --chain bsc
130
+ atel swap token 100 --chain bsc
131
+ atel transfer did:atel:ed25519:TARGET_DID 250 --memo "settlement"
132
+ ```
133
+
134
+ Important terminology:
135
+
136
+ - **TokenHub API key**: a gateway credential stored in `~/.atel/hub.json`
137
+ - **Platform DID**: the DID-backed account identity used for signed `/account/v1/...` requests
138
+ - **OpenAI-compatible gateway**: the `/tokenhub/v1/chat/completions` surface
139
+ - **`pending_verification`**: the on-chain settlement transaction was sent, but accounting is waiting for verification before balances are finalized
140
+
141
+ If a swap returns `pending_verification`, inspect `atel hub swap-history` or `atel hub ledger` before retrying the settlement path.
142
+ Use `atel hub dashboard` for a compact overview of the current TokenHub account state.
143
+
93
144
  ## Architecture
94
145
 
95
146
  ATEL is organized into protocol and runtime layers:
@@ -130,6 +181,37 @@ atel status # Check system health
130
181
  atel start [port] # Start agent endpoint
131
182
  ```
132
183
 
184
+ ### Key Management
185
+ ```bash
186
+ atel key create [--name "my-key"] # Create and save a TokenHub API key
187
+ atel key list # List TokenHub API keys
188
+ atel key revoke <id> # Revoke a TokenHub API key
189
+ atel key use # Print OpenAI-compatible env exports
190
+ ```
191
+
192
+ ### TokenHub & Account
193
+ ```bash
194
+ atel balance # Show platform account balance
195
+ atel deposit <amount> [channel] # Add funds to the platform account
196
+ atel withdraw <amount> [channel] [address] # Withdraw funds
197
+ atel transactions # List platform payment history
198
+
199
+ atel hub balance # Show USDC and ATELToken balances
200
+ atel hub dashboard # Show a compact TokenHub account summary
201
+ atel hub usage [--model <id>] [--days 7] # Show model usage history
202
+ atel hub ledger [--page 1] [--limit 20] # Show account transaction records
203
+ atel hub swap-history [--page 1] [--limit 20] # Show swap records
204
+ atel hub stats # Show public TokenHub stats
205
+ atel hub models [--search gpt] # List available models
206
+ atel hub chat <model> "<prompt>" [--stream] # Send a quick chat request
207
+
208
+ atel swap usdc <amount> [--chain bsc|base] # Swap USDC to ATELToken
209
+ atel swap token <amount> [--chain bsc|base] # Swap ATELToken to USDC
210
+ atel transfer <to_did> <amount> [--memo "settlement"] # Transfer ATELToken
211
+ ```
212
+
213
+ The grouped `atel hub key <...>` path remains available, but `atel key <...>` is the preferred entry point for new setups.
214
+
133
215
  ### Friend System
134
216
  ```bash
135
217
  # Friend Management
@@ -314,12 +396,15 @@ Friend system data is stored in `.atel/`:
314
396
 
315
397
  ## Documentation
316
398
 
317
- - [docs/START-HERE.md](docs/START-HERE.md) — One-page onboarding
318
- - [docs/QUICKSTART-5MIN.md](docs/QUICKSTART-5MIN.md) — 5-minute quickstart
319
- - [docs/API.md](docs/API.md) — Detailed API guide
320
- - [docs/AUDIT_SERVICE.md](docs/AUDIT_SERVICE.md) — Audit system guide
321
- - [docs/builtin-executor-guide.md](docs/builtin-executor-guide.md) — Built-in executor guide
322
- - [docs/protocol-specification.md](docs/protocol-specification.md) — Protocol specification
399
+ - [START-HERE.md](https://github.com/LawrenceLiang-BTC/atel-sdk/blob/main/docs/START-HERE.md) — One-page onboarding
400
+ - [QUICKSTART-5MIN.md](https://github.com/LawrenceLiang-BTC/atel-sdk/blob/main/docs/QUICKSTART-5MIN.md) — 5-minute quickstart
401
+ - [quick-start.md](https://github.com/LawrenceLiang-BTC/atel-sdk/blob/main/docs/quick-start.md) — TokenHub quick-start flow
402
+ - [API.md](https://github.com/LawrenceLiang-BTC/atel-sdk/blob/main/docs/API.md) — Detailed API guide
403
+ - [AUDIT_SERVICE.md](https://github.com/LawrenceLiang-BTC/atel-sdk/blob/main/docs/AUDIT_SERVICE.md) — Audit system guide
404
+ - [builtin-executor-guide.md](https://github.com/LawrenceLiang-BTC/atel-sdk/blob/main/docs/builtin-executor-guide.md) — Built-in executor guide
405
+ - [protocol-specification.md](https://github.com/LawrenceLiang-BTC/atel-sdk/blob/main/docs/protocol-specification.md) — Protocol specification
406
+ - [skill/SKILL.md](https://github.com/LawrenceLiang-BTC/atel-sdk/blob/main/skill/SKILL.md) — OpenClaw runtime and setup conventions
407
+ - [CHANGELOG.md](https://github.com/LawrenceLiang-BTC/atel-sdk/blob/main/CHANGELOG.md) — Release notes
323
408
 
324
409
  ## Environment Variables
325
410
 
@@ -334,10 +419,10 @@ Friend system data is stored in `.atel/`:
334
419
  ## Current Status
335
420
 
336
421
  - [x] **Phase 0 MVP complete** — 13 modules implemented, core trust workflow end-to-end
337
- - [x] **241 tests in suite** — Unit/integration coverage across modules
422
+ - [x] **Release verification** — `npm run build` and `npm test` are part of the publish flow
338
423
  - [x] **P2P friend system** — Relationship-based access control with temporary sessions
339
424
  - [x] **Audit system** — CoT reasoning verification with local LLM
340
- - [x] **Production deployment** — Platform + SDK deployed and tested
425
+ - [x] **Production deployment** — Platform + SDK deployed and smoke-tested
341
426
 
342
427
  ## Roadmap
343
428
 
package/bin/atel.mjs CHANGED
@@ -53,7 +53,7 @@
53
53
 
54
54
  import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync, copyFileSync } from 'node:fs';
55
55
  import { fileURLToPath } from 'node:url';
56
- import { cmdHub } from './hub-helpers.mjs';
56
+ import { cmdHub, cmdSwap, cmdTransfer } from './hub-helpers.mjs';
57
57
  import { resolve, join, dirname } from 'node:path';
58
58
  import crypto from 'node:crypto';
59
59
  import {
@@ -74,8 +74,8 @@ import { parseAttachmentFlags, processAttachments } from './atel-attachment-help
74
74
 
75
75
  const ATEL_DIR = resolve(process.env.ATEL_DIR || '.atel');
76
76
  const IDENTITY_FILE = resolve(ATEL_DIR, 'identity.json');
77
- const REGISTRY_URL = process.env.ATEL_REGISTRY || 'https://api.atelai.org';
78
- const ATEL_PLATFORM = process.env.ATEL_PLATFORM || 'https://api.atelai.org';
77
+ const ATEL_PLATFORM = process.env.ATEL_PLATFORM || process.env.ATEL_API || process.env.ATEL_REGISTRY || 'https://api.atelai.org';
78
+ const REGISTRY_URL = process.env.ATEL_REGISTRY || ATEL_PLATFORM || 'https://api.atelai.org';
79
79
  const ATEL_RELAY = process.env.ATEL_RELAY || 'https://api.atelai.org';
80
80
  const ATEL_NOTIFY_GATEWAY = process.env.ATEL_NOTIFY_GATEWAY || process.env.OPENCLAW_GATEWAY_URL || '';
81
81
  const ATEL_NOTIFY_TARGET = process.env.ATEL_NOTIFY_TARGET || '';
@@ -158,7 +158,7 @@ function shouldAllowRepoAccess(context = {}) {
158
158
  const resultSummary = String(context?.resultSummary || '').toLowerCase();
159
159
  const previousApprovedOutputs = String(context?.previousApprovedOutputs || '').toLowerCase();
160
160
  const combined = `${description}\n${objective}\n${resultSummary}\n${previousApprovedOutputs}`;
161
- return /(repo|repository|codebase|仓库|代码库|项目代码|source code|read files|analyze code|修改代码|修复代码|实现功能)/i.test(combined);
161
+ return /(repo|repository|codebase|\u4ed3\u5e93|\u4ee3\u7801\u5e93|\u9879\u76ee\u4ee3\u7801|source code|read files|analyze code|\u4fee\u6539\u4ee3\u7801|\u4fee\u590d\u4ee3\u7801|\u5b9e\u73b0\u529f\u80fd)/i.test(combined);
162
162
  }
163
163
 
164
164
  function summarizeApprovedMilestones(milestones = [], beforeIndex = Number.MAX_SAFE_INTEGER) {
@@ -312,26 +312,26 @@ async function pushTradeNotification(eventType, payload, body) {
312
312
  if (enabled.length === 0) return;
313
313
 
314
314
  const templates = {
315
- 'order_created': (p) => `📥 收到新订单\n订单: ${p.orderId || body?.orderId || '?'}\n金额: $${p.priceAmount ?? '?'} USDC\n来自: ${p.requesterDid || '未知请求方'}\n请审核后决定是否接单`,
316
- 'order_accepted': (p) => `📋 订单已被接单\n订单: ${p.orderId || body?.orderId || '?'}\n执行方已开始处理,进入里程碑阶段`,
315
+ 'order_created': (p) => `📥 New order received\nOrder: ${p.orderId || body?.orderId || '?'}\nAmount: $${p.priceAmount ?? '?'} USDC\nFrom: ${p.requesterDid || 'unknown requester'}\nReview and decide whether to accept`,
316
+ 'order_accepted': (p) => `📋 Order accepted\nOrder: ${p.orderId || body?.orderId || '?'}\nThe executor has started work. The order is now in the milestone phase`,
317
317
  'milestone_submitted': (p) => {
318
- const desc = p.milestoneDescription ? `\n目标: ${p.milestoneDescription}` : '';
319
- const content = p.resultSummary ? `\n提交内容: ${String(p.resultSummary).substring(0, 200)}` : '';
320
- return `📝 里程碑 M${p.milestoneIndex ?? '?'} 已提交\n订单: ${p.orderId || body?.orderId || '?'}${desc}${content}\n等待审核`;
318
+ const desc = p.milestoneDescription ? `\nGoal: ${p.milestoneDescription}` : '';
319
+ const content = p.resultSummary ? `\nSubmission: ${String(p.resultSummary).substring(0, 200)}` : '';
320
+ return `📝 Milestone M${p.milestoneIndex ?? '?'} submitted\nOrder: ${p.orderId || body?.orderId || '?'}${desc}${content}\nAwaiting review`;
321
321
  },
322
322
  'milestone_verified': (p) => {
323
- const desc = p.milestoneDescription ? `\n目标: ${p.milestoneDescription}` : '';
324
- const content = p.resultSummary ? `\n提交内容: ${String(p.resultSummary).substring(0, 200)}` : '';
325
- const progress = p.totalMilestones ? `\n进度: ${(p.milestoneIndex ?? 0) + 1}/${p.totalMilestones}` : '';
326
- return `✅ 里程碑 M${p.milestoneIndex ?? '?'} 审核通过\n订单: ${p.orderId || body?.orderId || '?'}${desc}${content}${progress}`;
323
+ const desc = p.milestoneDescription ? `\nGoal: ${p.milestoneDescription}` : '';
324
+ const content = p.resultSummary ? `\nSubmission: ${String(p.resultSummary).substring(0, 200)}` : '';
325
+ const progress = p.totalMilestones ? `\nProgress: ${(p.milestoneIndex ?? 0) + 1}/${p.totalMilestones}` : '';
326
+ return `✅ Milestone M${p.milestoneIndex ?? '?'} approved\nOrder: ${p.orderId || body?.orderId || '?'}${desc}${content}${progress}`;
327
327
  },
328
328
  'milestone_rejected': (p) => {
329
- const desc = p.milestoneDescription ? `\n目标: ${p.milestoneDescription}` : '';
330
- return `❌ 里程碑 M${p.milestoneIndex ?? '?'} 被拒绝\n订单: ${p.orderId || body?.orderId || '?'}${desc}\n原因: ${p.rejectReason || '未说明'}`;
329
+ const desc = p.milestoneDescription ? `\nGoal: ${p.milestoneDescription}` : '';
330
+ return `❌ Milestone M${p.milestoneIndex ?? '?'} rejected\nOrder: ${p.orderId || body?.orderId || '?'}${desc}\nReason: ${p.rejectReason || 'not provided'}`;
331
331
  },
332
332
  'order_settled': (p) => {
333
- const amount = p.priceAmount ? `\n金额: $${p.priceAmount} USDC` : '';
334
- return `💰 订单已结算完成\n订单: ${p.orderId || body?.orderId || '?'}${amount}\nUSDC 已支付`;
333
+ const amount = p.priceAmount ? `\nAmount: $${p.priceAmount} USDC` : '';
334
+ return `💰 Order settled\nOrder: ${p.orderId || body?.orderId || '?'}${amount}\nUSDC has been paid`;
335
335
  },
336
336
  };
337
337
  const tmpl = templates[eventType];
@@ -342,10 +342,14 @@ async function pushTradeNotification(eventType, payload, body) {
342
342
  try {
343
343
  if (target.channel === 'telegram' && target.botToken) {
344
344
  // Direct TG Bot API — no gateway needed
345
+ // Note: parse_mode is intentionally omitted. Templates render plain
346
+ // text (no HTML markup), and milestone payloads frequently contain
347
+ // raw `<` characters (e.g. "<5秒", "<30秒") that would otherwise
348
+ // trip Telegram's HTML parser and cause silent 400 drops.
345
349
  await fetch(`https://api.telegram.org/bot${target.botToken}/sendMessage`, {
346
350
  method: 'POST',
347
351
  headers: { 'Content-Type': 'application/json' },
348
- body: JSON.stringify({ chat_id: target.target, text: message, parse_mode: 'HTML' }),
352
+ body: JSON.stringify({ chat_id: target.target, text: message }),
349
353
  signal: AbortSignal.timeout(5000),
350
354
  }).catch(() => {});
351
355
  } else if (target.channel === 'gateway') {
@@ -382,12 +386,12 @@ async function pushP2PNotification(eventType, payload = {}) {
382
386
  if (enabled.length === 0) return;
383
387
 
384
388
  const templates = {
385
- 'p2p_task_sent': (p) => `📤 P2P任务已发送\n任务: ${p.taskId || '?'}\n目标: ${p.peerDid || '?'}`,
386
- 'p2p_task_received': (p) => `📩 收到新的P2P任务\n任务: ${p.taskId || '?'}\n来自: ${p.peerDid || '?'}`,
387
- 'p2p_task_started': (p) => `▶️ P2P任务开始处理\n任务: ${p.taskId || '?'}\n来自: ${p.peerDid || '?'}`,
388
- 'p2p_result_submitted': (p) => `📨 P2P结果已发回对方\n任务: ${p.taskId || '?'}\n目标: ${p.peerDid || '?'}`,
389
- 'p2p_result_received': (p) => `✅ P2P任务已完成\n任务: ${p.taskId || '?'}\n结果: ${String(p.result || '').slice(0, 80) || '已返回'}`,
390
- 'p2p_task_failed': (p) => `❌ P2P任务失败\n任务: ${p.taskId || '?'}\n原因: ${p.reason || '未知错误'}`,
389
+ 'p2p_task_sent': (p) => `📤 P2P task sent\nTask: ${p.taskId || '?'}\nTarget: ${p.peerDid || '?'}`,
390
+ 'p2p_task_received': (p) => `📩 New P2P task received\nTask: ${p.taskId || '?'}\nFrom: ${p.peerDid || '?'}`,
391
+ 'p2p_task_started': (p) => `▶️ P2P task started\nTask: ${p.taskId || '?'}\nFrom: ${p.peerDid || '?'}`,
392
+ 'p2p_result_submitted': (p) => `📨 P2P result sent back\nTask: ${p.taskId || '?'}\nTarget: ${p.peerDid || '?'}`,
393
+ 'p2p_result_received': (p) => `✅ P2P task completed\nTask: ${p.taskId || '?'}\nResult: ${String(p.result || '').slice(0, 80) || 'returned'}`,
394
+ 'p2p_task_failed': (p) => `❌ P2P task failed\nTask: ${p.taskId || '?'}\nReason: ${p.reason || 'unknown error'}`,
391
395
  };
392
396
  const tmpl = templates[eventType];
393
397
  if (!tmpl) return;
@@ -396,10 +400,11 @@ async function pushP2PNotification(eventType, payload = {}) {
396
400
  for (const target of enabled) {
397
401
  try {
398
402
  if (target.channel === 'telegram' && target.botToken) {
403
+ // parse_mode intentionally omitted — see pushTradeNotification for rationale
399
404
  await fetch(`https://api.telegram.org/bot${target.botToken}/sendMessage`, {
400
405
  method: 'POST',
401
406
  headers: { 'Content-Type': 'application/json' },
402
- body: JSON.stringify({ chat_id: target.target, text: message, parse_mode: 'HTML' }),
407
+ body: JSON.stringify({ chat_id: target.target, text: message }),
403
408
  signal: AbortSignal.timeout(5000),
404
409
  }).catch(() => {});
405
410
  } else if (target.channel === 'gateway') {
@@ -2950,18 +2955,18 @@ async function cmdStart(port) {
2950
2955
  function buildGatewayCallbackPrompt(eventType, promptText, callbackUrl, dedupeKey, cwd, payload = {}) {
2951
2956
  const callbackExamples = {
2952
2957
  milestone_submitted: [
2953
- '通过时执行:',
2958
+ 'Run on approval:',
2954
2959
  "python3 - <<'PY'",
2955
2960
  'import json, urllib.request',
2956
- `body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "decision": "pass", "summary": "审核通过的简短原因"}`,
2961
+ `body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "decision": "pass", "summary": "brief reason for approval"}`,
2957
2962
  `req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
2958
2963
  'urllib.request.urlopen(req, timeout=10).read()',
2959
2964
  'PY',
2960
2965
  '',
2961
- '拒绝时执行:',
2966
+ 'Run on rejection:',
2962
2967
  "python3 - <<'PY'",
2963
2968
  'import json, urllib.request',
2964
- `body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "decision": "reject", "reason": "具体拒绝原因", "summary": "简短审核结论"}`,
2969
+ `body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "decision": "reject", "reason": "specific rejection reason", "summary": "brief review conclusion"}`,
2965
2970
  `req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
2966
2971
  'urllib.request.urlopen(req, timeout=10).read()',
2967
2972
  'PY',
@@ -2969,7 +2974,7 @@ async function cmdStart(port) {
2969
2974
  default: [
2970
2975
  "python3 - <<'PY'",
2971
2976
  'import json, urllib.request',
2972
- `result = """在这里写最终交付内容"""`,
2977
+ `result = """Write the final deliverable here"""`,
2973
2978
  `body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "result": result, "summary": result[:120]}`,
2974
2979
  `req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
2975
2980
  'urllib.request.urlopen(req, timeout=10).read()',
@@ -2980,7 +2985,7 @@ async function cmdStart(port) {
2980
2985
  const callbackFailed = [
2981
2986
  "python3 - <<'PY'",
2982
2987
  'import json, urllib.request',
2983
- `body = {"dedupeKey": "${dedupeKey}", "status": "failed", "eventType": "${eventType}", "error": "在这里写失败原因"}`,
2988
+ `body = {"dedupeKey": "${dedupeKey}", "status": "failed", "eventType": "${eventType}", "error": "Write the failure reason here"}`,
2984
2989
  `req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
2985
2990
  'urllib.request.urlopen(req, timeout=10).read()',
2986
2991
  'PY',
@@ -2990,27 +2995,27 @@ async function cmdStart(port) {
2990
2995
  const contextFile = join(cwd, 'ORDER_CONTEXT.md');
2991
2996
  const allowRepoAccess = shouldAllowRepoAccess(payload);
2992
2997
  const fileAccessRule = allowRepoAccess
2993
- ? `3. 仅允许使用目录 ${cwd} 下与当前订单直接相关的内容;如果订单明确要求读仓库,也只能读取该订单工作区中明确提供的路径。`
2994
- : `3. 本单禁止读取任何本地文件、共享草稿、仓库或其他项目。不要使用文件搜索、目录浏览、读文件等方式扩展上下文;只允许依据本条消息中的订单描述、里程碑目标、提交内容来工作。`;
2998
+ ? `3. Only use content directly related to the current order under ${cwd}. If the order explicitly requires repository analysis, only read the paths explicitly provided inside this order workspace.`
2999
+ : `3. This order forbids reading any local files, shared drafts, repositories, or other projects. Do not expand context through file search, directory browsing, or file reads. Work only from the order description, milestone goal, and submission content in this message.`;
2995
3000
  const contextRule = allowRepoAccess
2996
- ? `4. 优先读取 ${contextFile},严格以其中的订单描述、里程碑目标、提交内容为准。`
2997
- : `4. 不要扫描本机其他目录,不要读取 /root/atel-workspace 下的共享文件,不要根据历史项目或 stray files 推断任务。`;
3001
+ ? `4. Read ${contextFile} first and treat its order description, milestone goal, and submission content as the authoritative source.`
3002
+ : `4. Do not scan other directories on the machine, do not read shared files under /root/atel-workspace, and do not infer the task from historical projects or stray files.`;
2998
3003
  const repoRule = allowRepoAccess
2999
- ? `5. 只有当订单明确要求分析仓库/代码时,才允许读取订单工作区里显式提供的代码路径;禁止顺带读取其他目录。`
3000
- : `5. 本单不是 repo/code 任务。禁止把任务扩展成代码分析、工程改造或共享草稿筛选。`;
3004
+ ? `5. Only when the order explicitly requires repository or code analysis may you read code paths explicitly provided in the order workspace. Do not read other directories opportunistically.`
3005
+ : `5. This order is not a repo/code task. Do not expand it into code analysis, engineering changes, or shared-draft review.`;
3001
3006
  if (eventType === 'p2p_task') {
3002
3007
  return `${promptText}
3003
3008
 
3004
- 重要要求:
3005
- 1. 这是一个 P2P 任务。不要调用 atel result;本地 SDK 会在你回调后自动提交结果。
3006
- 2. 你的任务是认真完成 AI 工作,并把最终结论通过回调发回本地 SDK
3009
+ Important requirements:
3010
+ 1. This is a P2P task. Do not call atel result; the local SDK will submit the result automatically after your callback.
3011
+ 2. Your job is to complete the AI work carefully and send the final conclusion back to the local SDK through the callback.
3007
3012
  ${fileAccessRule}
3008
3013
  ${contextRule}
3009
- 5. 完成后,必须立刻执行下面这个成功回调命令模板,并把其中内容替换成你的真实结果:
3014
+ 5. After finishing, you must immediately run the success callback command template below and replace its content with your real result:
3010
3015
 
3011
3016
  ${callbackDone}
3012
3017
 
3013
- 6. 如果重试后仍然失败,也必须执行下面这个失败回调命令:
3018
+ 6. If it still fails after retries, you must also run the failure callback command below:
3014
3019
 
3015
3020
  ${callbackFailed}
3016
3021
  `;
@@ -3018,17 +3023,17 @@ ${callbackFailed}
3018
3023
 
3019
3024
  return `${promptText}
3020
3025
 
3021
- 重要要求:
3022
- 1. 不要执行 atel milestone-submit / milestone-verify / milestone-feedback 命令;这些命令会由本地 SDK 在你回调后代为执行。
3023
- 2. 你的任务是认真完成 AI 工作,并把最终结论通过回调发回本地 SDK
3026
+ Important requirements:
3027
+ 1. Do not run atel milestone-submit / milestone-verify / milestone-feedback. The local SDK will execute those commands on your behalf after the callback.
3028
+ 2. Your job is to complete the AI work carefully and send the final conclusion back to the local SDK through the callback.
3024
3029
  ${fileAccessRule}
3025
3030
  ${contextRule}
3026
3031
  ${repoRule}
3027
- 6. 完成后,必须立刻执行下面这个成功回调命令模板,并把其中内容替换成你的真实结果:
3032
+ 6. After finishing, you must immediately run the success callback command template below and replace its content with your real result:
3028
3033
 
3029
3034
  ${callbackDone}
3030
3035
 
3031
- 7. 如果重试后仍然失败,也必须执行下面这个失败回调命令:
3036
+ 7. If it still fails after retries, you must also run the failure callback command below:
3032
3037
 
3033
3038
  ${callbackFailed}
3034
3039
  `;
@@ -3038,26 +3043,26 @@ ${callbackFailed}
3038
3043
  if (eventType === 'milestone_submitted') {
3039
3044
  return `${promptText}
3040
3045
 
3041
- 重要:你不是在和用户聊天。
3042
- 不要输出 markdown、代码块、解释、分析过程。
3043
- 你必须只输出一行 JSON
3046
+ Important: you are not chatting with the user.
3047
+ Do not output markdown, code blocks, explanations, or your reasoning.
3048
+ You must output exactly one line of JSON.
3044
3049
 
3045
- 通过时:
3046
- {"decision":"pass","summary":"简短通过原因"}
3050
+ For approval:
3051
+ {"decision":"pass","summary":"brief approval reason"}
3047
3052
 
3048
- 拒绝时:
3049
- {"decision":"reject","reason":"具体拒绝原因","summary":"简短审核结论"}`;
3053
+ For rejection:
3054
+ {"decision":"reject","reason":"specific rejection reason","summary":"brief review conclusion"}`;
3050
3055
  }
3051
3056
 
3052
3057
  if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
3053
3058
  return `${promptText}
3054
3059
 
3055
- 重要:你不是在和用户聊天。
3056
- 不要输出 markdown、标题、项目符号、解释、分析过程。
3057
- 你必须只输出一行 JSON
3060
+ Important: you are not chatting with the user.
3061
+ Do not output markdown, headings, bullet points, explanations, or your reasoning.
3062
+ You must output exactly one line of JSON.
3058
3063
 
3059
- 格式:
3060
- {"result":"当前里程碑的真实交付内容"}`;
3064
+ Format:
3065
+ {"result":"the actual deliverable for the current milestone"}`;
3061
3066
  }
3062
3067
 
3063
3068
  return promptText;
@@ -3168,10 +3173,10 @@ ${callbackFailed}
3168
3173
  return buildAgentCallbackAction(eventType, payload, parsed);
3169
3174
  } catch {
3170
3175
  const lowered = cleaned.toLowerCase();
3171
- if (lowered.startsWith('pass') || cleaned.includes('通过')) {
3176
+ if (lowered.startsWith('pass') || cleaned.includes('\u901a\u8fc7')) {
3172
3177
  return buildAgentCallbackAction(eventType, payload, { decision: 'pass', summary: cleaned });
3173
3178
  }
3174
- if (lowered.startsWith('reject') || cleaned.includes('拒绝')) {
3179
+ if (lowered.startsWith('reject') || cleaned.includes('\u62d2\u7edd')) {
3175
3180
  return buildAgentCallbackAction(eventType, payload, { decision: 'reject', reason: cleaned, summary: cleaned });
3176
3181
  }
3177
3182
  return { ok: false, error: 'invalid_local_review_stdout' };
@@ -3375,8 +3380,8 @@ ${callbackFailed}
3375
3380
  previousApprovedOutputs,
3376
3381
  };
3377
3382
  const promptText = currentIndex === 0
3378
- ? `你是ATEL接单方Agent。双方已确认方案,开始执行。\n订单原始要求:${orderDescription || '未提供'}\n当前里程碑 M0:${currentMilestone.title || ''}\n请只围绕这个订单要求完成当前里程碑,并通过回调返回最终交付内容。`
3379
- : `你是ATEL接单方Agent。M${currentIndex - 1} 已通过审核。\n订单原始要求:${orderDescription || '未提供'}\n下一个里程碑 M${currentIndex}:${currentMilestone.title || ''}\n前面已通过的阶段结果如下:\n${previousApprovedOutputs || ''}\n\n请严格基于这些已通过结果推进当前里程碑,不要自行假设缺失材料,也不要读取本地共享文件来补上下文。完成后通过回调返回最终交付内容。`;
3383
+ ? `You are the ATEL executor agent. The plan has been confirmed and execution begins.\nOriginal order requirements: ${orderDescription || 'not provided'}\nCurrent milestone M0: ${currentMilestone.title || ''}\nComplete only this milestone for the current order and return the final deliverable via the callback.`
3384
+ : `You are the ATEL executor agent. M${currentIndex - 1} has been approved.\nOriginal order requirements: ${orderDescription || 'not provided'}\nNext milestone M${currentIndex}: ${currentMilestone.title || ''}\nPreviously approved outputs:\n${previousApprovedOutputs || 'none'}\n\nAdvance the current milestone strictly based on these approved results. Do not invent missing materials or read local shared files to fill missing context. After completion, return the final deliverable via the callback.`;
3380
3385
  const recoveryKey = buildMilestoneHookRecoveryKey(eventType, payload);
3381
3386
  log({ event: 'trade_reconcile_executor', orderId, currentMilestone: currentIndex, recoveryKey });
3382
3387
  const queued = queueAgentHook(eventType, recoveryKey, promptText, workspace.dir, payload, { recoveryKey });
@@ -3409,7 +3414,7 @@ ${callbackFailed}
3409
3414
  orderDescription,
3410
3415
  previousApprovedOutputs,
3411
3416
  };
3412
- const promptText = `你是ATEL发单方Agent,需要审核执行方提交的工作。\n订单原始要求:${orderDescription || '未提供'}\n里程碑目标:${submittedMilestone.title || ''}\n前面已通过的阶段结果如下:\n${previousApprovedOutputs || ''}\n提交内容:${submittedMilestone.resultSummary || ''}\n请只按该订单要求和前序已通过结果审慎决定通过还是拒绝,并通过回调返回 decision=pass decision=reject。`;
3417
+ const promptText = `You are the ATEL requester agent. You need to review the work submitted by the executor.\nOriginal order requirements: ${orderDescription || 'not provided'}\nMilestone goal: ${submittedMilestone.title || ''}\nPreviously approved outputs:\n${previousApprovedOutputs || 'none'}\nSubmission: ${submittedMilestone.resultSummary || ''}\nDecide carefully whether to pass or reject based only on the order requirements and the previously approved outputs, then return decision=pass or decision=reject via the callback.`;
3413
3418
  const recoveryKey = buildMilestoneHookRecoveryKey('milestone_submitted', payload);
3414
3419
  const queued = queueAgentHook('milestone_submitted', recoveryKey, promptText, workspace.dir, payload, { recoveryKey });
3415
3420
  if (queued) log({ event: 'trade_reconcile_requester', orderId, milestoneIndex: submittedMilestone.index, recoveryKey });
@@ -3615,7 +3620,7 @@ ${callbackFailed}
3615
3620
  return;
3616
3621
  }
3617
3622
  // Add working directory context so agent runs atel commands in the right place
3618
- const cwdNote = `\n\n重要:OpenClaw 的分析工作目录是 ${hookCwd}。所有 atel 命令必须在目录 ${atelCwd} 下执行(cd ${atelCwd} && atel ...)。`;
3623
+ const cwdNote = `\n\nImportant: OpenClaw's analysis working directory is ${hookCwd}. All atel commands must be run in ${atelCwd} (cd ${atelCwd} && atel ...).`;
3619
3624
  const enrichedPrompt = sanitizeAgentPrompt(prompt + cwdNote, { eventType: event, dedupeKey });
3620
3625
  if (!enrichedPrompt) {
3621
3626
  res.json({ status: 'received', eventId, eventType: event, skipped: true });
@@ -4542,7 +4547,7 @@ ${callbackFailed}
4542
4547
  fetch(EXECUTOR_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taskId, from: message.from, action, payload, encrypted: !!session?.encrypted, toolProxy: `http://127.0.0.1:${toolProxyPort}` }) }).catch(e => log({ event: 'executor_forward_failed', taskId, error: e.message }));
4543
4548
  return { status: 'accepted', taskId, message: 'Task accepted. Result will be pushed when ready.' };
4544
4549
  } else {
4545
- const p2pPrompt = `你是 ATEL 接单方 Agent,收到一个 P2P 任务。\n任务类型:${action}\n任务内容:${JSON.stringify(payload?.payload || payload || {}, null, 2)}\n请认真完成任务,并通过回调返回最终结果。`;
4550
+ const p2pPrompt = `You are the ATEL executor agent and received a P2P task.\nTask type: ${action}\nTask payload: ${JSON.stringify(payload?.payload || payload || {}, null, 2)}\nComplete the task carefully and return the final result via the callback.`;
4546
4551
  appendP2PTaskStatus({ taskId, role: 'executor', peerDid: message.from, status: 'task_started' });
4547
4552
  pushP2PNotification('p2p_task_received', { taskId, peerDid: message.from }).catch(() => {});
4548
4553
  pushP2PNotification('p2p_task_started', { taskId, peerDid: message.from }).catch(() => {});
@@ -4934,6 +4939,17 @@ async function cmdRegister(name, capabilities, endpointUrl) {
4934
4939
  if (preferredChain) registerPayload.metadata = { preferredChain };
4935
4940
 
4936
4941
  const entry = await client.register(registerPayload, id);
4942
+ let actualWallets = wallets || null;
4943
+ for (let i = 0; i < 8; i++) {
4944
+ try {
4945
+ const resp = await fetch(`${REGISTRY_URL}/registry/v1/agent/${encodeURIComponent(id.did)}`, { signal: AbortSignal.timeout(5000) });
4946
+ if (resp.ok) {
4947
+ const fresh = await resp.json();
4948
+ if (fresh?.wallets) { actualWallets = fresh.wallets; break; }
4949
+ }
4950
+ } catch {}
4951
+ await new Promise(r => setTimeout(r, 750));
4952
+ }
4937
4953
  if (!wallets || !preferredChain) {
4938
4954
  console.error('[register] INFO: registered without published chain readiness. Yellow page registration still works. Free tasks can run normally. Paid orders require an anchoring key at execution time; re-register or restart later if you want wallets/preferredChain shown in the registry.');
4939
4955
  }
@@ -4953,7 +4969,7 @@ async function cmdRegister(name, capabilities, endpointUrl) {
4953
4969
  capabilities: capDisplay,
4954
4970
  endpoint: ep,
4955
4971
  discoverable,
4956
- wallets: wallets || null,
4972
+ wallets: actualWallets || null,
4957
4973
  preferredChain: preferredChain || null,
4958
4974
  registry: REGISTRY_URL,
4959
4975
  }, null, 2));
@@ -5885,6 +5901,7 @@ async function cmdOrder(executorDid, capType, price) {
5885
5901
  try { executorDid = resolveDID(executorDid); } catch (e) { console.error(e.message); process.exit(1); }
5886
5902
  }
5887
5903
  const description = rawArgs.find((a, i) => rawArgs[i-1] === '--desc') || '';
5904
+ const chainArg = rawArgs.find((a, i) => rawArgs[i-1] === '--chain') || '';
5888
5905
  const id = requireIdentity();
5889
5906
 
5890
5907
  try {
@@ -5944,7 +5961,7 @@ async function cmdOrder(executorDid, capType, price) {
5944
5961
  };
5945
5962
 
5946
5963
  // Send to Platform
5947
- const data = await signedFetch('POST', '/trade/v1/order', {
5964
+ const orderBody = {
5948
5965
  executorDid,
5949
5966
  capabilityType: capType,
5950
5967
  priceAmount: parseFloat(price),
@@ -5955,7 +5972,9 @@ async function cmdOrder(executorDid, capType, price) {
5955
5972
  taskRequest: taskRequest,
5956
5973
  taskSignature: taskSignature,
5957
5974
  intent: intentData, // AVIP intent
5958
- });
5975
+ };
5976
+ if (chainArg) { orderBody.chain = chainArg; }
5977
+ const data = await signedFetch('POST', '/trade/v1/order', orderBody);
5959
5978
 
5960
5979
  // For paid orders: show escrow info (chain escrow creation handled by Platform backend)
5961
5980
  if (data.orderId && parseFloat(price) > 0 && data.escrow?.escrowContract) {
@@ -6582,7 +6601,7 @@ async function cmdMilestoneFeedback(orderId) {
6582
6601
 
6583
6602
  if (!approve && !feedback) {
6584
6603
  console.error('Usage: atel milestone-feedback <orderId> --approve');
6585
- console.error(' atel milestone-feedback <orderId> --feedback "修改意见"');
6604
+ console.error(' atel milestone-feedback <orderId> --feedback "feedback text"');
6586
6605
  process.exit(1);
6587
6606
  }
6588
6607
 
@@ -6593,7 +6612,7 @@ async function cmdMilestoneFeedback(orderId) {
6593
6612
 
6594
6613
  async function cmdMilestoneSubmit(orderId, indexStr) {
6595
6614
  if (!orderId || indexStr === undefined) {
6596
- console.error('Usage: atel milestone-submit <orderId> <index> --result "结果描述" [--file <path>]');
6615
+ console.error('Usage: atel milestone-submit <orderId> <index> --result "result summary" [--file <path>]');
6597
6616
  console.error(' atel milestone-submit <orderId> <index> --result ./file.pdf [--hash 0x...] [--file <path>]');
6598
6617
  process.exit(1);
6599
6618
  }
@@ -6665,7 +6684,7 @@ async function cmdMilestoneSubmit(orderId, indexStr) {
6665
6684
  async function cmdMilestoneVerify(orderId, indexStr) {
6666
6685
  if (!orderId || indexStr === undefined) {
6667
6686
  console.error('Usage: atel milestone-verify <orderId> <index> --pass');
6668
- console.error(' atel milestone-verify <orderId> <index> --reject "原因"');
6687
+ console.error(' atel milestone-verify <orderId> <index> --reject "reason"');
6669
6688
  process.exit(1);
6670
6689
  }
6671
6690
  const pass = rawArgs.includes('--pass');
@@ -8298,6 +8317,10 @@ const commands = {
8298
8317
  auth: () => cmdAuth(args[0]),
8299
8318
  // Send (Rich Media P2P Message)
8300
8319
  hub: () => cmdHub(args[0], args.slice(1), rawArgs),
8320
+ key: () => cmdHub('key', args, rawArgs),
8321
+ // Platform account operations
8322
+ swap: () => cmdSwap(args[0], args[1], rawArgs),
8323
+ transfer: () => cmdTransfer(args[0], args[1], rawArgs),
8301
8324
  send: () => {
8302
8325
  if (rawArgs.includes('--help') || rawArgs.includes('-h') || args.length === 0) {
8303
8326
  showSendHelp();
@@ -8419,7 +8442,7 @@ const commands = {
8419
8442
  const resp = await fetch(`https://api.telegram.org/bot${target.botToken}/sendMessage`, {
8420
8443
  method: 'POST',
8421
8444
  headers: { 'Content-Type': 'application/json' },
8422
- body: JSON.stringify({ chat_id: target.target, text: '🔔 ATEL 通知测试\n如果你看到这条消息,说明通知已正确配置!' }),
8445
+ body: JSON.stringify({ chat_id: target.target, text: '🔔 ATEL notification test\nIf you can see this message, notifications are configured correctly!' }),
8423
8446
  signal: AbortSignal.timeout(10000),
8424
8447
  });
8425
8448
  const data = await resp.json();
@@ -8504,13 +8527,24 @@ Account Commands:
8504
8527
  transactions List payment history
8505
8528
 
8506
8529
  Hub Commands:
8507
- hub balance Show ATELToken balance
8508
- hub usage [--model <id>] [--days 7] Usage history
8509
- hub topup Top-up instructions
8510
- hub swap <usdc> [--chain bsc|base] Swap USDC → ATELToken
8511
- hub models [--search <kw>] List available models
8512
- hub chat <model> "<prompt>" [--stream] Quick chat
8513
- hub key <create|list|revoke|use> Manage TokenHub API keys
8530
+ swap usdc <amount> [--chain bsc|base] Swap USDC → ATELToken
8531
+ swap token <amount> [--chain bsc|base] Swap ATELToken → USDC
8532
+ transfer <to_did> <amount> [--memo] Transfer ATELToken to another DID
8533
+
8534
+ key create [--name <name>] Create and save a TokenHub API key
8535
+ key list List TokenHub API keys
8536
+ key revoke <id> Revoke a TokenHub API key
8537
+ key use Print OpenAI-compatible env exports
8538
+
8539
+ hub balance Show USDC and ATELToken balances
8540
+ hub dashboard Show a compact TokenHub account summary
8541
+ hub usage [--model <id>] [--days 7] Show model usage history
8542
+ hub ledger [--page N] [--limit N] Show account transaction records
8543
+ hub swap-history [--page N] [--limit N] Show swap records
8544
+ hub stats Show public TokenHub stats
8545
+ hub models [--search <kw>] List available models
8546
+ hub chat <model> "<prompt>" [--stream] Send a quick chat request
8547
+ hub key <create|list|revoke|use> Manage TokenHub API keys
8514
8548
 
8515
8549
  Trade Commands:
8516
8550
  trade-task <cap> <desc> [--budget N] One-shot: search → order → wait → confirm (requester)