@lawrenceliang-btc/atel-sdk 1.1.28 → 1.1.29
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 +31 -0
- package/bin/atel.mjs +102 -76
- package/bin/hub-helpers.mjs +490 -84
- package/package.json +7 -4
- 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
|
@@ -90,6 +90,37 @@ For custom runtimes, point `ATEL_EXECUTOR_URL` at your own service.
|
|
|
90
90
|
|
|
91
91
|
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
92
|
|
|
93
|
+
|
|
94
|
+
## TokenHub and AI Gateway
|
|
95
|
+
|
|
96
|
+
ATEL currently exposes two related but distinct integration surfaces:
|
|
97
|
+
|
|
98
|
+
- **DID-signed platform requests** under `/account/v1/...`
|
|
99
|
+
These are used by the CLI for account actions such as balance lookup, swaps, transfers, and account history.
|
|
100
|
+
- **TokenHub API-key requests** under `/tokenhub/v1/...`
|
|
101
|
+
These are used for external model access, OpenAI-compatible chat calls, and direct gateway integrations.
|
|
102
|
+
|
|
103
|
+
Canonical first-run flow:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
atel key create --name my-agent-key
|
|
107
|
+
atel hub balance
|
|
108
|
+
atel hub models --search gpt
|
|
109
|
+
atel hub chat openai/gpt-4o-mini "Hello"
|
|
110
|
+
atel swap usdc 0.01 --chain bsc
|
|
111
|
+
atel swap token 100 --chain bsc
|
|
112
|
+
atel transfer did:atel:ed25519:TARGET_DID 250 --memo "settlement"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Important terminology:
|
|
116
|
+
|
|
117
|
+
- **TokenHub API key**: a gateway credential stored in `~/.atel/hub.json`
|
|
118
|
+
- **Platform DID**: the DID-backed account identity used for signed `/account/v1/...` requests
|
|
119
|
+
- **OpenAI-compatible gateway**: the `/tokenhub/v1/chat/completions` surface
|
|
120
|
+
- **`pending_verification`**: the on-chain settlement transaction was sent, but accounting is waiting for verification before balances are finalized
|
|
121
|
+
|
|
122
|
+
Use `atel hub swap-history` and `atel hub ledger` to inspect post-settlement account state.
|
|
123
|
+
|
|
93
124
|
## Architecture
|
|
94
125
|
|
|
95
126
|
ATEL is organized into protocol and runtime layers:
|
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];
|
|
@@ -382,12 +382,12 @@ async function pushP2PNotification(eventType, payload = {}) {
|
|
|
382
382
|
if (enabled.length === 0) return;
|
|
383
383
|
|
|
384
384
|
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
|
|
385
|
+
'p2p_task_sent': (p) => `📤 P2P task sent\nTask: ${p.taskId || '?'}\nTarget: ${p.peerDid || '?'}`,
|
|
386
|
+
'p2p_task_received': (p) => `📩 New P2P task received\nTask: ${p.taskId || '?'}\nFrom: ${p.peerDid || '?'}`,
|
|
387
|
+
'p2p_task_started': (p) => `▶️ P2P task started\nTask: ${p.taskId || '?'}\nFrom: ${p.peerDid || '?'}`,
|
|
388
|
+
'p2p_result_submitted': (p) => `📨 P2P result sent back\nTask: ${p.taskId || '?'}\nTarget: ${p.peerDid || '?'}`,
|
|
389
|
+
'p2p_result_received': (p) => `✅ P2P task completed\nTask: ${p.taskId || '?'}\nResult: ${String(p.result || '').slice(0, 80) || 'returned'}`,
|
|
390
|
+
'p2p_task_failed': (p) => `❌ P2P task failed\nTask: ${p.taskId || '?'}\nReason: ${p.reason || 'unknown error'}`,
|
|
391
391
|
};
|
|
392
392
|
const tmpl = templates[eventType];
|
|
393
393
|
if (!tmpl) return;
|
|
@@ -2950,18 +2950,18 @@ async function cmdStart(port) {
|
|
|
2950
2950
|
function buildGatewayCallbackPrompt(eventType, promptText, callbackUrl, dedupeKey, cwd, payload = {}) {
|
|
2951
2951
|
const callbackExamples = {
|
|
2952
2952
|
milestone_submitted: [
|
|
2953
|
-
'
|
|
2953
|
+
'Run on approval:',
|
|
2954
2954
|
"python3 - <<'PY'",
|
|
2955
2955
|
'import json, urllib.request',
|
|
2956
|
-
`body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "decision": "pass", "summary": "
|
|
2956
|
+
`body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "decision": "pass", "summary": "brief reason for approval"}`,
|
|
2957
2957
|
`req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
|
|
2958
2958
|
'urllib.request.urlopen(req, timeout=10).read()',
|
|
2959
2959
|
'PY',
|
|
2960
2960
|
'',
|
|
2961
|
-
'
|
|
2961
|
+
'Run on rejection:',
|
|
2962
2962
|
"python3 - <<'PY'",
|
|
2963
2963
|
'import json, urllib.request',
|
|
2964
|
-
`body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "decision": "reject", "reason": "
|
|
2964
|
+
`body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "decision": "reject", "reason": "specific rejection reason", "summary": "brief review conclusion"}`,
|
|
2965
2965
|
`req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
|
|
2966
2966
|
'urllib.request.urlopen(req, timeout=10).read()',
|
|
2967
2967
|
'PY',
|
|
@@ -2969,7 +2969,7 @@ async function cmdStart(port) {
|
|
|
2969
2969
|
default: [
|
|
2970
2970
|
"python3 - <<'PY'",
|
|
2971
2971
|
'import json, urllib.request',
|
|
2972
|
-
`result = """
|
|
2972
|
+
`result = """Write the final deliverable here"""`,
|
|
2973
2973
|
`body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "result": result, "summary": result[:120]}`,
|
|
2974
2974
|
`req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
|
|
2975
2975
|
'urllib.request.urlopen(req, timeout=10).read()',
|
|
@@ -2980,7 +2980,7 @@ async function cmdStart(port) {
|
|
|
2980
2980
|
const callbackFailed = [
|
|
2981
2981
|
"python3 - <<'PY'",
|
|
2982
2982
|
'import json, urllib.request',
|
|
2983
|
-
`body = {"dedupeKey": "${dedupeKey}", "status": "failed", "eventType": "${eventType}", "error": "
|
|
2983
|
+
`body = {"dedupeKey": "${dedupeKey}", "status": "failed", "eventType": "${eventType}", "error": "Write the failure reason here"}`,
|
|
2984
2984
|
`req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
|
|
2985
2985
|
'urllib.request.urlopen(req, timeout=10).read()',
|
|
2986
2986
|
'PY',
|
|
@@ -2990,27 +2990,27 @@ async function cmdStart(port) {
|
|
|
2990
2990
|
const contextFile = join(cwd, 'ORDER_CONTEXT.md');
|
|
2991
2991
|
const allowRepoAccess = shouldAllowRepoAccess(payload);
|
|
2992
2992
|
const fileAccessRule = allowRepoAccess
|
|
2993
|
-
? `3.
|
|
2994
|
-
: `3.
|
|
2993
|
+
? `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.`
|
|
2994
|
+
: `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
2995
|
const contextRule = allowRepoAccess
|
|
2996
|
-
? `4.
|
|
2997
|
-
: `4.
|
|
2996
|
+
? `4. Read ${contextFile} first and treat its order description, milestone goal, and submission content as the authoritative source.`
|
|
2997
|
+
: `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
2998
|
const repoRule = allowRepoAccess
|
|
2999
|
-
? `5.
|
|
3000
|
-
: `5.
|
|
2999
|
+
? `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.`
|
|
3000
|
+
: `5. This order is not a repo/code task. Do not expand it into code analysis, engineering changes, or shared-draft review.`;
|
|
3001
3001
|
if (eventType === 'p2p_task') {
|
|
3002
3002
|
return `${promptText}
|
|
3003
3003
|
|
|
3004
|
-
|
|
3005
|
-
1.
|
|
3006
|
-
2.
|
|
3004
|
+
Important requirements:
|
|
3005
|
+
1. This is a P2P task. Do not call atel result; the local SDK will submit the result automatically after your callback.
|
|
3006
|
+
2. Your job is to complete the AI work carefully and send the final conclusion back to the local SDK through the callback.
|
|
3007
3007
|
${fileAccessRule}
|
|
3008
3008
|
${contextRule}
|
|
3009
|
-
5.
|
|
3009
|
+
5. After finishing, you must immediately run the success callback command template below and replace its content with your real result:
|
|
3010
3010
|
|
|
3011
3011
|
${callbackDone}
|
|
3012
3012
|
|
|
3013
|
-
6.
|
|
3013
|
+
6. If it still fails after retries, you must also run the failure callback command below:
|
|
3014
3014
|
|
|
3015
3015
|
${callbackFailed}
|
|
3016
3016
|
`;
|
|
@@ -3018,17 +3018,17 @@ ${callbackFailed}
|
|
|
3018
3018
|
|
|
3019
3019
|
return `${promptText}
|
|
3020
3020
|
|
|
3021
|
-
|
|
3022
|
-
1.
|
|
3023
|
-
2.
|
|
3021
|
+
Important requirements:
|
|
3022
|
+
1. Do not run atel milestone-submit / milestone-verify / milestone-feedback. The local SDK will execute those commands on your behalf after the callback.
|
|
3023
|
+
2. Your job is to complete the AI work carefully and send the final conclusion back to the local SDK through the callback.
|
|
3024
3024
|
${fileAccessRule}
|
|
3025
3025
|
${contextRule}
|
|
3026
3026
|
${repoRule}
|
|
3027
|
-
6.
|
|
3027
|
+
6. After finishing, you must immediately run the success callback command template below and replace its content with your real result:
|
|
3028
3028
|
|
|
3029
3029
|
${callbackDone}
|
|
3030
3030
|
|
|
3031
|
-
7.
|
|
3031
|
+
7. If it still fails after retries, you must also run the failure callback command below:
|
|
3032
3032
|
|
|
3033
3033
|
${callbackFailed}
|
|
3034
3034
|
`;
|
|
@@ -3038,26 +3038,26 @@ ${callbackFailed}
|
|
|
3038
3038
|
if (eventType === 'milestone_submitted') {
|
|
3039
3039
|
return `${promptText}
|
|
3040
3040
|
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3041
|
+
Important: you are not chatting with the user.
|
|
3042
|
+
Do not output markdown, code blocks, explanations, or your reasoning.
|
|
3043
|
+
You must output exactly one line of JSON.
|
|
3044
3044
|
|
|
3045
|
-
|
|
3046
|
-
{"decision":"pass","summary":"
|
|
3045
|
+
For approval:
|
|
3046
|
+
{"decision":"pass","summary":"brief approval reason"}
|
|
3047
3047
|
|
|
3048
|
-
|
|
3049
|
-
{"decision":"reject","reason":"
|
|
3048
|
+
For rejection:
|
|
3049
|
+
{"decision":"reject","reason":"specific rejection reason","summary":"brief review conclusion"}`;
|
|
3050
3050
|
}
|
|
3051
3051
|
|
|
3052
3052
|
if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
|
|
3053
3053
|
return `${promptText}
|
|
3054
3054
|
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3055
|
+
Important: you are not chatting with the user.
|
|
3056
|
+
Do not output markdown, headings, bullet points, explanations, or your reasoning.
|
|
3057
|
+
You must output exactly one line of JSON.
|
|
3058
3058
|
|
|
3059
|
-
|
|
3060
|
-
{"result":"
|
|
3059
|
+
Format:
|
|
3060
|
+
{"result":"the actual deliverable for the current milestone"}`;
|
|
3061
3061
|
}
|
|
3062
3062
|
|
|
3063
3063
|
return promptText;
|
|
@@ -3168,10 +3168,10 @@ ${callbackFailed}
|
|
|
3168
3168
|
return buildAgentCallbackAction(eventType, payload, parsed);
|
|
3169
3169
|
} catch {
|
|
3170
3170
|
const lowered = cleaned.toLowerCase();
|
|
3171
|
-
if (lowered.startsWith('pass') || cleaned.includes('
|
|
3171
|
+
if (lowered.startsWith('pass') || cleaned.includes('\u901a\u8fc7')) {
|
|
3172
3172
|
return buildAgentCallbackAction(eventType, payload, { decision: 'pass', summary: cleaned });
|
|
3173
3173
|
}
|
|
3174
|
-
if (lowered.startsWith('reject') || cleaned.includes('
|
|
3174
|
+
if (lowered.startsWith('reject') || cleaned.includes('\u62d2\u7edd')) {
|
|
3175
3175
|
return buildAgentCallbackAction(eventType, payload, { decision: 'reject', reason: cleaned, summary: cleaned });
|
|
3176
3176
|
}
|
|
3177
3177
|
return { ok: false, error: 'invalid_local_review_stdout' };
|
|
@@ -3375,8 +3375,8 @@ ${callbackFailed}
|
|
|
3375
3375
|
previousApprovedOutputs,
|
|
3376
3376
|
};
|
|
3377
3377
|
const promptText = currentIndex === 0
|
|
3378
|
-
?
|
|
3379
|
-
:
|
|
3378
|
+
? `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.`
|
|
3379
|
+
: `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
3380
|
const recoveryKey = buildMilestoneHookRecoveryKey(eventType, payload);
|
|
3381
3381
|
log({ event: 'trade_reconcile_executor', orderId, currentMilestone: currentIndex, recoveryKey });
|
|
3382
3382
|
const queued = queueAgentHook(eventType, recoveryKey, promptText, workspace.dir, payload, { recoveryKey });
|
|
@@ -3409,7 +3409,7 @@ ${callbackFailed}
|
|
|
3409
3409
|
orderDescription,
|
|
3410
3410
|
previousApprovedOutputs,
|
|
3411
3411
|
};
|
|
3412
|
-
const promptText =
|
|
3412
|
+
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
3413
|
const recoveryKey = buildMilestoneHookRecoveryKey('milestone_submitted', payload);
|
|
3414
3414
|
const queued = queueAgentHook('milestone_submitted', recoveryKey, promptText, workspace.dir, payload, { recoveryKey });
|
|
3415
3415
|
if (queued) log({ event: 'trade_reconcile_requester', orderId, milestoneIndex: submittedMilestone.index, recoveryKey });
|
|
@@ -3615,7 +3615,7 @@ ${callbackFailed}
|
|
|
3615
3615
|
return;
|
|
3616
3616
|
}
|
|
3617
3617
|
// Add working directory context so agent runs atel commands in the right place
|
|
3618
|
-
const cwdNote = `\n\
|
|
3618
|
+
const cwdNote = `\n\nImportant: OpenClaw's analysis working directory is ${hookCwd}. All atel commands must be run in ${atelCwd} (cd ${atelCwd} && atel ...).`;
|
|
3619
3619
|
const enrichedPrompt = sanitizeAgentPrompt(prompt + cwdNote, { eventType: event, dedupeKey });
|
|
3620
3620
|
if (!enrichedPrompt) {
|
|
3621
3621
|
res.json({ status: 'received', eventId, eventType: event, skipped: true });
|
|
@@ -4542,7 +4542,7 @@ ${callbackFailed}
|
|
|
4542
4542
|
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
4543
|
return { status: 'accepted', taskId, message: 'Task accepted. Result will be pushed when ready.' };
|
|
4544
4544
|
} else {
|
|
4545
|
-
const p2pPrompt =
|
|
4545
|
+
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
4546
|
appendP2PTaskStatus({ taskId, role: 'executor', peerDid: message.from, status: 'task_started' });
|
|
4547
4547
|
pushP2PNotification('p2p_task_received', { taskId, peerDid: message.from }).catch(() => {});
|
|
4548
4548
|
pushP2PNotification('p2p_task_started', { taskId, peerDid: message.from }).catch(() => {});
|
|
@@ -4934,6 +4934,17 @@ async function cmdRegister(name, capabilities, endpointUrl) {
|
|
|
4934
4934
|
if (preferredChain) registerPayload.metadata = { preferredChain };
|
|
4935
4935
|
|
|
4936
4936
|
const entry = await client.register(registerPayload, id);
|
|
4937
|
+
let actualWallets = wallets || null;
|
|
4938
|
+
for (let i = 0; i < 8; i++) {
|
|
4939
|
+
try {
|
|
4940
|
+
const resp = await fetch(`${REGISTRY_URL}/registry/v1/agent/${encodeURIComponent(id.did)}`, { signal: AbortSignal.timeout(5000) });
|
|
4941
|
+
if (resp.ok) {
|
|
4942
|
+
const fresh = await resp.json();
|
|
4943
|
+
if (fresh?.wallets) { actualWallets = fresh.wallets; break; }
|
|
4944
|
+
}
|
|
4945
|
+
} catch {}
|
|
4946
|
+
await new Promise(r => setTimeout(r, 750));
|
|
4947
|
+
}
|
|
4937
4948
|
if (!wallets || !preferredChain) {
|
|
4938
4949
|
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
4950
|
}
|
|
@@ -4953,7 +4964,7 @@ async function cmdRegister(name, capabilities, endpointUrl) {
|
|
|
4953
4964
|
capabilities: capDisplay,
|
|
4954
4965
|
endpoint: ep,
|
|
4955
4966
|
discoverable,
|
|
4956
|
-
wallets:
|
|
4967
|
+
wallets: actualWallets || null,
|
|
4957
4968
|
preferredChain: preferredChain || null,
|
|
4958
4969
|
registry: REGISTRY_URL,
|
|
4959
4970
|
}, null, 2));
|
|
@@ -6582,7 +6593,7 @@ async function cmdMilestoneFeedback(orderId) {
|
|
|
6582
6593
|
|
|
6583
6594
|
if (!approve && !feedback) {
|
|
6584
6595
|
console.error('Usage: atel milestone-feedback <orderId> --approve');
|
|
6585
|
-
console.error(' atel milestone-feedback <orderId> --feedback "
|
|
6596
|
+
console.error(' atel milestone-feedback <orderId> --feedback "feedback text"');
|
|
6586
6597
|
process.exit(1);
|
|
6587
6598
|
}
|
|
6588
6599
|
|
|
@@ -6593,7 +6604,7 @@ async function cmdMilestoneFeedback(orderId) {
|
|
|
6593
6604
|
|
|
6594
6605
|
async function cmdMilestoneSubmit(orderId, indexStr) {
|
|
6595
6606
|
if (!orderId || indexStr === undefined) {
|
|
6596
|
-
console.error('Usage: atel milestone-submit <orderId> <index> --result "
|
|
6607
|
+
console.error('Usage: atel milestone-submit <orderId> <index> --result "result summary" [--file <path>]');
|
|
6597
6608
|
console.error(' atel milestone-submit <orderId> <index> --result ./file.pdf [--hash 0x...] [--file <path>]');
|
|
6598
6609
|
process.exit(1);
|
|
6599
6610
|
}
|
|
@@ -6665,7 +6676,7 @@ async function cmdMilestoneSubmit(orderId, indexStr) {
|
|
|
6665
6676
|
async function cmdMilestoneVerify(orderId, indexStr) {
|
|
6666
6677
|
if (!orderId || indexStr === undefined) {
|
|
6667
6678
|
console.error('Usage: atel milestone-verify <orderId> <index> --pass');
|
|
6668
|
-
console.error(' atel milestone-verify <orderId> <index> --reject "
|
|
6679
|
+
console.error(' atel milestone-verify <orderId> <index> --reject "reason"');
|
|
6669
6680
|
process.exit(1);
|
|
6670
6681
|
}
|
|
6671
6682
|
const pass = rawArgs.includes('--pass');
|
|
@@ -8298,6 +8309,10 @@ const commands = {
|
|
|
8298
8309
|
auth: () => cmdAuth(args[0]),
|
|
8299
8310
|
// Send (Rich Media P2P Message)
|
|
8300
8311
|
hub: () => cmdHub(args[0], args.slice(1), rawArgs),
|
|
8312
|
+
key: () => cmdHub('key', args, rawArgs),
|
|
8313
|
+
// Platform account operations
|
|
8314
|
+
swap: () => cmdSwap(args[0], args[1], rawArgs),
|
|
8315
|
+
transfer: () => cmdTransfer(args[0], args[1], rawArgs),
|
|
8301
8316
|
send: () => {
|
|
8302
8317
|
if (rawArgs.includes('--help') || rawArgs.includes('-h') || args.length === 0) {
|
|
8303
8318
|
showSendHelp();
|
|
@@ -8419,7 +8434,7 @@ const commands = {
|
|
|
8419
8434
|
const resp = await fetch(`https://api.telegram.org/bot${target.botToken}/sendMessage`, {
|
|
8420
8435
|
method: 'POST',
|
|
8421
8436
|
headers: { 'Content-Type': 'application/json' },
|
|
8422
|
-
body: JSON.stringify({ chat_id: target.target, text: '🔔 ATEL
|
|
8437
|
+
body: JSON.stringify({ chat_id: target.target, text: '🔔 ATEL notification test\nIf you can see this message, notifications are configured correctly!' }),
|
|
8423
8438
|
signal: AbortSignal.timeout(10000),
|
|
8424
8439
|
});
|
|
8425
8440
|
const data = await resp.json();
|
|
@@ -8504,13 +8519,24 @@ Account Commands:
|
|
|
8504
8519
|
transactions List payment history
|
|
8505
8520
|
|
|
8506
8521
|
Hub Commands:
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8522
|
+
swap usdc <amount> [--chain bsc|base] Swap USDC → ATELToken
|
|
8523
|
+
swap token <amount> [--chain bsc|base] Swap ATELToken → USDC
|
|
8524
|
+
transfer <to_did> <amount> [--memo] Transfer ATELToken to another DID
|
|
8525
|
+
|
|
8526
|
+
key create [--name <name>] Create and save a TokenHub API key
|
|
8527
|
+
key list List TokenHub API keys
|
|
8528
|
+
key revoke <id> Revoke a TokenHub API key
|
|
8529
|
+
key use Print OpenAI-compatible env exports
|
|
8530
|
+
|
|
8531
|
+
hub balance Show USDC and ATELToken balances
|
|
8532
|
+
hub dashboard Show a compact TokenHub account summary
|
|
8533
|
+
hub usage [--model <id>] [--days 7] Show model usage history
|
|
8534
|
+
hub ledger [--page N] [--limit N] Show account transaction records
|
|
8535
|
+
hub swap-history [--page N] [--limit N] Show swap records
|
|
8536
|
+
hub stats Show public TokenHub stats
|
|
8537
|
+
hub models [--search <kw>] List available models
|
|
8538
|
+
hub chat <model> "<prompt>" [--stream] Send a quick chat request
|
|
8539
|
+
hub key <create|list|revoke|use> Manage TokenHub API keys
|
|
8514
8540
|
|
|
8515
8541
|
Trade Commands:
|
|
8516
8542
|
trade-task <cap> <desc> [--budget N] One-shot: search → order → wait → confirm (requester)
|