@lawrenceliang-btc/atel-sdk 1.2.13 → 1.2.14
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/bin/atel.mjs +109 -9
- package/bin/notification-action-helpers.mjs +23 -0
- package/package.json +1 -1
package/bin/atel.mjs
CHANGED
|
@@ -61,12 +61,12 @@ import {
|
|
|
61
61
|
createMessage, verifyMessage, parseDID, RegistryClient, ExecutionTrace, ProofGenerator,
|
|
62
62
|
SolanaAnchorProvider, 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 () => {};
|
|
@@ -1118,12 +1118,26 @@ async function pushTradeNotification(eventType, payload, body) {
|
|
|
1118
1118
|
if (c === 'bsc') return ' (BSC)';
|
|
1119
1119
|
return '';
|
|
1120
1120
|
};
|
|
1121
|
+
const autoAcceptReasonText = (reason) => {
|
|
1122
|
+
if (reason === 'missing_recommended_actions') return '平台未提供可执行接单动作';
|
|
1123
|
+
if (reason === 'missing_accept_action') return '事件里没有 accept 动作';
|
|
1124
|
+
if (reason === 'task_mode_not_auto') return '当前 taskMode 不是 auto';
|
|
1125
|
+
if (reason === 'auto_accept_platform_disabled') return '当前未启用免费单自动接单';
|
|
1126
|
+
if (reason === 'paid_auto_accept_disabled') return '当前未启用付费单自动接单';
|
|
1127
|
+
if (reason === 'price_exceeds_accept_max') return '订单金额超过自动接单上限';
|
|
1128
|
+
return reason || '未说明';
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1121
1131
|
const templates = {
|
|
1122
1132
|
'order_created': (p) => `📥 收到新订单
|
|
1123
1133
|
订单: ${p.orderId || body?.orderId || '?'}
|
|
1124
1134
|
金额: $${p.priceAmount ?? '?'} USDC
|
|
1125
1135
|
来自: ${p.requesterDid || '未知请求方'}
|
|
1126
1136
|
请审核后决定是否接单`,
|
|
1137
|
+
'order_created_auto_accept_skipped': (p) => `⏸️ 未自动接单
|
|
1138
|
+
订单: ${p.orderId || body?.orderId || '?'}
|
|
1139
|
+
原因: ${autoAcceptReasonText(p.reasonCode)}
|
|
1140
|
+
请人工判断是否接单`,
|
|
1127
1141
|
'order_accepted': (p) => `📋 订单已被接单
|
|
1128
1142
|
订单: ${p.orderId || body?.orderId || '?'}
|
|
1129
1143
|
执行方已开始处理,进入里程碑阶段`,
|
|
@@ -1176,7 +1190,8 @@ async function pushTradeNotification(eventType, payload, body) {
|
|
|
1176
1190
|
订单: ${p.orderId || body?.orderId || '?'}
|
|
1177
1191
|
原因: ${p.reason || '未说明'}`,
|
|
1178
1192
|
'order_expired': (p) => `⌛ 订单已过期
|
|
1179
|
-
订单: ${p.orderId || body?.orderId || '?'}
|
|
1193
|
+
订单: ${p.orderId || p.order_id || body?.orderId || body?.order_id || '?'}
|
|
1194
|
+
原因: ${p.reason || '未说明'}
|
|
1180
1195
|
系统已自动结束该订单`,
|
|
1181
1196
|
'dispute_created': (p) => `⚖️ 争议已创建
|
|
1182
1197
|
订单: ${p.orderId || body?.orderId || '?'}
|
|
@@ -2200,9 +2215,8 @@ async function getWalletAddresses() {
|
|
|
2200
2215
|
function detectPreferredChain() {
|
|
2201
2216
|
const config = loadAnchorConfig();
|
|
2202
2217
|
if (config?.preferredChain) return config.preferredChain;
|
|
2203
|
-
if (getChainPrivateKey('bsc')) return 'bsc';
|
|
2204
2218
|
if (getChainPrivateKey('base')) return 'base';
|
|
2205
|
-
if (getChainPrivateKey('
|
|
2219
|
+
if (getChainPrivateKey('bsc')) return 'bsc';
|
|
2206
2220
|
return null;
|
|
2207
2221
|
}
|
|
2208
2222
|
|
|
@@ -2312,6 +2326,12 @@ function addFriend(did, options = {}) {
|
|
|
2312
2326
|
|
|
2313
2327
|
saveFriends(data);
|
|
2314
2328
|
log({ event: 'friend_added', did, addedBy: options.addedBy });
|
|
2329
|
+
syncContactToPlatform(did, { alias: options.alias || '', notes: options.notes || '' }).catch(() => {});
|
|
2330
|
+
pushP2PNotification('p2p_contact_added', {
|
|
2331
|
+
peerDid: did,
|
|
2332
|
+
alias: options.alias || '',
|
|
2333
|
+
text: options.notes || ''
|
|
2334
|
+
}).catch((e) => log({ event: 'p2p_notify_error', kind: 'friend_added', error: e.message }));
|
|
2315
2335
|
return true;
|
|
2316
2336
|
}
|
|
2317
2337
|
|
|
@@ -3125,7 +3145,7 @@ async function configureAnchor() {
|
|
|
3125
3145
|
// 1. Select chain
|
|
3126
3146
|
const chain = await promptChoice(
|
|
3127
3147
|
'Select blockchain for anchoring:',
|
|
3128
|
-
['
|
|
3148
|
+
['base', 'bsc']
|
|
3129
3149
|
);
|
|
3130
3150
|
|
|
3131
3151
|
// 2. Input private key
|
|
@@ -3667,6 +3687,15 @@ async function cmdStart(port) {
|
|
|
3667
3687
|
log({ event: 'atel_skill_sync_error', error: e.message });
|
|
3668
3688
|
}
|
|
3669
3689
|
|
|
3690
|
+
try {
|
|
3691
|
+
const syncedContacts = await syncAllFriendsToPlatform();
|
|
3692
|
+
if (syncedContacts.attempted > 0) {
|
|
3693
|
+
log({ event: 'contacts_backfill_sync_done', ...syncedContacts });
|
|
3694
|
+
}
|
|
3695
|
+
} catch (e) {
|
|
3696
|
+
log({ event: 'contacts_backfill_sync_error', error: e.message });
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3670
3699
|
// Initialize Ollama only if explicitly enabled (optional local AI audit)
|
|
3671
3700
|
if (process.env.ATEL_OLLAMA_ENABLED === 'true') {
|
|
3672
3701
|
await initializeOllama().catch(err => {
|
|
@@ -4024,6 +4053,22 @@ async function cmdStart(port) {
|
|
|
4024
4053
|
const MAX_HOOK_CONCURRENCY = Math.max(1, Math.min(4, Number.parseInt(process.env.ATEL_HOOK_CONCURRENCY || '3', 10) || 3));
|
|
4025
4054
|
const hookQueue = [];
|
|
4026
4055
|
const activeRecoveryKeys = new Set();
|
|
4056
|
+
const recoveryKeyLogState = new Map();
|
|
4057
|
+
|
|
4058
|
+
function logRecoveryKeyActive(eventType, dedupeKey, recoveryKey) {
|
|
4059
|
+
if (!recoveryKey) return;
|
|
4060
|
+
const now = Date.now();
|
|
4061
|
+
const previous = recoveryKeyLogState.get(recoveryKey) || { lastLoggedAt: 0, suppressed: 0 };
|
|
4062
|
+
if (now - previous.lastLoggedAt < 15000) {
|
|
4063
|
+
previous.suppressed += 1;
|
|
4064
|
+
recoveryKeyLogState.set(recoveryKey, previous);
|
|
4065
|
+
return;
|
|
4066
|
+
}
|
|
4067
|
+
const payload = { event: 'agent_hook_not_queued', eventType, dedupeKey, reason: 'recovery_key_active', recoveryKey };
|
|
4068
|
+
if (previous.suppressed > 0) payload.suppressed = previous.suppressed;
|
|
4069
|
+
log(payload);
|
|
4070
|
+
recoveryKeyLogState.set(recoveryKey, { lastLoggedAt: now, suppressed: 0 });
|
|
4071
|
+
}
|
|
4027
4072
|
|
|
4028
4073
|
endpoint.app?.post?.('/atel/v1/agent-callback', async (req, res) => {
|
|
4029
4074
|
const body = req.body || {};
|
|
@@ -4609,7 +4654,7 @@ Format:
|
|
|
4609
4654
|
const recoveryKey = options.recoveryKey || '';
|
|
4610
4655
|
if (recoveryKey) {
|
|
4611
4656
|
if (activeRecoveryKeys.has(recoveryKey)) {
|
|
4612
|
-
|
|
4657
|
+
logRecoveryKeyActive(eventType, dedupeKey, recoveryKey);
|
|
4613
4658
|
return false;
|
|
4614
4659
|
}
|
|
4615
4660
|
activeRecoveryKeys.add(recoveryKey);
|
|
@@ -5143,6 +5188,22 @@ Advance the current milestone strictly based on these approved results. Do not i
|
|
|
5143
5188
|
let directExecutionSucceeded = false;
|
|
5144
5189
|
const rejectLimitReached = event === 'milestone_rejected' && Number(payload?.submitCount || body?.submitCount || 0) >= 3;
|
|
5145
5190
|
const directActions = rejectLimitReached ? [] : getDirectExecutableActions(event, recommendedActions, payload, currentPolicy);
|
|
5191
|
+
if (event === 'order_created' && directActions.length === 0 && !rejectLimitReached) {
|
|
5192
|
+
const skipReason = explainDirectExecutionSkip(event, recommendedActions, payload, currentPolicy);
|
|
5193
|
+
if (skipReason) {
|
|
5194
|
+
log({
|
|
5195
|
+
event: 'order_created_auto_accept_skipped',
|
|
5196
|
+
orderId: payload?.orderId || body?.orderId || '',
|
|
5197
|
+
amount: Number(payload?.priceAmount || 0),
|
|
5198
|
+
reason: skipReason,
|
|
5199
|
+
});
|
|
5200
|
+
pushTradeNotification('order_created_auto_accept_skipped', {
|
|
5201
|
+
...payload,
|
|
5202
|
+
orderId: payload?.orderId || body?.orderId || '',
|
|
5203
|
+
reasonCode: skipReason,
|
|
5204
|
+
}, body).catch(e => log({ event: 'trade_notify_error', error: e.message }));
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5146
5207
|
if (rejectLimitReached) {
|
|
5147
5208
|
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
5209
|
}
|
|
@@ -5222,7 +5283,10 @@ Advance the current milestone strictly based on these approved results. Do not i
|
|
|
5222
5283
|
}
|
|
5223
5284
|
const { execFile } = await import('child_process');
|
|
5224
5285
|
const finishHook = () => {
|
|
5225
|
-
if (recoveryKey)
|
|
5286
|
+
if (recoveryKey) {
|
|
5287
|
+
activeRecoveryKeys.delete(recoveryKey);
|
|
5288
|
+
recoveryKeyLogState.delete(recoveryKey);
|
|
5289
|
+
}
|
|
5226
5290
|
activeHookWorkers = Math.max(0, activeHookWorkers - 1);
|
|
5227
5291
|
processHookQueue();
|
|
5228
5292
|
};
|
|
@@ -7293,6 +7357,42 @@ async function cmdRotate() {
|
|
|
7293
7357
|
|
|
7294
7358
|
const PLATFORM_URL = ATEL_PLATFORM;
|
|
7295
7359
|
|
|
7360
|
+
async function syncContactToPlatform(contactDid, options = {}) {
|
|
7361
|
+
const id = requireIdentity();
|
|
7362
|
+
const normalized = String(contactDid || '').trim();
|
|
7363
|
+
if (!normalized || normalized === id.did) return { ok: false, reason: 'invalid_contact' };
|
|
7364
|
+
try {
|
|
7365
|
+
await signedFetch('POST', '/contacts/v1/sync', {
|
|
7366
|
+
contactDid: normalized,
|
|
7367
|
+
alias: String(options.alias || '').trim(),
|
|
7368
|
+
notes: String(options.notes || '').trim(),
|
|
7369
|
+
});
|
|
7370
|
+
log({ event: 'contact_sync_ok', contactDid: normalized });
|
|
7371
|
+
return { ok: true };
|
|
7372
|
+
} catch (e) {
|
|
7373
|
+
log({ event: 'contact_sync_failed', contactDid: normalized, error: e.message || 'unknown_error' });
|
|
7374
|
+
return { ok: false, reason: e.message || 'sync_failed' };
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7377
|
+
|
|
7378
|
+
async function syncAllFriendsToPlatform() {
|
|
7379
|
+
const friends = loadFriends();
|
|
7380
|
+
const accepted = Array.isArray(friends?.friends) ? friends.friends : [];
|
|
7381
|
+
if (accepted.length === 0) return { attempted: 0, synced: 0, failed: 0 };
|
|
7382
|
+
let synced = 0;
|
|
7383
|
+
let failed = 0;
|
|
7384
|
+
for (const friend of accepted) {
|
|
7385
|
+
const result = await syncContactToPlatform(friend.did, {
|
|
7386
|
+
alias: friend.alias || '',
|
|
7387
|
+
notes: friend.notes || ''
|
|
7388
|
+
});
|
|
7389
|
+
if (result.ok) synced += 1;
|
|
7390
|
+
else failed += 1;
|
|
7391
|
+
}
|
|
7392
|
+
log({ event: 'contacts_backfill_sync', attempted: accepted.length, synced, failed });
|
|
7393
|
+
return { attempted: accepted.length, synced, failed };
|
|
7394
|
+
}
|
|
7395
|
+
|
|
7296
7396
|
async function signedFetch(method, path, payload = {}) {
|
|
7297
7397
|
const id = requireIdentity();
|
|
7298
7398
|
const { default: nacl } = await import('tweetnacl');
|
|
@@ -10558,7 +10658,7 @@ Auth Commands:
|
|
|
10558
10658
|
|
|
10559
10659
|
Account Commands:
|
|
10560
10660
|
balance Show platform account balance
|
|
10561
|
-
deposit <amount> [channel] Deposit funds (channel: manual|
|
|
10661
|
+
deposit <amount> [channel] Deposit funds (channel: manual|crypto_base|crypto_bsc|stripe|alipay)
|
|
10562
10662
|
withdraw <amount> [channel] [address] Withdraw funds (address required for crypto)
|
|
10563
10663
|
transactions List payment history
|
|
10564
10664
|
|
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
export function explainDirectExecutionSkip(eventType, recommendedActions, payload = {}, policy = {}) {
|
|
2
|
+
if (eventType !== 'order_created') return '';
|
|
3
|
+
if (!Array.isArray(recommendedActions) || recommendedActions.length === 0) return 'missing_recommended_actions';
|
|
4
|
+
const hasAcceptAction = recommendedActions.some((action) =>
|
|
5
|
+
action?.type === 'cli' &&
|
|
6
|
+
action?.action === 'accept' &&
|
|
7
|
+
Array.isArray(action.command) &&
|
|
8
|
+
action.command[0] === 'atel'
|
|
9
|
+
);
|
|
10
|
+
if (!hasAcceptAction) return 'missing_accept_action';
|
|
11
|
+
const amount = Number(payload?.priceAmount || 0);
|
|
12
|
+
const autoPolicy = policy?.autoPolicy || {};
|
|
13
|
+
const acceptMaxAmount = Number(autoPolicy.acceptMaxAmount || 0);
|
|
14
|
+
if (amount <= 0) {
|
|
15
|
+
if (policy?.taskMode !== 'auto') return 'task_mode_not_auto';
|
|
16
|
+
if (policy?.autoAcceptPlatform !== true) return 'auto_accept_platform_disabled';
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
if (autoPolicy.acceptOrders !== true) return 'paid_auto_accept_disabled';
|
|
20
|
+
if (acceptMaxAmount > 0 && amount > acceptMaxAmount) return 'price_exceeds_accept_max';
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
|
|
1
24
|
export function getDirectExecutableActions(eventType, recommendedActions, payload = {}, policy = {}) {
|
|
2
25
|
if (!Array.isArray(recommendedActions) || recommendedActions.length === 0) return [];
|
|
3
26
|
|