@lawrenceliang-btc/atel-sdk 1.2.13 → 1.2.15
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/README.md +8 -10
- package/bin/atel.mjs +188 -87
- package/bin/notification-action-helpers.mjs +76 -16
- package/dist/anchor/index.d.ts +2 -3
- package/dist/anchor/index.js +1 -2
- package/dist/endpoint/index.d.ts +0 -2
- package/dist/handshake/index.d.ts +0 -8
- package/package.json +1 -2
- package/skill/atel-agent/SKILL.md +1 -1
- package/skill/references/commercial.md +3 -14
- package/skill/references/executor.md +1 -1
- package/skill/references/onchain.md +7 -3
- package/skill/references/quickstart.md +1 -5
- package/skill/references/security.md +1 -1
- package/skill/references/workflows.md +1 -1
- package/dist/anchor/solana.d.ts +0 -95
- package/dist/anchor/solana.js +0 -298
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ ATEL provides the cryptographic primitives and protocol building blocks that ena
|
|
|
10
10
|
- **📋 Policy Enforcement** — Scoped consent tokens, call tracking, deterministic hashing
|
|
11
11
|
- **🔍 Execution Tracing** — Tamper-evident, hash-chained audit logs with auto-checkpoints
|
|
12
12
|
- **✅ Proof Generation** — Merkle-tree proof bundles with multi-check verification
|
|
13
|
-
- **⚓ On-Chain Anchoring** — Multi-chain proof anchoring (
|
|
13
|
+
- **⚓ On-Chain Anchoring** — Multi-chain proof anchoring (Base/BSC)
|
|
14
14
|
- **📊 Trust Scoring** — Local trust computation based on execution history
|
|
15
15
|
- **🔔 Notification & Callback Runtime** — Local notify, callback, inbox, and recovery flow
|
|
16
16
|
- **👥 P2P Access Control** — Relationship-based friend system with temporary sessions
|
|
@@ -60,7 +60,7 @@ the `paymentTxHash` on Base, the `auditUrl` pointing at the CompletionProof (Int
|
|
|
60
60
|
### Trust & Verification
|
|
61
61
|
- Tamper-evident execution traces
|
|
62
62
|
- Merkle-tree proof generation
|
|
63
|
-
- On-chain anchoring (
|
|
63
|
+
- On-chain anchoring (Base/BSC)
|
|
64
64
|
- Local trust score computation
|
|
65
65
|
- Callback-driven execution and recovery
|
|
66
66
|
|
|
@@ -359,21 +359,21 @@ const report = ProofVerifier.verify(bundle, { trace });
|
|
|
359
359
|
### On-Chain Anchoring
|
|
360
360
|
|
|
361
361
|
```typescript
|
|
362
|
-
import {
|
|
362
|
+
import { BaseAnchorProvider } from '@lawrenceliang-btc/atel-sdk';
|
|
363
363
|
|
|
364
|
-
const
|
|
365
|
-
rpcUrl: 'https://
|
|
366
|
-
privateKey: process.env.
|
|
364
|
+
const base = new BaseAnchorProvider({
|
|
365
|
+
rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org',
|
|
366
|
+
privateKey: process.env.ATEL_BASE_PRIVATE_KEY
|
|
367
367
|
});
|
|
368
368
|
|
|
369
|
-
const result = await
|
|
369
|
+
const result = await base.anchor(traceRoot, {
|
|
370
370
|
executorDid: 'did:atel:ed25519:...',
|
|
371
371
|
requesterDid: 'did:atel:ed25519:...',
|
|
372
372
|
taskId: 'task-123'
|
|
373
373
|
});
|
|
374
374
|
// result.txHash, result.blockNumber
|
|
375
375
|
|
|
376
|
-
const verified = await
|
|
376
|
+
const verified = await base.verify(traceRoot, txHash);
|
|
377
377
|
// verified.valid, verified.detail
|
|
378
378
|
```
|
|
379
379
|
|
|
@@ -433,8 +433,6 @@ Friend system data is stored in `.atel/`:
|
|
|
433
433
|
## Environment Variables
|
|
434
434
|
|
|
435
435
|
**On-Chain Anchoring:**
|
|
436
|
-
- `ATEL_SOLANA_PRIVATE_KEY` - Solana wallet private key (base58)
|
|
437
|
-
- `ATEL_SOLANA_RPC_URL` - Solana RPC endpoint
|
|
438
436
|
- `ATEL_BASE_PRIVATE_KEY` - Base chain private key (hex)
|
|
439
437
|
- `ATEL_BASE_RPC_URL` - Base RPC endpoint
|
|
440
438
|
- `ATEL_BSC_PRIVATE_KEY` - BSC private key (hex)
|
package/bin/atel.mjs
CHANGED
|
@@ -59,14 +59,14 @@ import crypto from 'node:crypto';
|
|
|
59
59
|
import {
|
|
60
60
|
AgentIdentity, AgentEndpoint, AgentClient, HandshakeManager,
|
|
61
61
|
createMessage, verifyMessage, parseDID, RegistryClient, ExecutionTrace, ProofGenerator,
|
|
62
|
-
|
|
62
|
+
BaseAnchorProvider, BSCAnchorProvider,
|
|
63
63
|
autoNetworkSetup, collectCandidates, connectToAgent,
|
|
64
|
-
discoverPublicIP, checkReachable, ContentAuditor, TrustScoreClient,
|
|
64
|
+
discoverPublicIP, checkReachable, verifyPortReachable, ContentAuditor, TrustScoreClient,
|
|
65
65
|
RollbackManager, rotateKey, verifyKeyRotation, ToolGateway, PolicyEngine, mintConsentToken, sign,
|
|
66
66
|
TrustGraph, calculateTaskWeight,
|
|
67
67
|
} from '@lawrenceliang-btc/atel-sdk';
|
|
68
68
|
import { TunnelManager, HeartbeatManager } from './tunnel-manager.mjs';
|
|
69
|
-
import { buildAgentCallbackAction, getDirectExecutableActions, normalizeGatewayBind, shouldSkipAgentHook, shouldUseGatewaySession } from './notification-action-helpers.mjs';
|
|
69
|
+
import { buildAgentCallbackAction, explainDirectExecutionSkip, getDirectExecutableActions, normalizeGatewayBind, shouldSkipAgentHook, shouldUseGatewaySession } from './notification-action-helpers.mjs';
|
|
70
70
|
import { parseOrderCancelArgs, preflightOrderCancel } from './order-cancel-helpers.mjs';
|
|
71
71
|
// ollama-manager removed — SDK does not run local models
|
|
72
72
|
const initializeOllama = async () => {};
|
|
@@ -107,7 +107,6 @@ function getEnvironmentProfile(url = '') {
|
|
|
107
107
|
const host = String(parsed.hostname || '').toLowerCase();
|
|
108
108
|
if (host === 'api.atelai.org') return { name: 'production', label: 'production', url: normalized };
|
|
109
109
|
if (host === '127.0.0.1' || host === 'localhost') return { name: 'test', label: 'local-test', url: normalized };
|
|
110
|
-
if (host === '43.160.230.129' || host === '43.160.211.180') return { name: 'test', label: 'host-test', url: normalized };
|
|
111
110
|
return { name: 'custom', label: host || 'custom', url: normalized };
|
|
112
111
|
} catch {
|
|
113
112
|
return { name: 'custom', label: normalized, url: normalized };
|
|
@@ -1118,12 +1117,26 @@ async function pushTradeNotification(eventType, payload, body) {
|
|
|
1118
1117
|
if (c === 'bsc') return ' (BSC)';
|
|
1119
1118
|
return '';
|
|
1120
1119
|
};
|
|
1120
|
+
const autoAcceptReasonText = (reason) => {
|
|
1121
|
+
if (reason === 'missing_recommended_actions') return '平台未提供可执行接单动作';
|
|
1122
|
+
if (reason === 'missing_accept_action') return '事件里没有 accept 动作';
|
|
1123
|
+
if (reason === 'task_mode_not_auto') return '当前 taskMode 不是 auto';
|
|
1124
|
+
if (reason === 'auto_accept_platform_disabled') return '当前未启用免费单自动接单';
|
|
1125
|
+
if (reason === 'paid_auto_accept_disabled') return '当前未启用付费单自动接单';
|
|
1126
|
+
if (reason === 'price_exceeds_accept_max') return '订单金额超过自动接单上限';
|
|
1127
|
+
return reason || '未说明';
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1121
1130
|
const templates = {
|
|
1122
1131
|
'order_created': (p) => `📥 收到新订单
|
|
1123
1132
|
订单: ${p.orderId || body?.orderId || '?'}
|
|
1124
1133
|
金额: $${p.priceAmount ?? '?'} USDC
|
|
1125
1134
|
来自: ${p.requesterDid || '未知请求方'}
|
|
1126
1135
|
请审核后决定是否接单`,
|
|
1136
|
+
'order_created_auto_accept_skipped': (p) => `⏸️ 未自动接单
|
|
1137
|
+
订单: ${p.orderId || body?.orderId || '?'}
|
|
1138
|
+
原因: ${autoAcceptReasonText(p.reasonCode)}
|
|
1139
|
+
请人工判断是否接单`,
|
|
1127
1140
|
'order_accepted': (p) => `📋 订单已被接单
|
|
1128
1141
|
订单: ${p.orderId || body?.orderId || '?'}
|
|
1129
1142
|
执行方已开始处理,进入里程碑阶段`,
|
|
@@ -1176,7 +1189,8 @@ async function pushTradeNotification(eventType, payload, body) {
|
|
|
1176
1189
|
订单: ${p.orderId || body?.orderId || '?'}
|
|
1177
1190
|
原因: ${p.reason || '未说明'}`,
|
|
1178
1191
|
'order_expired': (p) => `⌛ 订单已过期
|
|
1179
|
-
订单: ${p.orderId || body?.orderId || '?'}
|
|
1192
|
+
订单: ${p.orderId || p.order_id || body?.orderId || body?.order_id || '?'}
|
|
1193
|
+
原因: ${p.reason || '未说明'}
|
|
1180
1194
|
系统已自动结束该订单`,
|
|
1181
1195
|
'dispute_created': (p) => `⚖️ 争议已创建
|
|
1182
1196
|
订单: ${p.orderId || body?.orderId || '?'}
|
|
@@ -2147,7 +2161,6 @@ function getChainPrivateKey(chain) {
|
|
|
2147
2161
|
return config.chains[chain].privateKey;
|
|
2148
2162
|
}
|
|
2149
2163
|
// 2. Fall back to environment variables (backward compatibility)
|
|
2150
|
-
if (chain === 'solana') return process.env.ATEL_SOLANA_PRIVATE_KEY;
|
|
2151
2164
|
if (chain === 'base') return process.env.ATEL_BASE_PRIVATE_KEY;
|
|
2152
2165
|
if (chain === 'bsc') return process.env.ATEL_BSC_PRIVATE_KEY;
|
|
2153
2166
|
return null;
|
|
@@ -2158,19 +2171,6 @@ async function getWalletAddresses() {
|
|
|
2158
2171
|
const wallets = {};
|
|
2159
2172
|
const config = loadAnchorConfig();
|
|
2160
2173
|
|
|
2161
|
-
// Solana: base58 private key → public key
|
|
2162
|
-
const solKey = getChainPrivateKey('solana');
|
|
2163
|
-
if (solKey) {
|
|
2164
|
-
try {
|
|
2165
|
-
const { Keypair } = await import('@solana/web3.js');
|
|
2166
|
-
const bs58 = (await import('bs58')).default;
|
|
2167
|
-
const kp = Keypair.fromSecretKey(bs58.decode(solKey));
|
|
2168
|
-
wallets.solana = kp.publicKey.toBase58();
|
|
2169
|
-
} catch {}
|
|
2170
|
-
} else if (config?.chains?.solana?.address) {
|
|
2171
|
-
wallets.solana = config.chains.solana.address;
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
2174
|
// Base: hex private key → address
|
|
2175
2175
|
const baseKey = getChainPrivateKey('base');
|
|
2176
2176
|
if (baseKey) {
|
|
@@ -2200,9 +2200,8 @@ async function getWalletAddresses() {
|
|
|
2200
2200
|
function detectPreferredChain() {
|
|
2201
2201
|
const config = loadAnchorConfig();
|
|
2202
2202
|
if (config?.preferredChain) return config.preferredChain;
|
|
2203
|
-
if (getChainPrivateKey('bsc')) return 'bsc';
|
|
2204
2203
|
if (getChainPrivateKey('base')) return 'base';
|
|
2205
|
-
if (getChainPrivateKey('
|
|
2204
|
+
if (getChainPrivateKey('bsc')) return 'bsc';
|
|
2206
2205
|
return null;
|
|
2207
2206
|
}
|
|
2208
2207
|
|
|
@@ -2312,6 +2311,12 @@ function addFriend(did, options = {}) {
|
|
|
2312
2311
|
|
|
2313
2312
|
saveFriends(data);
|
|
2314
2313
|
log({ event: 'friend_added', did, addedBy: options.addedBy });
|
|
2314
|
+
syncContactToPlatform(did, { alias: options.alias || '', notes: options.notes || '' }).catch(() => {});
|
|
2315
|
+
pushP2PNotification('p2p_contact_added', {
|
|
2316
|
+
peerDid: did,
|
|
2317
|
+
alias: options.alias || '',
|
|
2318
|
+
text: options.notes || ''
|
|
2319
|
+
}).catch((e) => log({ event: 'p2p_notify_error', kind: 'friend_added', error: e.message }));
|
|
2315
2320
|
return true;
|
|
2316
2321
|
}
|
|
2317
2322
|
|
|
@@ -2941,16 +2946,23 @@ function riskAllowed(maxRisk, requestedRisk) { return (RISK_ORDER[requestedRisk]
|
|
|
2941
2946
|
// Verify anchor_tx list on-chain, return count of valid proofs
|
|
2942
2947
|
async function verifyAnchorTxList(anchorTxList, targetDid) {
|
|
2943
2948
|
if (!anchorTxList || anchorTxList.length === 0) return { verified: 0, total: 0, proofs: [] };
|
|
2944
|
-
const rpcUrl = process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
|
|
2945
|
-
const provider = new SolanaAnchorProvider({ rpcUrl });
|
|
2946
2949
|
let verified = 0;
|
|
2947
2950
|
const proofs = [];
|
|
2948
2951
|
for (const tx of anchorTxList) {
|
|
2949
2952
|
try {
|
|
2953
|
+
const chain = String(tx?.chain || detectPreferredChain() || 'base').toLowerCase();
|
|
2954
|
+
let provider;
|
|
2955
|
+
if (chain === 'base') {
|
|
2956
|
+
provider = new BaseAnchorProvider({ rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org' });
|
|
2957
|
+
} else if (chain === 'bsc') {
|
|
2958
|
+
provider = new BSCAnchorProvider({ rpcUrl: process.env.ATEL_BSC_RPC_URL || 'https://bsc-dataseed.binance.org' });
|
|
2959
|
+
} else {
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2950
2962
|
const result = await provider.verify(tx.trace_root || '', tx.txHash || tx.anchor_tx || '');
|
|
2951
2963
|
if (result.valid) {
|
|
2952
2964
|
verified++;
|
|
2953
|
-
proofs.push({ proof_id: tx.proof_id || tx.txHash, trace_root: tx.trace_root, verified: true, anchor_tx: tx.txHash || tx.anchor_tx, timestamp: new Date().toISOString() });
|
|
2965
|
+
proofs.push({ proof_id: tx.proof_id || tx.txHash, trace_root: tx.trace_root, verified: true, anchor_tx: tx.txHash || tx.anchor_tx, chain, timestamp: new Date().toISOString() });
|
|
2954
2966
|
}
|
|
2955
2967
|
} catch {}
|
|
2956
2968
|
}
|
|
@@ -3021,12 +3033,7 @@ async function anchorOnChain(traceRoot, metadata, preferredChain) {
|
|
|
3021
3033
|
if (!key) return null;
|
|
3022
3034
|
|
|
3023
3035
|
let provider;
|
|
3024
|
-
if (chain === '
|
|
3025
|
-
provider = new SolanaAnchorProvider({
|
|
3026
|
-
rpcUrl: process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
|
|
3027
|
-
privateKey: key
|
|
3028
|
-
});
|
|
3029
|
-
} else if (chain === 'base') {
|
|
3036
|
+
if (chain === 'base') {
|
|
3030
3037
|
provider = new BaseAnchorProvider({
|
|
3031
3038
|
rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org',
|
|
3032
3039
|
privateKey: key,
|
|
@@ -3125,7 +3132,7 @@ async function configureAnchor() {
|
|
|
3125
3132
|
// 1. Select chain
|
|
3126
3133
|
const chain = await promptChoice(
|
|
3127
3134
|
'Select blockchain for anchoring:',
|
|
3128
|
-
['
|
|
3135
|
+
['base', 'bsc']
|
|
3129
3136
|
);
|
|
3130
3137
|
|
|
3131
3138
|
// 2. Input private key
|
|
@@ -3141,16 +3148,9 @@ async function configureAnchor() {
|
|
|
3141
3148
|
// 3. Validate private key and derive address
|
|
3142
3149
|
let address;
|
|
3143
3150
|
try {
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
const kp = Keypair.fromSecretKey(bs58.decode(privateKey));
|
|
3148
|
-
address = kp.publicKey.toBase58();
|
|
3149
|
-
} else {
|
|
3150
|
-
const { ethers } = await import('ethers');
|
|
3151
|
-
const wallet = new ethers.Wallet(privateKey);
|
|
3152
|
-
address = wallet.address;
|
|
3153
|
-
}
|
|
3151
|
+
const { ethers } = await import('ethers');
|
|
3152
|
+
const wallet = new ethers.Wallet(privateKey);
|
|
3153
|
+
address = wallet.address;
|
|
3154
3154
|
} catch (e) {
|
|
3155
3155
|
console.error(`❌ Invalid ${chain.toUpperCase()} private key: ${e.message}`);
|
|
3156
3156
|
process.exit(1);
|
|
@@ -3667,6 +3667,15 @@ async function cmdStart(port) {
|
|
|
3667
3667
|
log({ event: 'atel_skill_sync_error', error: e.message });
|
|
3668
3668
|
}
|
|
3669
3669
|
|
|
3670
|
+
try {
|
|
3671
|
+
const syncedContacts = await syncAllFriendsToPlatform();
|
|
3672
|
+
if (syncedContacts.attempted > 0) {
|
|
3673
|
+
log({ event: 'contacts_backfill_sync_done', ...syncedContacts });
|
|
3674
|
+
}
|
|
3675
|
+
} catch (e) {
|
|
3676
|
+
log({ event: 'contacts_backfill_sync_error', error: e.message });
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3670
3679
|
// Initialize Ollama only if explicitly enabled (optional local AI audit)
|
|
3671
3680
|
if (process.env.ATEL_OLLAMA_ENABLED === 'true') {
|
|
3672
3681
|
await initializeOllama().catch(err => {
|
|
@@ -3705,14 +3714,6 @@ async function cmdStart(port) {
|
|
|
3705
3714
|
if (!txHash || !traceRoot) return { checked: false, verified: false, reason: 'missing_anchor_or_root' };
|
|
3706
3715
|
const c = (chain || 'base').toLowerCase();
|
|
3707
3716
|
|
|
3708
|
-
if (c === 'solana') {
|
|
3709
|
-
const rpcUrl = process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
|
|
3710
|
-
if (process.env.ATEL_DEBUG) console.error('[DEBUG] Solana RPC URL:', rpcUrl);
|
|
3711
|
-
const provider = new SolanaAnchorProvider({ rpcUrl });
|
|
3712
|
-
const r = await provider.verify(traceRoot, txHash);
|
|
3713
|
-
if (process.env.ATEL_DEBUG) console.error('[DEBUG] Solana verify result:', r);
|
|
3714
|
-
return { checked: true, verified: !!r?.valid, chain: 'solana', detail: r?.detail };
|
|
3715
|
-
}
|
|
3716
3717
|
if (c === 'base') {
|
|
3717
3718
|
const rpcUrl = process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org';
|
|
3718
3719
|
if (process.env.ATEL_DEBUG) console.error('[DEBUG] Base RPC URL:', rpcUrl);
|
|
@@ -4024,6 +4025,22 @@ async function cmdStart(port) {
|
|
|
4024
4025
|
const MAX_HOOK_CONCURRENCY = Math.max(1, Math.min(4, Number.parseInt(process.env.ATEL_HOOK_CONCURRENCY || '3', 10) || 3));
|
|
4025
4026
|
const hookQueue = [];
|
|
4026
4027
|
const activeRecoveryKeys = new Set();
|
|
4028
|
+
const recoveryKeyLogState = new Map();
|
|
4029
|
+
|
|
4030
|
+
function logRecoveryKeyActive(eventType, dedupeKey, recoveryKey) {
|
|
4031
|
+
if (!recoveryKey) return;
|
|
4032
|
+
const now = Date.now();
|
|
4033
|
+
const previous = recoveryKeyLogState.get(recoveryKey) || { lastLoggedAt: 0, suppressed: 0 };
|
|
4034
|
+
if (now - previous.lastLoggedAt < 15000) {
|
|
4035
|
+
previous.suppressed += 1;
|
|
4036
|
+
recoveryKeyLogState.set(recoveryKey, previous);
|
|
4037
|
+
return;
|
|
4038
|
+
}
|
|
4039
|
+
const payload = { event: 'agent_hook_not_queued', eventType, dedupeKey, reason: 'recovery_key_active', recoveryKey };
|
|
4040
|
+
if (previous.suppressed > 0) payload.suppressed = previous.suppressed;
|
|
4041
|
+
log(payload);
|
|
4042
|
+
recoveryKeyLogState.set(recoveryKey, { lastLoggedAt: now, suppressed: 0 });
|
|
4043
|
+
}
|
|
4027
4044
|
|
|
4028
4045
|
endpoint.app?.post?.('/atel/v1/agent-callback', async (req, res) => {
|
|
4029
4046
|
const body = req.body || {};
|
|
@@ -4258,6 +4275,15 @@ async function cmdStart(port) {
|
|
|
4258
4275
|
'urllib.request.urlopen(req, timeout=10).read()',
|
|
4259
4276
|
'PY',
|
|
4260
4277
|
].join('\n'),
|
|
4278
|
+
executor_milestone: [
|
|
4279
|
+
"python3 - <<'PY'",
|
|
4280
|
+
'import json, urllib.request',
|
|
4281
|
+
`result = """Write the final deliverable here"""`,
|
|
4282
|
+
`body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "orderId": "${payload?.orderId || ''}", "milestoneIndex": ${Number.isFinite(Number(payload?.currentMilestone)) ? Number(payload.currentMilestone) : Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0}, "result": result, "summary": result[:120]}`,
|
|
4283
|
+
`req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
|
|
4284
|
+
'urllib.request.urlopen(req, timeout=10).read()',
|
|
4285
|
+
'PY',
|
|
4286
|
+
].join('\n'),
|
|
4261
4287
|
default: [
|
|
4262
4288
|
"python3 - <<'PY'",
|
|
4263
4289
|
'import json, urllib.request',
|
|
@@ -4278,7 +4304,11 @@ async function cmdStart(port) {
|
|
|
4278
4304
|
'PY',
|
|
4279
4305
|
].join('\n');
|
|
4280
4306
|
|
|
4281
|
-
const callbackDone = eventType === 'milestone_submitted'
|
|
4307
|
+
const callbackDone = eventType === 'milestone_submitted'
|
|
4308
|
+
? callbackExamples.milestone_submitted
|
|
4309
|
+
: (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)
|
|
4310
|
+
? callbackExamples.executor_milestone
|
|
4311
|
+
: callbackExamples.default);
|
|
4282
4312
|
const contextFile = join(cwd, 'ORDER_CONTEXT.md');
|
|
4283
4313
|
const allowRepoAccess = shouldAllowRepoAccess(payload);
|
|
4284
4314
|
const fileAccessRule = allowRepoAccess
|
|
@@ -4326,7 +4356,7 @@ ${callbackFailed}
|
|
|
4326
4356
|
`;
|
|
4327
4357
|
}
|
|
4328
4358
|
|
|
4329
|
-
function buildLocalAgentPrompt(eventType, promptText) {
|
|
4359
|
+
function buildLocalAgentPrompt(eventType, promptText, payload = {}) {
|
|
4330
4360
|
if (eventType === 'milestone_submitted') {
|
|
4331
4361
|
return `${promptText}
|
|
4332
4362
|
|
|
@@ -4342,6 +4372,10 @@ For rejection:
|
|
|
4342
4372
|
}
|
|
4343
4373
|
|
|
4344
4374
|
if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
|
|
4375
|
+
const expectedIndex = eventType === 'milestone_plan_confirmed'
|
|
4376
|
+
? (Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0)
|
|
4377
|
+
: (Number.isFinite(Number(payload?.currentMilestone)) ? Number(payload.currentMilestone) : Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0);
|
|
4378
|
+
const expectedOrderId = String(payload?.orderId || '').trim();
|
|
4345
4379
|
return `${promptText}
|
|
4346
4380
|
|
|
4347
4381
|
Important: you are not chatting with the user.
|
|
@@ -4355,9 +4389,11 @@ You must prefer factual evidence:
|
|
|
4355
4389
|
- what concrete output / state / observation you actually got
|
|
4356
4390
|
- whether that evidence satisfies the current milestone
|
|
4357
4391
|
If you could not find evidence, say that explicitly in the JSON result, including what you checked.
|
|
4392
|
+
The JSON must include the exact current order and milestone identity.
|
|
4393
|
+
If you are not certain, fail closed instead of guessing.
|
|
4358
4394
|
|
|
4359
4395
|
Format:
|
|
4360
|
-
{"result":"factual deliverable with concrete evidence and observations only"}`;
|
|
4396
|
+
{"orderId":"${expectedOrderId}","milestoneIndex":${expectedIndex},"result":"factual deliverable with concrete evidence and observations only"}`;
|
|
4361
4397
|
}
|
|
4362
4398
|
|
|
4363
4399
|
return promptText;
|
|
@@ -4486,7 +4522,7 @@ Format:
|
|
|
4486
4522
|
if (isKnownInvalidLocalAgentStdout(cleaned)) {
|
|
4487
4523
|
return { ok: false, error: 'invalid_local_agent_stdout_known_failure' };
|
|
4488
4524
|
}
|
|
4489
|
-
return
|
|
4525
|
+
return { ok: false, error: 'invalid_local_agent_stdout_requires_json' };
|
|
4490
4526
|
}
|
|
4491
4527
|
}
|
|
4492
4528
|
|
|
@@ -4609,7 +4645,7 @@ Format:
|
|
|
4609
4645
|
const recoveryKey = options.recoveryKey || '';
|
|
4610
4646
|
if (recoveryKey) {
|
|
4611
4647
|
if (activeRecoveryKeys.has(recoveryKey)) {
|
|
4612
|
-
|
|
4648
|
+
logRecoveryKeyActive(eventType, dedupeKey, recoveryKey);
|
|
4613
4649
|
return false;
|
|
4614
4650
|
}
|
|
4615
4651
|
activeRecoveryKeys.add(recoveryKey);
|
|
@@ -5143,6 +5179,22 @@ Advance the current milestone strictly based on these approved results. Do not i
|
|
|
5143
5179
|
let directExecutionSucceeded = false;
|
|
5144
5180
|
const rejectLimitReached = event === 'milestone_rejected' && Number(payload?.submitCount || body?.submitCount || 0) >= 3;
|
|
5145
5181
|
const directActions = rejectLimitReached ? [] : getDirectExecutableActions(event, recommendedActions, payload, currentPolicy);
|
|
5182
|
+
if (event === 'order_created' && directActions.length === 0 && !rejectLimitReached) {
|
|
5183
|
+
const skipReason = explainDirectExecutionSkip(event, recommendedActions, payload, currentPolicy);
|
|
5184
|
+
if (skipReason) {
|
|
5185
|
+
log({
|
|
5186
|
+
event: 'order_created_auto_accept_skipped',
|
|
5187
|
+
orderId: payload?.orderId || body?.orderId || '',
|
|
5188
|
+
amount: Number(payload?.priceAmount || 0),
|
|
5189
|
+
reason: skipReason,
|
|
5190
|
+
});
|
|
5191
|
+
pushTradeNotification('order_created_auto_accept_skipped', {
|
|
5192
|
+
...payload,
|
|
5193
|
+
orderId: payload?.orderId || body?.orderId || '',
|
|
5194
|
+
reasonCode: skipReason,
|
|
5195
|
+
}, body).catch(e => log({ event: 'trade_notify_error', error: e.message }));
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5146
5198
|
if (rejectLimitReached) {
|
|
5147
5199
|
log({ event: 'direct_action_skip_manual_arbitration', eventType: event, dedupeKey, orderId: payload?.orderId || body?.orderId || '', milestoneIndex: payload?.milestoneIndex ?? body?.milestoneIndex ?? null, reason: 'rejection_limit_reached' });
|
|
5148
5200
|
}
|
|
@@ -5222,7 +5274,10 @@ Advance the current milestone strictly based on these approved results. Do not i
|
|
|
5222
5274
|
}
|
|
5223
5275
|
const { execFile } = await import('child_process');
|
|
5224
5276
|
const finishHook = () => {
|
|
5225
|
-
if (recoveryKey)
|
|
5277
|
+
if (recoveryKey) {
|
|
5278
|
+
activeRecoveryKeys.delete(recoveryKey);
|
|
5279
|
+
recoveryKeyLogState.delete(recoveryKey);
|
|
5280
|
+
}
|
|
5226
5281
|
activeHookWorkers = Math.max(0, activeHookWorkers - 1);
|
|
5227
5282
|
processHookQueue();
|
|
5228
5283
|
};
|
|
@@ -5237,15 +5292,15 @@ Advance the current milestone strictly based on these approved results. Do not i
|
|
|
5237
5292
|
return;
|
|
5238
5293
|
}
|
|
5239
5294
|
log({ event: 'agent_session_spawn_error', eventType: hookEvent, dedupeKey: hookKey, error: gatewayResult.error, fallback: 'cli' });
|
|
5240
|
-
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg);
|
|
5295
|
+
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg, hookPayload);
|
|
5241
5296
|
} else if (spawnArgs.length > 0) {
|
|
5242
5297
|
const promptArg = spawnArgs[spawnArgs.length - 1] || '';
|
|
5243
|
-
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg);
|
|
5298
|
+
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg, hookPayload);
|
|
5244
5299
|
}
|
|
5245
5300
|
|
|
5246
5301
|
const MAX_ATTEMPTS = 5;
|
|
5247
5302
|
const isMilestoneHook = ['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected', 'milestone_submitted'].includes(hookEvent);
|
|
5248
|
-
const localHookTimeoutMs = isMilestoneHook ?
|
|
5303
|
+
const localHookTimeoutMs = isMilestoneHook ? 240000 : 600000;
|
|
5249
5304
|
const preparedInvocation = prepareHookInvocation(spawnCmd, spawnArgs, hookKey, Math.ceil(localHookTimeoutMs / 1000));
|
|
5250
5305
|
const runHook = (attempt, invocation = preparedInvocation) => {
|
|
5251
5306
|
const hookStartedAt = Date.now();
|
|
@@ -5543,7 +5598,7 @@ Advance the current milestone strictly based on these approved results. Do not i
|
|
|
5543
5598
|
|
|
5544
5599
|
// ── Anchoring Warning ──
|
|
5545
5600
|
if (!anchor) {
|
|
5546
|
-
log({ event: 'anchor_missing', taskId, warning: 'Proof not anchored on-chain. Set
|
|
5601
|
+
log({ event: 'anchor_missing', taskId, warning: 'Proof not anchored on-chain. Set a supported anchor key for verifiable trust.', timestamp: new Date().toISOString() });
|
|
5547
5602
|
}
|
|
5548
5603
|
|
|
5549
5604
|
|
|
@@ -7129,22 +7184,32 @@ async function cmdVerifyProof(anchorTx, traceRoot) {
|
|
|
7129
7184
|
|
|
7130
7185
|
console.log(JSON.stringify({ event: 'verifying_proof', anchor_tx: anchorTx, trace_root: traceRoot }));
|
|
7131
7186
|
|
|
7132
|
-
const
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7187
|
+
const providers = [
|
|
7188
|
+
{ chain: 'base', provider: new BaseAnchorProvider({ rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org' }) },
|
|
7189
|
+
{ chain: 'bsc', provider: new BSCAnchorProvider({ rpcUrl: process.env.ATEL_BSC_RPC_URL || 'https://bsc-dataseed.binance.org' }) },
|
|
7190
|
+
];
|
|
7191
|
+
const attempts = [];
|
|
7192
|
+
for (const entry of providers) {
|
|
7193
|
+
try {
|
|
7194
|
+
const result = await entry.provider.verify(traceRoot, anchorTx);
|
|
7195
|
+
attempts.push({ chain: entry.chain, result });
|
|
7196
|
+
if (result.valid) {
|
|
7197
|
+
console.log(JSON.stringify({
|
|
7198
|
+
verified: true,
|
|
7199
|
+
chain: entry.chain,
|
|
7200
|
+
anchor_tx: anchorTx,
|
|
7201
|
+
trace_root: traceRoot,
|
|
7202
|
+
detail: result.detail || 'Anchor matches trace_root',
|
|
7203
|
+
block: result.blockNumber,
|
|
7204
|
+
timestamp: result.timestamp,
|
|
7205
|
+
}, null, 2));
|
|
7206
|
+
return;
|
|
7207
|
+
}
|
|
7208
|
+
} catch (e) {
|
|
7209
|
+
attempts.push({ chain: entry.chain, error: e.message });
|
|
7210
|
+
}
|
|
7147
7211
|
}
|
|
7212
|
+
console.log(JSON.stringify({ verified: false, anchor_tx: anchorTx, trace_root: traceRoot, attempts }, null, 2));
|
|
7148
7213
|
}
|
|
7149
7214
|
|
|
7150
7215
|
async function cmdAudit(targetDidOrUrl, taskId) {
|
|
@@ -7256,13 +7321,16 @@ async function cmdRotate() {
|
|
|
7256
7321
|
|
|
7257
7322
|
// Anchor rotation on-chain if possible
|
|
7258
7323
|
let anchor = null;
|
|
7259
|
-
const
|
|
7324
|
+
const chain = detectPreferredChain() || 'base';
|
|
7325
|
+
const key = getChainPrivateKey(chain);
|
|
7260
7326
|
if (key) {
|
|
7261
7327
|
try {
|
|
7262
|
-
const
|
|
7328
|
+
const provider = chain === 'bsc'
|
|
7329
|
+
? new BSCAnchorProvider({ rpcUrl: process.env.ATEL_BSC_RPC_URL || 'https://bsc-dataseed.binance.org', privateKey: key })
|
|
7330
|
+
: new BaseAnchorProvider({ rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org', privateKey: key, anchorRegistryAddress: process.env.ATEL_ANCHOR_REGISTRY_ADDRESS || undefined });
|
|
7263
7331
|
const { createHash } = await import('node:crypto');
|
|
7264
7332
|
const rotationHash = createHash('sha256').update(JSON.stringify(proof)).digest('hex');
|
|
7265
|
-
anchor = await
|
|
7333
|
+
anchor = await provider.anchor(`rotation:${rotationHash}`, { oldDid, newDid: newIdentity.did, type: 'key_rotation' });
|
|
7266
7334
|
} catch (e) { console.log(JSON.stringify({ warning: 'On-chain anchor failed', error: e.message })); }
|
|
7267
7335
|
}
|
|
7268
7336
|
|
|
@@ -7293,6 +7361,42 @@ async function cmdRotate() {
|
|
|
7293
7361
|
|
|
7294
7362
|
const PLATFORM_URL = ATEL_PLATFORM;
|
|
7295
7363
|
|
|
7364
|
+
async function syncContactToPlatform(contactDid, options = {}) {
|
|
7365
|
+
const id = requireIdentity();
|
|
7366
|
+
const normalized = String(contactDid || '').trim();
|
|
7367
|
+
if (!normalized || normalized === id.did) return { ok: false, reason: 'invalid_contact' };
|
|
7368
|
+
try {
|
|
7369
|
+
await signedFetch('POST', '/contacts/v1/sync', {
|
|
7370
|
+
contactDid: normalized,
|
|
7371
|
+
alias: String(options.alias || '').trim(),
|
|
7372
|
+
notes: String(options.notes || '').trim(),
|
|
7373
|
+
});
|
|
7374
|
+
log({ event: 'contact_sync_ok', contactDid: normalized });
|
|
7375
|
+
return { ok: true };
|
|
7376
|
+
} catch (e) {
|
|
7377
|
+
log({ event: 'contact_sync_failed', contactDid: normalized, error: e.message || 'unknown_error' });
|
|
7378
|
+
return { ok: false, reason: e.message || 'sync_failed' };
|
|
7379
|
+
}
|
|
7380
|
+
}
|
|
7381
|
+
|
|
7382
|
+
async function syncAllFriendsToPlatform() {
|
|
7383
|
+
const friends = loadFriends();
|
|
7384
|
+
const accepted = Array.isArray(friends?.friends) ? friends.friends : [];
|
|
7385
|
+
if (accepted.length === 0) return { attempted: 0, synced: 0, failed: 0 };
|
|
7386
|
+
let synced = 0;
|
|
7387
|
+
let failed = 0;
|
|
7388
|
+
for (const friend of accepted) {
|
|
7389
|
+
const result = await syncContactToPlatform(friend.did, {
|
|
7390
|
+
alias: friend.alias || '',
|
|
7391
|
+
notes: friend.notes || ''
|
|
7392
|
+
});
|
|
7393
|
+
if (result.ok) synced += 1;
|
|
7394
|
+
else failed += 1;
|
|
7395
|
+
}
|
|
7396
|
+
log({ event: 'contacts_backfill_sync', attempted: accepted.length, synced, failed });
|
|
7397
|
+
return { attempted: accepted.length, synced, failed };
|
|
7398
|
+
}
|
|
7399
|
+
|
|
7296
7400
|
async function signedFetch(method, path, payload = {}) {
|
|
7297
7401
|
const id = requireIdentity();
|
|
7298
7402
|
const { default: nacl } = await import('tweetnacl');
|
|
@@ -7424,6 +7528,10 @@ async function cmdWithdraw(amount, address, chain) {
|
|
|
7424
7528
|
}
|
|
7425
7529
|
// Smart wallet withdrawal (new flow)
|
|
7426
7530
|
chain = chain || 'base';
|
|
7531
|
+
if (chain !== 'base' && chain !== 'bsc') {
|
|
7532
|
+
console.error('Unsupported withdrawal chain. Supported chains: base, bsc.');
|
|
7533
|
+
process.exit(1);
|
|
7534
|
+
}
|
|
7427
7535
|
|
|
7428
7536
|
// Validate destination address
|
|
7429
7537
|
const channel = 'crypto_' + chain;
|
|
@@ -7432,11 +7540,6 @@ async function cmdWithdraw(amount, address, chain) {
|
|
|
7432
7540
|
console.error('Invalid EVM address. Must be 0x followed by 40 hex characters.');
|
|
7433
7541
|
process.exit(1);
|
|
7434
7542
|
}
|
|
7435
|
-
} else if (channel === 'crypto_solana') {
|
|
7436
|
-
if (!address.match(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/)) {
|
|
7437
|
-
console.error('Invalid Solana address.');
|
|
7438
|
-
process.exit(1);
|
|
7439
|
-
}
|
|
7440
7543
|
}
|
|
7441
7544
|
|
|
7442
7545
|
try {
|
|
@@ -8132,9 +8235,9 @@ async function cmdComplete(orderId, taskId) {
|
|
|
8132
8235
|
console.error('[complete] Anchoring proof on-chain...');
|
|
8133
8236
|
anchor = await anchorOnChain(proof.trace_root, { proof_id: proof.proof_id, executorDid: id.did, requesterDid, taskId: effectiveTaskId, action: 'cli-complete' });
|
|
8134
8237
|
if (anchor) {
|
|
8135
|
-
console.error(`[complete] Anchored on
|
|
8238
|
+
console.error(`[complete] Anchored on-chain: ${anchor.txHash}`);
|
|
8136
8239
|
} else {
|
|
8137
|
-
console.error('[complete] WARNING: On-chain anchoring failed. Set
|
|
8240
|
+
console.error('[complete] WARNING: On-chain anchoring failed. Set a supported anchor key for verifiable trust.');
|
|
8138
8241
|
}
|
|
8139
8242
|
}
|
|
8140
8243
|
|
|
@@ -10558,7 +10661,7 @@ Auth Commands:
|
|
|
10558
10661
|
|
|
10559
10662
|
Account Commands:
|
|
10560
10663
|
balance Show platform account balance
|
|
10561
|
-
deposit <amount> [channel] Deposit funds (channel: manual|
|
|
10664
|
+
deposit <amount> [channel] Deposit funds (channel: manual|crypto_base|crypto_bsc|stripe|alipay)
|
|
10562
10665
|
withdraw <amount> [channel] [address] Withdraw funds (address required for crypto)
|
|
10563
10666
|
transactions List payment history
|
|
10564
10667
|
|
|
@@ -10690,8 +10793,6 @@ Environment:
|
|
|
10690
10793
|
ATEL_REGISTRY Registry URL (default: https://api.atelai.org)
|
|
10691
10794
|
ATEL_PLATFORM Platform URL (default: ATEL_REGISTRY value)
|
|
10692
10795
|
ATEL_EXECUTOR_URL Local executor HTTP endpoint
|
|
10693
|
-
ATEL_SOLANA_PRIVATE_KEY Solana key for on-chain anchoring
|
|
10694
|
-
ATEL_SOLANA_RPC_URL Solana RPC (default: mainnet-beta)
|
|
10695
10796
|
ATEL_BASE_PRIVATE_KEY Base chain key for on-chain anchoring
|
|
10696
10797
|
ATEL_BSC_PRIVATE_KEY BSC chain key for on-chain anchoring
|
|
10697
10798
|
|