@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 +13 -0
- package/README.md +93 -8
- package/bin/atel.mjs +114 -80
- package/bin/hub-helpers.mjs +490 -84
- package/package.json +8 -5
- package/skill/SKILL.md +53 -197
- package/skill/atel-agent/SKILL.md +46 -172
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
|
-
- [
|
|
318
|
-
- [
|
|
319
|
-
- [
|
|
320
|
-
- [
|
|
321
|
-
- [
|
|
322
|
-
- [
|
|
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] **
|
|
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
|
|
78
|
-
const
|
|
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
|
|
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) => `📥
|
|
316
|
-
'order_accepted': (p) => `📋
|
|
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 ? `\
|
|
319
|
-
const content = p.resultSummary ? `\
|
|
320
|
-
return `📝
|
|
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 ? `\
|
|
324
|
-
const content = p.resultSummary ? `\
|
|
325
|
-
const progress = p.totalMilestones ? `\
|
|
326
|
-
return `✅
|
|
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 ? `\
|
|
330
|
-
return `❌
|
|
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 ? `\
|
|
334
|
-
return `💰
|
|
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
|
|
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
|
|
386
|
-
'p2p_task_received': (p) => `📩
|
|
387
|
-
'p2p_task_started': (p) => `▶️ P2P
|
|
388
|
-
'p2p_result_submitted': (p) => `📨 P2P
|
|
389
|
-
'p2p_result_received': (p) => `✅ P2P
|
|
390
|
-
'p2p_task_failed': (p) => `❌ P2P
|
|
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
|
|
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": "
|
|
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.
|
|
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.
|
|
2997
|
-
: `4.
|
|
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.
|
|
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.
|
|
3006
|
-
2.
|
|
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.
|
|
3023
|
-
2.
|
|
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
|
-
|
|
3043
|
-
|
|
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":"
|
|
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
|
-
|
|
3057
|
-
|
|
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
|
-
?
|
|
3379
|
-
:
|
|
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 =
|
|
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\
|
|
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 =
|
|
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:
|
|
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
|
|
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 "
|
|
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
|
|
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
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
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)
|