@lawrenceliang-btc/atel-sdk 1.1.5 → 1.1.6
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 +236 -6
- package/package.json +3 -3
package/bin/atel.mjs
CHANGED
|
@@ -89,6 +89,8 @@ const PENDING_FILE = resolve(ATEL_DIR, 'pending-tasks.json');
|
|
|
89
89
|
const RESULT_PUSH_QUEUE_FILE = resolve(ATEL_DIR, 'pending-result-pushes.json');
|
|
90
90
|
const NOTIFY_TARGETS_FILE = resolve(ATEL_DIR, 'notify-targets.json');
|
|
91
91
|
const TRADE_TRACK_FILE = resolve(ATEL_DIR, 'tracked-orders.json');
|
|
92
|
+
const P2P_STATUS_FILE = resolve(ATEL_DIR, 'p2p-task-status.jsonl');
|
|
93
|
+
const PENDING_AGENT_CALLBACKS_FILE = resolve(ATEL_DIR, 'pending-agent-callbacks.json');
|
|
92
94
|
const KEYS_DIR = resolve(ATEL_DIR, 'keys');
|
|
93
95
|
const ANCHOR_FILE = resolve(KEYS_DIR, 'anchor.json');
|
|
94
96
|
|
|
@@ -244,6 +246,102 @@ async function pushTradeNotification(eventType, payload, body) {
|
|
|
244
246
|
try { saveNotifyTargets(targets); } catch {}
|
|
245
247
|
}
|
|
246
248
|
|
|
249
|
+
function appendP2PTaskStatus(entry) {
|
|
250
|
+
if (!entry?.taskId || !entry?.status) return;
|
|
251
|
+
ensureDir();
|
|
252
|
+
appendFileSync(P2P_STATUS_FILE, `${JSON.stringify({ updatedAt: new Date().toISOString(), ...entry })}\n`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function pushP2PNotification(eventType, payload = {}) {
|
|
256
|
+
const targets = loadNotifyTargets();
|
|
257
|
+
const enabled = (targets.targets || []).filter(t => t.enabled !== false);
|
|
258
|
+
if (enabled.length === 0) return;
|
|
259
|
+
|
|
260
|
+
const templates = {
|
|
261
|
+
'p2p_task_sent': (p) => `📤 P2P任务已发送\n任务: ${p.taskId || '?'}\n目标: ${p.peerDid || '?'}`,
|
|
262
|
+
'p2p_task_received': (p) => `📩 收到新的P2P任务\n任务: ${p.taskId || '?'}\n来自: ${p.peerDid || '?'}`,
|
|
263
|
+
'p2p_task_started': (p) => `▶️ P2P任务开始处理\n任务: ${p.taskId || '?'}\n来自: ${p.peerDid || '?'}`,
|
|
264
|
+
'p2p_result_submitted': (p) => `📨 P2P结果已发回对方\n任务: ${p.taskId || '?'}\n目标: ${p.peerDid || '?'}`,
|
|
265
|
+
'p2p_result_received': (p) => `✅ P2P任务已完成\n任务: ${p.taskId || '?'}\n结果: ${String(p.result || '').slice(0, 80) || '已返回'}`,
|
|
266
|
+
'p2p_task_failed': (p) => `❌ P2P任务失败\n任务: ${p.taskId || '?'}\n原因: ${p.reason || '未知错误'}`,
|
|
267
|
+
};
|
|
268
|
+
const tmpl = templates[eventType];
|
|
269
|
+
if (!tmpl) return;
|
|
270
|
+
const message = tmpl(payload);
|
|
271
|
+
|
|
272
|
+
for (const target of enabled) {
|
|
273
|
+
try {
|
|
274
|
+
if (target.channel === 'telegram' && target.botToken) {
|
|
275
|
+
await fetch(`https://api.telegram.org/bot${target.botToken}/sendMessage`, {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
headers: { 'Content-Type': 'application/json' },
|
|
278
|
+
body: JSON.stringify({ chat_id: target.target, text: message, parse_mode: 'HTML' }),
|
|
279
|
+
signal: AbortSignal.timeout(5000),
|
|
280
|
+
}).catch(() => {});
|
|
281
|
+
} else if (target.channel === 'gateway') {
|
|
282
|
+
const gw = discoverGateway();
|
|
283
|
+
if (gw?.url && gw?.token) {
|
|
284
|
+
await fetch(`${gw.url}/tools/invoke`, {
|
|
285
|
+
method: 'POST',
|
|
286
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${gw.token}` },
|
|
287
|
+
body: JSON.stringify({ tool: 'message', args: { action: 'send', message, target: target.target } }),
|
|
288
|
+
signal: AbortSignal.timeout(5000),
|
|
289
|
+
}).catch(() => {});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
target.lastUsedAt = new Date().toISOString();
|
|
293
|
+
} catch {}
|
|
294
|
+
}
|
|
295
|
+
try { saveNotifyTargets(targets); } catch {}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function loadPendingAgentCallbacksPersisted() {
|
|
299
|
+
if (!existsSync(PENDING_AGENT_CALLBACKS_FILE)) return {};
|
|
300
|
+
try {
|
|
301
|
+
const data = JSON.parse(readFileSync(PENDING_AGENT_CALLBACKS_FILE, 'utf-8'));
|
|
302
|
+
return data && typeof data === 'object' ? data : {};
|
|
303
|
+
} catch {
|
|
304
|
+
return {};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function savePendingAgentCallbacksPersisted(data) {
|
|
309
|
+
ensureDir();
|
|
310
|
+
writeFileSync(PENDING_AGENT_CALLBACKS_FILE, JSON.stringify(data, null, 2));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function persistPendingAgentCallback(dedupeKey, data) {
|
|
314
|
+
if (!dedupeKey || !data) return;
|
|
315
|
+
const persisted = loadPendingAgentCallbacksPersisted();
|
|
316
|
+
persisted[dedupeKey] = {
|
|
317
|
+
...data,
|
|
318
|
+
updatedAt: new Date().toISOString(),
|
|
319
|
+
};
|
|
320
|
+
savePendingAgentCallbacksPersisted(persisted);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function markPersistedPendingAgentCallbackCompleted(dedupeKey, meta = {}) {
|
|
324
|
+
if (!dedupeKey) return;
|
|
325
|
+
const persisted = loadPendingAgentCallbacksPersisted();
|
|
326
|
+
const existing = persisted[dedupeKey];
|
|
327
|
+
persisted[dedupeKey] = {
|
|
328
|
+
...(existing || {}),
|
|
329
|
+
...meta,
|
|
330
|
+
terminal: true,
|
|
331
|
+
completedAt: new Date().toISOString(),
|
|
332
|
+
updatedAt: new Date().toISOString(),
|
|
333
|
+
};
|
|
334
|
+
savePendingAgentCallbacksPersisted(persisted);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function clearPersistedPendingAgentCallback(dedupeKey) {
|
|
338
|
+
if (!dedupeKey) return;
|
|
339
|
+
const persisted = loadPendingAgentCallbacksPersisted();
|
|
340
|
+
if (!persisted[dedupeKey]) return;
|
|
341
|
+
delete persisted[dedupeKey];
|
|
342
|
+
savePendingAgentCallbacksPersisted(persisted);
|
|
343
|
+
}
|
|
344
|
+
|
|
247
345
|
async function executeRecommendedActionDirect(eventType, action, cwd, dedupeKey) {
|
|
248
346
|
const command = Array.isArray(action?.command) ? action.command.filter(Boolean) : [];
|
|
249
347
|
if (command.length === 0) {
|
|
@@ -2392,6 +2490,63 @@ async function cmdStart(port) {
|
|
|
2392
2490
|
const dedupeKey = body.dedupeKey;
|
|
2393
2491
|
const pending = dedupeKey ? pendingAgentCallbacks.get(dedupeKey) : null;
|
|
2394
2492
|
if (!pending) {
|
|
2493
|
+
const persisted = dedupeKey ? loadPendingAgentCallbacksPersisted()[dedupeKey] : null;
|
|
2494
|
+
if (persisted?.terminal) {
|
|
2495
|
+
log({
|
|
2496
|
+
event: 'callback_late_skip',
|
|
2497
|
+
dedupeKey,
|
|
2498
|
+
eventType: persisted.eventType || body?.eventType || 'unknown',
|
|
2499
|
+
callback_source: persisted.source || 'unknown',
|
|
2500
|
+
});
|
|
2501
|
+
res.json({ status: 'ok', skipped: true, reason: 'late_callback_after_completion' });
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
log({
|
|
2505
|
+
event: 'callback_404',
|
|
2506
|
+
callback_404_type: 'unknown_dedupeKey',
|
|
2507
|
+
dedupeKey,
|
|
2508
|
+
callback_404_recovered: !!persisted,
|
|
2509
|
+
callback_source: persisted?.source || 'unknown',
|
|
2510
|
+
eventType: persisted?.eventType || body?.eventType || 'unknown',
|
|
2511
|
+
});
|
|
2512
|
+
if (persisted) {
|
|
2513
|
+
const callbackAction = buildAgentCallbackAction(persisted.eventType, persisted.payload || {}, body);
|
|
2514
|
+
if (callbackAction.ok && callbackAction.action?.type === 'local_result') {
|
|
2515
|
+
try {
|
|
2516
|
+
const localResp = await fetch(`http://127.0.0.1:${p}/atel/v1/result`, {
|
|
2517
|
+
method: 'POST',
|
|
2518
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2519
|
+
body: JSON.stringify({
|
|
2520
|
+
taskId: callbackAction.action.taskId,
|
|
2521
|
+
result: callbackAction.action.result,
|
|
2522
|
+
success: true,
|
|
2523
|
+
}),
|
|
2524
|
+
signal: AbortSignal.timeout(15000),
|
|
2525
|
+
});
|
|
2526
|
+
const localBody = await localResp.json().catch(() => ({}));
|
|
2527
|
+
if (localResp.ok) {
|
|
2528
|
+
clearPersistedPendingAgentCallback(dedupeKey);
|
|
2529
|
+
res.json({ status: 'ok', recovered: true });
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
res.status(500).json({ error: localBody.error || 'local_result_callback_failed', recovered: false });
|
|
2533
|
+
return;
|
|
2534
|
+
} catch (e) {
|
|
2535
|
+
res.status(500).json({ error: e.message || 'local_result_callback_failed', recovered: false });
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
if (callbackAction.ok) {
|
|
2540
|
+
const execResult = await executeRecommendedActionDirect(persisted.eventType, callbackAction.action, persisted.cwd || process.cwd(), dedupeKey);
|
|
2541
|
+
if (execResult.ok) {
|
|
2542
|
+
clearPersistedPendingAgentCallback(dedupeKey);
|
|
2543
|
+
res.json({ status: 'ok', recovered: true });
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
res.status(500).json({ error: execResult.error || 'callback_action_failed', recovered: false });
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2395
2550
|
res.status(404).json({ error: 'Unknown dedupeKey' });
|
|
2396
2551
|
return;
|
|
2397
2552
|
}
|
|
@@ -2407,6 +2562,7 @@ async function cmdStart(port) {
|
|
|
2407
2562
|
|
|
2408
2563
|
if (body.status === 'failed') {
|
|
2409
2564
|
pendingAgentCallbacks.delete(dedupeKey);
|
|
2565
|
+
clearPersistedPendingAgentCallback(dedupeKey);
|
|
2410
2566
|
pending.resolve({ ok: false, body, error: body.error || 'agent_reported_failed' });
|
|
2411
2567
|
res.json({ status: 'ok' });
|
|
2412
2568
|
return;
|
|
@@ -2415,12 +2571,14 @@ async function cmdStart(port) {
|
|
|
2415
2571
|
const callbackAction = buildAgentCallbackAction(pending.eventType, pending.payload || {}, body);
|
|
2416
2572
|
if (callbackAction.skipped) {
|
|
2417
2573
|
pendingAgentCallbacks.delete(dedupeKey);
|
|
2574
|
+
markPersistedPendingAgentCallbackCompleted(dedupeKey, { eventType: pending.eventType, payload: pending.payload, cwd: pending.cwd, source: dedupeKey?.startsWith('reconcile:') ? 'reconcile' : 'main', skipped: true, reason: callbackAction.reason });
|
|
2418
2575
|
pending.resolve({ ok: true, skipped: true, body, reason: callbackAction.reason });
|
|
2419
2576
|
res.json({ status: 'ok', skipped: true });
|
|
2420
2577
|
return;
|
|
2421
2578
|
}
|
|
2422
2579
|
if (!callbackAction.ok) {
|
|
2423
2580
|
pendingAgentCallbacks.delete(dedupeKey);
|
|
2581
|
+
clearPersistedPendingAgentCallback(dedupeKey);
|
|
2424
2582
|
pending.resolve({ ok: false, body, error: callbackAction.error || 'invalid_callback_payload' });
|
|
2425
2583
|
res.status(400).json({ error: callbackAction.error || 'invalid_callback_payload' });
|
|
2426
2584
|
return;
|
|
@@ -2440,6 +2598,11 @@ async function cmdStart(port) {
|
|
|
2440
2598
|
});
|
|
2441
2599
|
const localBody = await localResp.json().catch(() => ({}));
|
|
2442
2600
|
pendingAgentCallbacks.delete(dedupeKey);
|
|
2601
|
+
if (localResp.ok) {
|
|
2602
|
+
markPersistedPendingAgentCallbackCompleted(dedupeKey, { eventType: pending.eventType, payload: pending.payload, cwd: pending.cwd, source: dedupeKey?.startsWith('reconcile:') ? 'reconcile' : 'main', action: callbackAction.action, localBody });
|
|
2603
|
+
} else {
|
|
2604
|
+
clearPersistedPendingAgentCallback(dedupeKey);
|
|
2605
|
+
}
|
|
2443
2606
|
pending.resolve({ ok: localResp.ok, body, action: callbackAction.action, localBody });
|
|
2444
2607
|
if (!localResp.ok) {
|
|
2445
2608
|
res.status(500).json({ error: localBody.error || 'local_result_callback_failed' });
|
|
@@ -2449,6 +2612,7 @@ async function cmdStart(port) {
|
|
|
2449
2612
|
return;
|
|
2450
2613
|
} catch (e) {
|
|
2451
2614
|
pendingAgentCallbacks.delete(dedupeKey);
|
|
2615
|
+
clearPersistedPendingAgentCallback(dedupeKey);
|
|
2452
2616
|
pending.resolve({ ok: false, body, action: callbackAction.action, error: e.message });
|
|
2453
2617
|
res.status(500).json({ error: e.message || 'local_result_callback_failed' });
|
|
2454
2618
|
return;
|
|
@@ -2457,6 +2621,11 @@ async function cmdStart(port) {
|
|
|
2457
2621
|
|
|
2458
2622
|
const execResult = await executeRecommendedActionDirect(pending.eventType, callbackAction.action, pending.cwd || process.cwd(), dedupeKey);
|
|
2459
2623
|
pendingAgentCallbacks.delete(dedupeKey);
|
|
2624
|
+
if (execResult.ok) {
|
|
2625
|
+
markPersistedPendingAgentCallbackCompleted(dedupeKey, { eventType: pending.eventType, payload: pending.payload, cwd: pending.cwd, source: dedupeKey?.startsWith('reconcile:') ? 'reconcile' : 'main', action: callbackAction.action });
|
|
2626
|
+
} else {
|
|
2627
|
+
clearPersistedPendingAgentCallback(dedupeKey);
|
|
2628
|
+
}
|
|
2460
2629
|
pending.resolve({ ok: execResult.ok, body, action: callbackAction.action, execResult });
|
|
2461
2630
|
if (!execResult.ok) {
|
|
2462
2631
|
res.status(500).json({ error: execResult.error || 'callback_action_failed' });
|
|
@@ -2554,6 +2723,7 @@ ${callbackFailed}
|
|
|
2554
2723
|
return await new Promise(async (resolve) => {
|
|
2555
2724
|
const timer = setTimeout(() => {
|
|
2556
2725
|
pendingAgentCallbacks.delete(dedupeKey);
|
|
2726
|
+
persistPendingAgentCallback(dedupeKey, { eventType, payload, cwd, source: dedupeKey.startsWith('reconcile:') ? 'reconcile' : 'main', timeout: true });
|
|
2557
2727
|
resolve({ ok: false, error: 'agent_callback_timeout' });
|
|
2558
2728
|
}, timeoutMs);
|
|
2559
2729
|
|
|
@@ -2567,6 +2737,12 @@ ${callbackFailed}
|
|
|
2567
2737
|
resolve(result);
|
|
2568
2738
|
},
|
|
2569
2739
|
});
|
|
2740
|
+
persistPendingAgentCallback(dedupeKey, {
|
|
2741
|
+
eventType,
|
|
2742
|
+
payload,
|
|
2743
|
+
cwd,
|
|
2744
|
+
source: dedupeKey.startsWith('reconcile:') ? 'reconcile' : 'main',
|
|
2745
|
+
});
|
|
2570
2746
|
|
|
2571
2747
|
try {
|
|
2572
2748
|
const resp = await fetch(`${gw.url}/tools/invoke`, {
|
|
@@ -2586,6 +2762,7 @@ ${callbackFailed}
|
|
|
2586
2762
|
});
|
|
2587
2763
|
if (!resp.ok) {
|
|
2588
2764
|
pendingAgentCallbacks.delete(dedupeKey);
|
|
2765
|
+
clearPersistedPendingAgentCallback(dedupeKey);
|
|
2589
2766
|
clearTimeout(timer);
|
|
2590
2767
|
resolve({ ok: false, error: `sessions_spawn_http_${resp.status}` });
|
|
2591
2768
|
return;
|
|
@@ -2598,6 +2775,7 @@ ${callbackFailed}
|
|
|
2598
2775
|
log({ event: 'agent_session_spawned', eventType, dedupeKey, childSessionKey });
|
|
2599
2776
|
} catch (e) {
|
|
2600
2777
|
pendingAgentCallbacks.delete(dedupeKey);
|
|
2778
|
+
clearPersistedPendingAgentCallback(dedupeKey);
|
|
2601
2779
|
clearTimeout(timer);
|
|
2602
2780
|
resolve({ ok: false, error: e.message });
|
|
2603
2781
|
}
|
|
@@ -2958,7 +3136,24 @@ ${callbackFailed}
|
|
|
2958
3136
|
// Result callback: POST /atel/v1/result (executor calls this when done)
|
|
2959
3137
|
endpoint.app?.post?.('/atel/v1/result', async (req, res) => {
|
|
2960
3138
|
const { taskId, result, success, trace: executorTrace } = req.body || {};
|
|
2961
|
-
if (!taskId || !pendingTasks[taskId]) {
|
|
3139
|
+
if (!taskId || !pendingTasks[taskId]) {
|
|
3140
|
+
try {
|
|
3141
|
+
if (existsSync(P2P_STATUS_FILE)) {
|
|
3142
|
+
const lines = readFileSync(P2P_STATUS_FILE, 'utf-8').split('\n').filter(Boolean);
|
|
3143
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
3144
|
+
const entry = JSON.parse(lines[i]);
|
|
3145
|
+
if (entry?.taskId === taskId && entry?.status === 'result_received') {
|
|
3146
|
+
log({ event: 'callback_late_skip', taskId, callback_source: 'result' });
|
|
3147
|
+
res.json({ status: 'ok', skipped: true, reason: 'late_result_after_completion' });
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
} catch {}
|
|
3153
|
+
log({ event: 'callback_404', callback_404_type: 'unknown_taskId', taskId, callback_404_recovered: false, callback_source: 'result' });
|
|
3154
|
+
res.status(404).json({ error: 'Unknown taskId' });
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
2962
3157
|
|
|
2963
3158
|
// Milestone auto-execution removed. Agent handles results via its own AI.
|
|
2964
3159
|
const task = pendingTasks[taskId];
|
|
@@ -3242,6 +3437,8 @@ ${callbackFailed}
|
|
|
3242
3437
|
}
|
|
3243
3438
|
|
|
3244
3439
|
log({ event: 'result_push_starting', taskId, hasSenderEndpoint: !!task.senderEndpoint, hasSenderCandidates: !!(task.senderCandidates?.length) });
|
|
3440
|
+
appendP2PTaskStatus({ taskId, role: 'executor', peerDid: task.from, status: 'result_submitted', result });
|
|
3441
|
+
pushP2PNotification('p2p_result_submitted', { taskId, peerDid: task.from, result }).catch(() => {});
|
|
3245
3442
|
|
|
3246
3443
|
// Push result back to sender
|
|
3247
3444
|
// Re-lookup sender if we don't have their endpoint (e.g., lookup failed at accept time)
|
|
@@ -3302,6 +3499,13 @@ ${callbackFailed}
|
|
|
3302
3499
|
}
|
|
3303
3500
|
} catch (pushErr) { log({ event: 'result_push_outer_error', taskId, error: pushErr.message, stack: pushErr.stack?.split('\n')[1]?.trim() }); }
|
|
3304
3501
|
|
|
3502
|
+
appendP2PTaskStatus({ taskId, role: 'requester', peerDid: task.from, status: 'result_received', result });
|
|
3503
|
+
pushP2PNotification(success === false ? 'p2p_task_failed' : 'p2p_result_received', {
|
|
3504
|
+
taskId,
|
|
3505
|
+
peerDid: task.from,
|
|
3506
|
+
result,
|
|
3507
|
+
reason: success === false ? (result?.error || 'execution_failed') : null,
|
|
3508
|
+
}).catch(() => {});
|
|
3305
3509
|
delete pendingTasks[taskId]; saveTasks(pendingTasks);
|
|
3306
3510
|
res.json({ status: 'ok', proof_id: proof.proof_id, anchor_tx: anchor?.txHash || null });
|
|
3307
3511
|
});
|
|
@@ -3483,6 +3687,13 @@ ${callbackFailed}
|
|
|
3483
3687
|
// Ignore task-result messages (these are responses, not new tasks)
|
|
3484
3688
|
if (message.type === 'task-result' || payload.status === 'completed' || payload.status === 'failed') {
|
|
3485
3689
|
log({ event: 'result_received', type: 'task-result', from: message.from, taskId: payload.taskId, status: payload.status, proof: payload.proof || null, anchor: payload.anchor || null, execution: payload.execution || null, result: payload.result || null, timestamp: new Date().toISOString() });
|
|
3690
|
+
appendP2PTaskStatus({ taskId: payload.taskId, role: 'requester', peerDid: message.from, status: 'result_received', result: payload.result || null });
|
|
3691
|
+
pushP2PNotification(payload.status === 'failed' ? 'p2p_task_failed' : 'p2p_result_received', {
|
|
3692
|
+
taskId: payload.taskId,
|
|
3693
|
+
peerDid: message.from,
|
|
3694
|
+
result: payload.result || null,
|
|
3695
|
+
reason: payload.error || payload.result?.error || null,
|
|
3696
|
+
}).catch(() => {});
|
|
3486
3697
|
return { status: 'ok', message: 'Result received' };
|
|
3487
3698
|
}
|
|
3488
3699
|
|
|
@@ -3597,10 +3808,12 @@ ${callbackFailed}
|
|
|
3597
3808
|
encrypted: !!session?.encrypted,
|
|
3598
3809
|
reason: 'open_mode_requires_confirm'
|
|
3599
3810
|
};
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3811
|
+
savePending(pending);
|
|
3812
|
+
appendP2PTaskStatus({ taskId, role: 'executor', peerDid: message.from, status: 'task_received' });
|
|
3813
|
+
pushP2PNotification('p2p_task_received', { taskId, peerDid: message.from }).catch(() => {});
|
|
3814
|
+
|
|
3815
|
+
log({
|
|
3816
|
+
event: 'task_queued',
|
|
3604
3817
|
taskId,
|
|
3605
3818
|
from: message.from,
|
|
3606
3819
|
action,
|
|
@@ -3659,6 +3872,8 @@ ${callbackFailed}
|
|
|
3659
3872
|
encrypted: !!session?.encrypted,
|
|
3660
3873
|
};
|
|
3661
3874
|
savePending(pending);
|
|
3875
|
+
appendP2PTaskStatus({ taskId, role: 'executor', peerDid: message.from, status: 'task_received' });
|
|
3876
|
+
pushP2PNotification('p2p_task_received', { taskId, peerDid: message.from }).catch(() => {});
|
|
3662
3877
|
log({ event: 'task_queued', taskId, from: message.from, action, reason: taskMode === 'confirm' ? 'task_mode_confirm' : 'autoAcceptP2P_off', timestamp: new Date().toISOString() });
|
|
3663
3878
|
return { status: 'queued', taskId, message: 'Task queued for manual confirmation. Use: atel approve ' + taskId };
|
|
3664
3879
|
}
|
|
@@ -3690,14 +3905,21 @@ ${callbackFailed}
|
|
|
3690
3905
|
sessionId: accessCheck.sessionId
|
|
3691
3906
|
};
|
|
3692
3907
|
saveTasks(pendingTasks);
|
|
3908
|
+
appendP2PTaskStatus({ taskId, role: 'executor', peerDid: message.from, status: 'task_received' });
|
|
3693
3909
|
log({ event: 'task_accepted', taskId, from: message.from, action, encrypted: !!session?.encrypted, relationship: accessCheck.relationship, timestamp: new Date().toISOString() });
|
|
3694
3910
|
|
|
3695
3911
|
// Forward to executor, gateway session, or echo fallback
|
|
3696
3912
|
if (EXECUTOR_URL) {
|
|
3913
|
+
appendP2PTaskStatus({ taskId, role: 'executor', peerDid: message.from, status: 'task_started' });
|
|
3914
|
+
pushP2PNotification('p2p_task_received', { taskId, peerDid: message.from }).catch(() => {});
|
|
3915
|
+
pushP2PNotification('p2p_task_started', { taskId, peerDid: message.from }).catch(() => {});
|
|
3697
3916
|
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 }));
|
|
3698
3917
|
return { status: 'accepted', taskId, message: 'Task accepted. Result will be pushed when ready.' };
|
|
3699
3918
|
} else {
|
|
3700
3919
|
const p2pPrompt = `你是 ATEL 接单方 Agent,收到一个 P2P 任务。\n任务类型:${action}\n任务内容:${JSON.stringify(payload?.payload || payload || {}, null, 2)}\n请认真完成任务,并通过回调返回最终结果。`;
|
|
3920
|
+
appendP2PTaskStatus({ taskId, role: 'executor', peerDid: message.from, status: 'task_started' });
|
|
3921
|
+
pushP2PNotification('p2p_task_received', { taskId, peerDid: message.from }).catch(() => {});
|
|
3922
|
+
pushP2PNotification('p2p_task_started', { taskId, peerDid: message.from }).catch(() => {});
|
|
3701
3923
|
const queued = queueAgentHook('p2p_task', `p2p:${taskId}`, p2pPrompt, process.cwd(), { taskId });
|
|
3702
3924
|
if (queued) {
|
|
3703
3925
|
return { status: 'accepted', taskId, message: 'Task accepted. Result will be pushed when ready.' };
|
|
@@ -3715,6 +3937,8 @@ ${callbackFailed}
|
|
|
3715
3937
|
const anchor = await anchorOnChain(proof.trace_root, { proof_id: proof.proof_id, executorDid: id.did, requesterDid: message.from, action, taskId });
|
|
3716
3938
|
const echoAcceptedAt = pendingTasks[taskId]?.acceptedAt;
|
|
3717
3939
|
delete pendingTasks[taskId]; saveTasks(pendingTasks);
|
|
3940
|
+
appendP2PTaskStatus({ taskId, role: 'executor', peerDid: message.from, status: 'task_failed', reason: 'no_executor' });
|
|
3941
|
+
pushP2PNotification('p2p_task_failed', { taskId, peerDid: message.from, reason: 'no_executor' }).catch(() => {});
|
|
3718
3942
|
|
|
3719
3943
|
// ── Trust Score + Graph Update (echo mode) ──
|
|
3720
3944
|
try {
|
|
@@ -4346,10 +4570,13 @@ async function cmdTask(target, taskJson) {
|
|
|
4346
4570
|
const relayAck = await relaySend('/atel/v1/task', msg);
|
|
4347
4571
|
|
|
4348
4572
|
console.log(JSON.stringify({ status: 'task_sent', remoteDid, via: 'relay', relay_ack: relayAck, note: 'Relay mode is async. Waiting for result (up to 120s)...' }));
|
|
4573
|
+
const relayTaskId = relayAck?.result?.taskId || msg.id || msg.payload?.taskId;
|
|
4574
|
+
appendP2PTaskStatus({ taskId: relayTaskId, role: 'requester', peerDid: remoteDid, status: 'task_sent' });
|
|
4575
|
+
pushP2PNotification('p2p_task_sent', { taskId: relayTaskId, peerDid: remoteDid }).catch(() => {});
|
|
4349
4576
|
|
|
4350
4577
|
// Wait for result to arrive in inbox (poll for task-result)
|
|
4351
4578
|
// Extract taskId from relay ack (assigned by remote agent), fallback to msg fields
|
|
4352
|
-
const taskId =
|
|
4579
|
+
const taskId = relayTaskId;
|
|
4353
4580
|
let result = null;
|
|
4354
4581
|
const waitStart = Date.now();
|
|
4355
4582
|
const WAIT_TIMEOUT = 120000; // 2 minutes
|
|
@@ -4429,6 +4656,9 @@ async function cmdTask(target, taskJson) {
|
|
|
4429
4656
|
const msg = createMessage({ type: 'task', from: id.did, to: remoteDid, payload: enhancedPayload, secretKey: id.secretKey });
|
|
4430
4657
|
const result = await client.sendTask(remoteEndpoint, msg, hsManager);
|
|
4431
4658
|
console.log(JSON.stringify({ status: 'task_sent', remoteDid, via: remoteEndpoint, result }, null, 2));
|
|
4659
|
+
const directTaskId = result?.taskId || taskRequest.taskId || msg.id || msg.payload?.taskId;
|
|
4660
|
+
appendP2PTaskStatus({ taskId: directTaskId, role: 'requester', peerDid: remoteDid, status: 'task_sent' });
|
|
4661
|
+
pushP2PNotification('p2p_task_sent', { taskId: directTaskId, peerDid: remoteDid }).catch(() => {});
|
|
4432
4662
|
|
|
4433
4663
|
// Update local trust history
|
|
4434
4664
|
const success = result?.status !== 'rejected' && result?.status !== 'failed';
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lawrenceliang-btc/atel-sdk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "ATEL Protocol SDK - Agent Trust & Exchange Layer",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/LawrenceLiang-BTC/atel-sdk.git"
|
|
7
|
+
"url": "git+https://github.com/LawrenceLiang-BTC/atel-sdk.git"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"bin": {
|
|
19
|
-
"atel": "
|
|
19
|
+
"atel": "bin/atel.mjs"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|