@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.
Files changed (2) hide show
  1. package/bin/atel.mjs +236 -6
  2. 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]) { res.status(404).json({ error: 'Unknown taskId' }); return; }
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
- savePending(pending);
3601
-
3602
- log({
3603
- event: 'task_queued',
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 = relayAck?.result?.taskId || msg.id || msg.payload?.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.5",
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": "./bin/atel.mjs"
19
+ "atel": "bin/atel.mjs"
20
20
  },
21
21
  "files": [
22
22
  "dist",