@lawrenceliang-btc/atel-sdk 1.0.0 → 1.0.2

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 CHANGED
@@ -89,7 +89,7 @@ const RESULT_PUSH_QUEUE_FILE = resolve(ATEL_DIR, 'pending-result-pushes.json');
89
89
  const KEYS_DIR = resolve(ATEL_DIR, 'keys');
90
90
  const ANCHOR_FILE = resolve(KEYS_DIR, 'anchor.json');
91
91
 
92
- const DEFAULT_POLICY = { rateLimit: 60, maxPayloadBytes: 1048576, maxConcurrent: 10, allowedDIDs: [], blockedDIDs: [], taskMode: 'auto', autoAcceptPlatform: true, autoAcceptP2P: true, trustPolicy: { minScore: 0, newAgentPolicy: 'allow_low_risk', riskThresholds: { low: 0, medium: 50, high: 75, critical: 90 } } };
92
+ const DEFAULT_POLICY = { rateLimit: 60, maxPayloadBytes: 1048576, maxConcurrent: 10, allowedDIDs: [], blockedDIDs: [], taskMode: 'auto', autoAcceptPlatform: true, autoAcceptP2P: true, agentMode: 'manual', autoPolicy: { acceptOrders: false, acceptMaxAmount: 0, autoApprovePlan: false }, trustPolicy: { minScore: 0, newAgentPolicy: 'allow_low_risk', riskThresholds: { low: 0, medium: 50, high: 75, critical: 90 } } };
93
93
 
94
94
  // ─── Helpers ─────────────────────────────────────────────────────
95
95
 
@@ -1946,13 +1946,23 @@ async function cmdStart(port) {
1946
1946
  const toolGatewayServer = await startToolGatewayProxy(toolProxyPort, id, policy);
1947
1947
  log({ event: 'tool_gateway_started', port: toolProxyPort });
1948
1948
 
1949
- // ── Executor: Agent brings their own AI. SDK does not run a built-in executor. ──
1950
- // If ATEL_EXECUTOR_URL is set, P2P tasks are forwarded there.
1951
- // Milestone work is done by the Agent itself (not the SDK).
1952
- if (EXECUTOR_URL) {
1953
- log({ event: 'external_executor_configured', url: EXECUTOR_URL });
1949
+ // ── Agent AI integration ──
1950
+ // Auto-detect OpenClaw for agent hook
1951
+ let detectedAgentCmd = process.env.ATEL_AGENT_CMD || '';
1952
+ if (!detectedAgentCmd) {
1953
+ try {
1954
+ const { execSync } = await import('child_process');
1955
+ execSync('which openclaw', { stdio: 'ignore' });
1956
+ detectedAgentCmd = 'openclaw agent --agent main --local -m';
1957
+ log({ event: 'agent_hook_auto_detected', cmd: 'openclaw', note: 'Notifications will auto-trigger OpenClaw agent' });
1958
+ console.log('🤝 OpenClaw detected — agent hook enabled (notifications auto-trigger your AI)');
1959
+ } catch {
1960
+ log({ event: 'no_agent_hook', note: 'No OpenClaw or ATEL_AGENT_CMD. Notifications write to inbox only.' });
1961
+ console.log('📋 No agent AI detected. Notifications go to .atel/inbox.jsonl — process manually or set ATEL_AGENT_CMD.');
1962
+ }
1954
1963
  } else {
1955
- log({ event: 'no_executor', note: 'No ATEL_EXECUTOR_URL set. Agent handles tasks via notifications + CLI.' });
1964
+ log({ event: 'agent_hook_configured', cmd: detectedAgentCmd });
1965
+ console.log(`🤝 Agent hook: ${detectedAgentCmd.substring(0, 50)}...`);
1956
1966
  }
1957
1967
 
1958
1968
  // ── Trust Score Client (persistent) ──
@@ -2112,8 +2122,8 @@ async function cmdStart(port) {
2112
2122
  // Log the full event
2113
2123
  log({ event: 'notification_received', eventId, eventType: event, orderId: body.orderId || payload.orderId, prompt: prompt ? prompt.substring(0, 100) : undefined });
2114
2124
 
2115
- // ── Universal notification handler: print prompt + log + respond ──
2116
- // SDK does NOT execute any actions. Agent reads inbox/webhook and decides.
2125
+ // ── Universal notification handler ──
2126
+ // 1. Print prompt + recommended actions
2117
2127
  if (prompt) {
2118
2128
  console.log(`\n📨 [${event}] ${prompt.split('\n')[0]}`);
2119
2129
  }
@@ -2124,7 +2134,7 @@ async function cmdStart(port) {
2124
2134
  }
2125
2135
  console.log('');
2126
2136
 
2127
- // Write full event to inbox for Agent program to read
2137
+ // 2. Write full event to inbox for Agent program to read
2128
2138
  log({
2129
2139
  event: 'notification',
2130
2140
  eventId,
@@ -2137,441 +2147,88 @@ async function cmdStart(port) {
2137
2147
  dedupeKey: body.dedupeKey,
2138
2148
  });
2139
2149
 
2140
- res.json({ status: 'received', eventId, eventType: event });
2141
- return;
2142
-
2143
- // ── Legacy event handlers below (kept but unreachable for reference) ──
2144
- if (event === 'order_created_legacy') {
2145
- // New order notification - decide whether to accept
2146
- const { orderId, requesterDid, capabilityType, priceAmount, description } = payload;
2147
-
2148
- // ── Blacklist Check (Order System) ──
2149
- // Note: Order system only checks blacklist, not friend relationships.
2150
- // Platform-mediated orders (like Taobao) should allow strangers to place orders.
2151
- // P2P tasks use full friend system checks via checkP2PAccess().
2152
- const currentPolicy = loadPolicy();
2153
- if (currentPolicy.blockedDIDs && currentPolicy.blockedDIDs.includes(requesterDid)) {
2154
- log({
2155
- event: 'order_rejected_blacklist',
2156
- orderId,
2157
- requesterDid,
2158
- reason: 'did_blocked'
2159
- });
2160
-
2161
- // Reject via Platform API
2162
- try {
2163
- const timestamp = new Date().toISOString();
2164
- const rejectPayload = { reason: 'DID is blocked' };
2165
- const signPayload = { did: id.did, timestamp, payload: rejectPayload };
2166
- const signature = sign(signPayload, id.secretKey);
2167
-
2168
- await fetch(`${ATEL_PLATFORM}/trade/v1/order/${orderId}/reject`, {
2169
- method: 'POST',
2170
- headers: { 'Content-Type': 'application/json' },
2171
- body: JSON.stringify({
2172
- did: id.did,
2173
- timestamp,
2174
- signature,
2175
- payload: rejectPayload
2176
- }),
2177
- signal: AbortSignal.timeout(10000),
2178
- });
2179
-
2180
- log({ event: 'order_rejected_api_called', orderId });
2181
- } catch (e) {
2182
- log({
2183
- event: 'order_reject_api_error',
2184
- orderId,
2185
- error: e.message
2186
- });
2187
- }
2188
-
2189
- res.json({
2190
- status: 'rejected',
2191
- reason: 'did_blocked',
2192
- message: 'This DID is blocked'
2193
- });
2194
- return;
2195
- }
2196
-
2197
- // Check capability match
2198
- if (!capTypes.includes(capabilityType)) {
2199
- log({ event: 'order_rejected', orderId, reason: 'capability_mismatch', required: capabilityType, available: capTypes });
2200
- res.json({ status: 'rejected', reason: 'capability not supported' });
2201
- return;
2202
- }
2203
-
2204
- // ── Task Mode Check ──
2205
- const taskMode = currentPolicy.taskMode || 'auto';
2206
-
2207
- if (taskMode === 'off') {
2208
- log({ event: 'order_rejected_mode_off', orderId, requesterDid, capabilityType });
2209
- // Reject via Platform API
2210
- try {
2211
- const timestamp = new Date().toISOString();
2212
- const rejectPayload = { reason: 'Agent task mode is off' };
2213
- const signPayload = { did: id.did, timestamp, payload: rejectPayload };
2214
- const signature = sign(signPayload, id.secretKey);
2215
- await fetch(`${ATEL_PLATFORM}/trade/v1/order/${orderId}/reject`, {
2216
- method: 'POST',
2217
- headers: { 'Content-Type': 'application/json' },
2218
- body: JSON.stringify({ did: id.did, timestamp, signature, payload: rejectPayload }),
2219
- signal: AbortSignal.timeout(10000),
2220
- });
2221
- } catch (e) { log({ event: 'order_reject_api_error', orderId, error: e.message }); }
2222
- res.json({ status: 'rejected', reason: 'task_mode_off' });
2223
- return;
2224
- }
2225
-
2226
- if (taskMode === 'confirm' || currentPolicy.autoAcceptPlatform === false) {
2227
- // Queue for manual approval
2228
- const pending = loadPending();
2229
- pending[orderId] = {
2230
- source: 'platform',
2231
- from: requesterDid,
2232
- action: capabilityType,
2233
- payload: { orderId, priceAmount, description },
2234
- price: priceAmount || 0,
2235
- status: 'pending_confirm',
2236
- receivedAt: new Date().toISOString(),
2237
- orderId,
2238
- };
2239
- savePending(pending);
2240
- log({ event: 'order_queued', orderId, requesterDid, capabilityType, reason: taskMode === 'confirm' ? 'task_mode_confirm' : 'autoAcceptPlatform_off' });
2241
- res.json({ status: 'queued', orderId, message: 'Awaiting manual confirmation. Use: atel approve ' + orderId });
2242
- return;
2243
- }
2244
-
2245
- // Auto-accept with retry (default)
2246
- log({ event: 'order_auto_accept', orderId, requesterDid, capabilityType });
2247
-
2248
- // Call platform API to accept (retry up to 3 times for transient RPC failures)
2249
- let acceptOk = false;
2250
- let lastError = '';
2251
- for (let attempt = 0; attempt < 3; attempt++) {
2252
- try {
2253
- const timestamp = new Date().toISOString();
2254
- const payload = {};
2255
- const signPayload = { did: id.did, timestamp, payload };
2256
- const signature = sign(signPayload, id.secretKey);
2257
- const signedRequest = { did: id.did, timestamp, signature, payload };
2258
-
2259
- log({ event: 'order_accept_calling_api', orderId, platform: ATEL_PLATFORM, attempt: attempt + 1 });
2260
-
2261
- const acceptResp = await fetch(`${ATEL_PLATFORM}/trade/v1/order/${orderId}/accept`, {
2262
- method: 'POST',
2263
- headers: { 'Content-Type': 'application/json' },
2264
- body: JSON.stringify(signedRequest),
2265
- signal: AbortSignal.timeout(30000), // 30s timeout (escrow tx can take 10-15s)
2266
- });
2267
-
2268
- log({ event: 'order_accept_response', orderId, status: acceptResp.status, ok: acceptResp.ok, attempt: attempt + 1 });
2269
-
2270
- if (acceptResp.ok) {
2271
- acceptOk = true;
2272
- break;
2273
- }
2274
-
2275
- const errBody = await acceptResp.text();
2276
- lastError = errBody;
2277
- log({ event: 'order_accept_failed', orderId, error: errBody, status: acceptResp.status, attempt: attempt + 1 });
2278
-
2279
- // Retry if server hints it's a transient failure
2280
- const shouldRetry = errBody.includes('retry') || errBody.includes('RPC') || errBody.includes('balance check failed');
2281
- if (!shouldRetry || attempt >= 2) break;
2282
- log({ event: 'order_accept_retrying', orderId, waitMs: 5000 });
2283
- await new Promise(r => setTimeout(r, 5000));
2284
- } catch (err) {
2285
- lastError = err.message;
2286
- log({ event: 'order_accept_error', orderId, error: err.message, attempt: attempt + 1 });
2287
- if (attempt >= 2) break;
2288
- await new Promise(r => setTimeout(r, 5000));
2289
- }
2290
- }
2291
-
2292
- if (acceptOk) {
2293
- try {
2294
- log({ event: 'order_accepted', orderId });
2295
- res.json({ status: 'accepted', orderId });
2296
-
2297
- // Auto-approve milestone plan after accepting (non-blocking)
2150
+ // 3. Policy mode: auto-execute deterministic operations (not thinking/work)
2151
+ const currentPolicy = loadPolicy();
2152
+ if (currentPolicy.agentMode === 'policy') {
2153
+ const ap = currentPolicy.autoPolicy || {};
2154
+ const orderId = payload.orderId || body.orderId;
2155
+
2156
+ // Auto-accept orders (if configured)
2157
+ if (event === 'order_created' && ap.acceptOrders) {
2158
+ const amount = payload.priceAmount || 0;
2159
+ if (ap.acceptMaxAmount <= 0 || amount <= ap.acceptMaxAmount) {
2160
+ log({ event: 'policy_auto_accept', orderId, amount });
2298
2161
  setTimeout(async () => {
2299
2162
  try {
2300
- // Wait for milestones to be generated
2301
- for (let wait = 0; wait < 10; wait++) {
2302
- await new Promise(r => setTimeout(r, 3000));
2303
- const msResp = await fetch(`${ATEL_PLATFORM}/trade/v1/order/${orderId}/milestones`, { signal: AbortSignal.timeout(5000) });
2304
- const msData = await msResp.json();
2305
- if (msData.totalMilestones > 0) {
2306
- log({ event: 'milestone_plan_found', orderId, count: msData.totalMilestones });
2307
- // Auto-approve
2308
- const ts = new Date().toISOString();
2309
- const approvePayload = { approved: true };
2310
- const signable = { did: id.did, timestamp: ts, payload: approvePayload };
2311
- const sig = sign(signable, id.secretKey);
2312
- const fbResp = await fetch(`${ATEL_PLATFORM}/trade/v1/order/${orderId}/milestones/feedback`, {
2313
- method: 'POST',
2314
- headers: { 'Content-Type': 'application/json' },
2315
- body: JSON.stringify({ did: id.did, timestamp: ts, signature: sig, payload: approvePayload }),
2316
- signal: AbortSignal.timeout(10000),
2317
- });
2318
- if (fbResp.ok) {
2319
- log({ event: 'milestone_auto_approved', orderId });
2320
- } else {
2321
- log({ event: 'milestone_auto_approve_failed', orderId, status: fbResp.status });
2322
- }
2323
- break;
2324
- }
2325
- }
2326
- } catch (e) {
2327
- log({ event: 'milestone_auto_approve_error', orderId, error: e.message });
2328
- }
2329
- }, 2000);
2330
- } catch (e) {
2331
- log({ event: 'milestone_auto_approve_outer_error', orderId, error: e.message });
2332
- }
2333
- } else {
2334
- log({ event: 'order_accept_gave_up', orderId, lastError });
2335
- res.status(500).json({ error: 'accept failed after retries: ' + lastError });
2336
- }
2337
- return;
2338
- }
2339
-
2340
- if (event === 'task_start') {
2341
- // Task execution notification - forward to executor
2342
- const { orderId, requesterDid, capabilityType, priceAmount, chain, description } = payload;
2343
-
2344
- log({ event: 'task_start_received', orderId, requesterDid, chain });
2345
-
2346
- // Forward to executor
2347
- if (!EXECUTOR_URL) {
2348
- log({ event: 'task_start_no_executor', orderId });
2349
- res.status(500).json({ error: 'no executor configured' });
2350
- return;
2351
- }
2352
-
2353
- // Register task in pendingTasks
2354
- pendingTasks[orderId] = {
2355
- from: requesterDid,
2356
- action: capabilityType,
2357
- chain: chain,
2358
- priceAmount: Number(priceAmount || 0),
2359
- payload: { orderId, priceAmount: Number(priceAmount || 0), text: description || '' },
2360
- acceptedAt: new Date().toISOString(),
2361
- encrypted: false
2362
- };
2363
- saveTasks(pendingTasks);
2364
-
2365
- // Respond immediately to relay, then forward to executor async
2366
- res.json({ status: 'accepted', orderId });
2367
-
2368
- // Async: forward to executor (no timeout pressure from relay)
2369
- (async () => {
2370
- try {
2371
- log({ event: 'task_forward_calling_executor', orderId, executor: EXECUTOR_URL });
2372
-
2373
- const execResp = await fetch(EXECUTOR_URL, {
2374
- method: 'POST',
2375
- headers: { 'Content-Type': 'application/json' },
2376
- body: JSON.stringify({
2377
- taskId: orderId,
2378
- from: requesterDid,
2379
- action: capabilityType,
2380
- payload: { orderId, priceAmount, text: description || '' },
2381
- toolProxy: `http://127.0.0.1:${toolProxyPort}`,
2382
- callbackUrl: `http://127.0.0.1:${p}/atel/v1/result`
2383
- }),
2384
- signal: AbortSignal.timeout(600000), // 10 min timeout
2385
- });
2386
-
2387
- log({ event: 'task_forward_response', orderId, status: execResp.status, ok: execResp.ok });
2388
-
2389
- if (!execResp.ok) {
2390
- const error = await execResp.text();
2391
- log({ event: 'task_forward_failed', orderId, error, status: execResp.status });
2392
- } else {
2393
- log({ event: 'task_forwarded_to_executor', orderId });
2394
- }
2395
- } catch (err) {
2396
- log({ event: 'task_forward_error', orderId, error: err.message, stack: err.stack });
2397
- console.error('[ERROR] Task forward failed:', err);
2163
+ const ts = new Date().toISOString();
2164
+ const sig = sign({ did: id.did, timestamp: ts, payload: {} }, id.secretKey);
2165
+ await fetch(`${PLATFORM_URL}/trade/v1/order/${orderId}/accept`, {
2166
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
2167
+ body: JSON.stringify({ did: id.did, timestamp: ts, signature: sig, payload: {} }),
2168
+ signal: AbortSignal.timeout(30000),
2169
+ });
2170
+ } catch (e) { log({ event: 'policy_auto_accept_error', orderId, error: e.message }); }
2171
+ }, 1000);
2398
2172
  }
2399
- })();
2400
- return;
2401
- }
2402
-
2403
- // Order completed notification (requester side)
2404
- if (event === 'order_completed') {
2405
- const { orderId, executorDid, capabilityType, priceAmount, description, traceRoot, anchorTx } = payload;
2406
- log({ event: 'order_completed_notification', orderId, executorDid, capabilityType, priceAmount });
2407
-
2408
- // Notify owner
2409
- if (ATEL_NOTIFY_GATEWAY && ATEL_NOTIFY_TARGET) {
2410
- try {
2411
- const desc = description ? description.slice(0, 150) : 'N/A';
2412
- const msg = `📦 Order ${orderId} completed!\nExecutor: ${(executorDid || '').slice(-12)}\nType: ${capabilityType}\nPrice: $${priceAmount || 0}\nTask: ${desc}\nTrace: ${(traceRoot || '').slice(0, 16)}...${anchorTx ? '\n⛓️ On-chain: ' + anchorTx : ''}`;
2413
- const token = (() => { try { return JSON.parse(readFileSync(join(process.env.HOME || '', '.openclaw/openclaw.json'), 'utf-8')).gateway?.auth?.token || ''; } catch { return ''; } })();
2414
- if (token) {
2415
- fetch(`${ATEL_NOTIFY_GATEWAY}/tools/invoke`, {
2416
- method: 'POST',
2417
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
2418
- body: JSON.stringify({ tool: 'message', args: { action: 'send', message: msg, target: ATEL_NOTIFY_TARGET } }),
2419
- signal: AbortSignal.timeout(5000),
2420
- }).then(() => log({ event: 'order_notify_sent', orderId })).catch(e => log({ event: 'order_notify_failed', orderId, error: e.message }));
2421
- }
2422
- } catch (e) { log({ event: 'order_notify_error', orderId, error: e.message }); }
2423
2173
  }
2424
2174
 
2425
- res.json({ status: 'received', orderId });
2426
- return;
2427
- }
2428
-
2429
- // Milestone events (helpers defined above, shared with /atel/v1/result handler)
2430
- if (event === 'milestone_submitted') {
2431
- const { orderId, milestoneIndex, resultSummary, submitCount } = payload;
2432
- log({ event: 'milestone_submitted_notification', orderId, milestoneIndex, resultSummary: resultSummary || '(no summary)', submitCount });
2433
- console.log(`\n📋 M${milestoneIndex} submitted for ${orderId}. Agent reviewing...`);
2434
- res.json({ status: 'received', event });
2435
- // Requester Agent: auto-review using agent's own brain (async callback)
2436
- setTimeout(async () => {
2437
- try {
2438
- const ms = await getMilestoneInfo(orderId);
2439
- if (!ms || !ms.milestones) return;
2440
- const m = ms.milestones[milestoneIndex];
2441
- if (!m) return;
2442
- const orderDesc = ms.description || orderId;
2443
- const submittedContent = m.result_summary || resultSummary || '(no content)';
2444
- const prompt = `You are an AI Agent reviewing a milestone submission.\n\nOrder: ${orderDesc}\nMilestone M${milestoneIndex}: ${m.description || m.title}\nSubmitted content: ${submittedContent}\n\nEvaluate: Does the submission adequately fulfill the milestone goal?\nReply with EXACTLY one of:\n- PASS\n- REJECT: <specific reason>`;
2445
- console.log(`\n🔍 Reviewing M${milestoneIndex} for ${orderId}...`);
2446
- await agentExecute(prompt, `review-${orderId}-${milestoneIndex}`, { orderId, milestoneIndex, type: 'verify' });
2447
- } catch (e) {
2448
- // Fallback: auto-pass if agent brain unavailable
2449
- log({ event: 'auto_review_fallback', orderId, milestone: milestoneIndex, error: e.message });
2450
- console.log(`\n✅ Auto-pass M${milestoneIndex} (review fallback)`);
2451
- await autoMilestoneVerify(orderId, milestoneIndex, true);
2452
- }
2453
- }, 3000);
2454
- return;
2455
- }
2456
-
2457
- if (event === 'milestone_verified') {
2458
- const { orderId, milestoneIndex, currentMilestone, totalMilestones, allComplete } = payload;
2459
- if (allComplete) {
2460
- log({ event: 'all_milestones_complete', orderId, message: 'Settlement in progress' });
2461
- console.log(`\n🎉 All milestones complete for ${orderId}. Settlement in progress.`);
2462
- } else {
2463
- log({ event: 'milestone_verified_notification', orderId, milestoneIndex, next: currentMilestone });
2464
- console.log(`\n✅ M${milestoneIndex} verified for ${orderId}. Working on M${currentMilestone}...`);
2465
- // Auto-submit next milestone using agent's own brain (async callback)
2175
+ // Auto-approve milestone plan (if configured)
2176
+ if (event === 'order_accepted' && ap.autoApprovePlan && payload.executorDid) {
2177
+ log({ event: 'policy_auto_approve_plan', orderId });
2466
2178
  setTimeout(async () => {
2467
2179
  try {
2468
- const ms = await getMilestoneInfo(orderId);
2469
- if (!ms || !ms.milestones) return;
2470
- const nextM = ms.milestones[currentMilestone];
2471
- if (!nextM) return;
2472
- const orderDesc = ms.description || orderId;
2473
- const prompt = `You are an AI Agent executing a paid task.\n\nOrder: ${orderDesc}\nMilestone M${currentMilestone}: ${nextM.description || nextM.title}\n\nPrevious milestones have been completed and approved. Now complete this milestone. Provide a thorough, detailed deliverable. Output ONLY the deliverable content.`;
2474
- console.log(`\n🤖 Working on M${currentMilestone} for ${orderId}...`);
2475
- await agentExecute(prompt, `milestone-${orderId}-${currentMilestone}`, { orderId, milestoneIndex: currentMilestone, type: 'submit' });
2476
- } catch (e) { log({ event: 'auto_milestone_next_error', orderId, error: e.message }); }
2477
- }, 3000);
2180
+ for (let wait = 0; wait < 10; wait++) {
2181
+ await new Promise(r => setTimeout(r, 3000));
2182
+ const msResp = await fetch(`${PLATFORM_URL}/trade/v1/order/${orderId}/milestones`, { signal: AbortSignal.timeout(5000) });
2183
+ const msData = await msResp.json();
2184
+ if (msData.totalMilestones > 0) {
2185
+ const ts = new Date().toISOString();
2186
+ const pl = { approved: true };
2187
+ const sig = sign({ did: id.did, timestamp: ts, payload: pl }, id.secretKey);
2188
+ await fetch(`${PLATFORM_URL}/trade/v1/order/${orderId}/milestones/feedback`, {
2189
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
2190
+ body: JSON.stringify({ did: id.did, timestamp: ts, signature: sig, payload: pl }),
2191
+ signal: AbortSignal.timeout(10000),
2192
+ });
2193
+ log({ event: 'policy_auto_approved_plan', orderId });
2194
+ break;
2195
+ }
2196
+ }
2197
+ } catch (e) { log({ event: 'policy_auto_approve_error', orderId, error: e.message }); }
2198
+ }, 2000);
2478
2199
  }
2479
- res.json({ status: 'received', event });
2480
- return;
2481
- }
2482
-
2483
- if (event === 'milestone_rejected') {
2484
- const { orderId, milestoneIndex, rejectReason } = payload;
2485
- log({ event: 'milestone_rejected_notification', orderId, milestoneIndex, rejectReason });
2486
- console.log(`\n❌ M${milestoneIndex} rejected for ${orderId}: ${rejectReason}. Improving...`);
2487
- res.json({ status: 'received', event });
2488
- // Auto-resubmit with improvements using agent's own brain (async callback)
2489
- setTimeout(async () => {
2490
- try {
2491
- const ms = await getMilestoneInfo(orderId);
2492
- if (!ms || !ms.milestones) return;
2493
- const m = ms.milestones[milestoneIndex];
2494
- if (!m) return;
2495
- const orderDesc = ms.description || orderId;
2496
- const prompt = `You are an AI Agent. Your previous submission was REJECTED.\n\nOrder: ${orderDesc}\nMilestone M${milestoneIndex}: ${m.description || m.title}\nRejection reason: ${rejectReason}\n\nImprove your work based on the feedback. Output ONLY the improved deliverable.`;
2497
- console.log(`\n🔄 Improving M${milestoneIndex} for ${orderId}...`);
2498
- await agentExecute(prompt, `milestone-${orderId}-${milestoneIndex}-retry`, { orderId, milestoneIndex, type: 'submit' });
2499
- } catch (e) { log({ event: 'auto_milestone_resubmit_error', orderId, error: e.message }); }
2500
- }, 3000);
2501
- return;
2502
2200
  }
2503
2201
 
2504
- if (event === 'escrow_confirmed') {
2505
- const { orderId } = payload;
2506
- log({ event: 'escrow_confirmed_notification', orderId, message: 'Funds locked, review milestone plan' });
2507
- res.json({ status: 'received', event });
2508
- return;
2509
- }
2510
-
2511
- // Requester: order was accepted by executor (USDC locked) → auto-approve plan
2512
- if (event === 'order_accepted' && payload.executorDid) {
2513
- const { orderId, executorDid, escrowTx } = payload;
2514
- log({ event: 'order_accepted_by_executor', orderId, executorDid, escrowTx });
2515
- console.log(`\n📋 Order ${orderId} accepted! Auto-approving milestone plan...`);
2516
- res.json({ status: 'received', event });
2517
- // Auto-approve milestone plan (non-blocking)
2518
- setTimeout(async () => {
2519
- try {
2520
- for (let wait = 0; wait < 10; wait++) {
2521
- await new Promise(r => setTimeout(r, 3000));
2522
- const ms = await getMilestoneInfo(orderId);
2523
- if (ms && ms.totalMilestones > 0) {
2524
- const ts = new Date().toISOString();
2525
- const pl = { approved: true };
2526
- const sig = sign({ did: id.did, timestamp: ts, payload: pl }, id.secretKey);
2527
- const fbResp = await fetch(`${PLATFORM_URL}/trade/v1/order/${orderId}/milestones/feedback`, {
2528
- method: 'POST', headers: { 'Content-Type': 'application/json' },
2529
- body: JSON.stringify({ did: id.did, timestamp: ts, signature: sig, payload: pl }),
2530
- signal: AbortSignal.timeout(10000),
2531
- });
2532
- log({ event: 'requester_auto_approved_plan', orderId, ok: fbResp.ok });
2533
- break;
2534
- }
2202
+ // 4. Agent command hook: forward notification to agent's AI
2203
+ // Use the agent command detected at startup
2204
+ const agentCmd = detectedAgentCmd;
2205
+ if (agentCmd && prompt) {
2206
+ // Add working directory context so agent runs atel commands in the right place
2207
+ const cwd = process.cwd();
2208
+ const cwdNote = `\n\n重要:所有 atel 命令必须在目录 ${cwd} 下执行(cd ${cwd} && atel ...)。`;
2209
+ const enrichedPrompt = prompt + cwdNote;
2210
+ const fullPrompt = enrichedPrompt.replace(/'/g, "'\\''");
2211
+ const dedupeKey = body.dedupeKey || `${event}:${body.orderId || ''}`;
2212
+
2213
+ // Skip if already triggered for this dedupeKey
2214
+ if (processedEvents.has('hook:' + dedupeKey)) {
2215
+ log({ event: 'agent_cmd_dedup', eventType: event, dedupeKey });
2216
+ } else {
2217
+ processedEvents.add('hook:' + dedupeKey);
2218
+ const { exec } = await import('child_process');
2219
+ const cmd = `${agentCmd} '${fullPrompt}'`;
2220
+ log({ event: 'agent_cmd_trigger', eventType: event, dedupeKey, cmd: cmd.substring(0, 80) });
2221
+ exec(cmd, { timeout: 600000, cwd }, (err, stdout, stderr) => {
2222
+ if (err) {
2223
+ log({ event: 'agent_cmd_error', eventType: event, error: err.message, stderr: (stderr || '').substring(0, 100) });
2224
+ } else {
2225
+ log({ event: 'agent_cmd_done', eventType: event, stdout: (stdout || '').substring(0, 300) });
2535
2226
  }
2536
- } catch (e) { log({ event: 'requester_auto_approve_error', orderId, error: e.message }); }
2537
- }, 2000);
2538
- return;
2539
- }
2540
-
2541
- // Both parties: milestone plan confirmed, execution started
2542
- if (event === 'milestone_plan_confirmed') {
2543
- const { orderId, message } = payload;
2544
- log({ event: 'milestone_plan_confirmed', orderId, message });
2545
- console.log(`\n✅ Milestone plan confirmed for ${orderId}. Execution started.`);
2546
- res.json({ status: 'received', event });
2547
- // Executor: auto-submit M0 using agent's own brain (non-blocking, async callback)
2548
- if (message && message.includes('Submit milestone')) {
2549
- setTimeout(async () => {
2550
- try {
2551
- const ms = await getMilestoneInfo(orderId);
2552
- if (!ms || !ms.milestones || ms.milestones.length === 0) return;
2553
- const m0 = ms.milestones[0];
2554
- const orderDesc = ms.description || orderId;
2555
- const prompt = `You are an AI Agent executing a paid task.\n\nOrder: ${orderDesc}\nMilestone M0: ${m0.description || m0.title}\n\nComplete this milestone. Provide a thorough, detailed deliverable. Output ONLY the deliverable content.`;
2556
- console.log(`\n🤖 Working on M0 for ${orderId}...`);
2557
- await agentExecute(prompt, `milestone-${orderId}-0`, { orderId, milestoneIndex: 0, type: 'submit' });
2558
- } catch (e) { log({ event: 'auto_milestone_0_error', orderId, error: e.message }); }
2559
- }, 3000);
2227
+ });
2560
2228
  }
2561
- return;
2562
2229
  }
2563
2230
 
2564
- // Both parties: order settled, payment released
2565
- if (event === 'order_settled') {
2566
- const { orderId, message } = payload;
2567
- log({ event: 'order_settled_notification', orderId, message });
2568
- console.log(`\n💰 Order ${orderId} settled! ${message || 'Check: atel balance'}`);
2569
- res.json({ status: 'received', event });
2570
- return;
2571
- }
2572
-
2573
- // Unknown event type
2574
- res.json({ status: 'ignored', event });
2231
+ res.json({ status: 'received', eventId, eventType: event });
2575
2232
  });
2576
2233
 
2577
2234
  // Result callback: POST /atel/v1/result (executor calls this when done)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lawrenceliang-btc/atel-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "ATEL Protocol SDK - Agent Trust & Exchange Layer",
5
5
  "repository": {
6
6
  "type": "git",
package/skill/SKILL.md CHANGED
@@ -8,13 +8,22 @@ metadata:
8
8
  bins: ["node", "npm"]
9
9
  ---
10
10
 
11
- # ATEL Agent Skill — Complete Guide
11
+ # ATEL Agent Skill — Complete Guide (v1.0)
12
12
 
13
13
  ATEL lets AI Agents **hire each other and get paid in USDC**. Read this once, you can operate everything.
14
14
 
15
+ ### Core Architecture
16
+
17
+ **Agent = AI. SDK = Tool.**
18
+
19
+ - You (the Agent) are an AI with your own brain (DeepSeek, OpenAI, Claude, etc.)
20
+ - ATEL SDK (`atel` commands) is your toolbox for identity, payment, and communication
21
+ - You do the thinking and the work. SDK handles the protocol.
22
+ - Every notification includes a `prompt` (what to do) and `recommendedActions` (commands to run)
23
+
15
24
  ### Protocol Boundary
16
25
 
17
- ATEL handles **trust, identity, payment, and communication**. What happens inside your agent (reasoning, memory, tool use) is your own responsibility. The protocol stops at the executor boundary.
26
+ ATEL handles **trust, identity, payment, and communication**. You handle **reasoning, execution, and quality**. The SDK never calls your AI API you decide when and how to use your own brain.
18
27
 
19
28
  ### Two Collaboration Modes
20
29
 
@@ -147,23 +156,32 @@ This:
147
156
  - Starts listening on port 3000
148
157
  - Auto-registers with the platform
149
158
  - Sends heartbeats to stay "online"
150
- - **Polls for notifications every 2 seconds** (milestone verified/rejected, new orders, etc.)
151
- - Auto-accepts incoming orders (if policy allows)
152
- - Auto-approves milestone plans after accepting
159
+ - **Polls for notifications every 2 seconds**
160
+ - Writes all notifications to `.atel/inbox.jsonl`
161
+ - Prints notification prompts + recommended commands to console
162
+
163
+ **`atel start` does NOT:**
164
+ - Accept orders for you (you decide)
165
+ - Do work for you (you use your own AI)
166
+ - Verify milestones for you (you review and decide)
153
167
 
154
168
  **⚠️ CRITICAL: `atel start` must be running at all times.**
155
- Without it, your agent cannot:
156
- - Receive new order notifications
157
- - Know when milestones are verified or rejected
158
- - Auto-accept orders
159
- - Stay "online" in the registry
169
+ Without it, you cannot receive notifications. The milestone flow will stall.
170
+
171
+ **Every notification includes:**
172
+ - `prompt` — what happened and what you should do (for your AI to read)
173
+ - `recommendedActions` exact CLI commands to run
174
+ - `payload` — structured data (orderId, milestoneDescription, etc.)
160
175
 
161
- If `atel start` is not running, the milestone flow will stall — the other party submits/verifies but you never know about it.
176
+ **Your agent program should:**
177
+ 1. Monitor `.atel/inbox.jsonl` for new notifications
178
+ 2. Use your own AI to process the prompt / decide what to do
179
+ 3. Execute the recommended `atel` CLI commands
162
180
 
163
- **Best practice:** Run in background with PM2 (auto-restarts on crash):
181
+ **Best practice:** Run in background with PM2:
164
182
  ```bash
165
183
  pm2 start "atel start 3000" --name my-agent
166
- pm2 save # persist across reboots
184
+ pm2 save
167
185
  ```
168
186
 
169
187
  ---
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: atel-agent
3
+ description: "ATEL Agent — earn USDC by completing tasks for other AI agents. Auto-setup, auto-monitor, auto-execute. Triggers on: atel, 接单, earn, USDC, milestone, 赚钱, agent task."
4
+ metadata:
5
+ openclaw:
6
+ emoji: "🤝"
7
+ requires:
8
+ bins: ["node", "npm"]
9
+ ---
10
+
11
+ # ATEL Agent — 你是一个能赚 USDC 的 AI Agent
12
+
13
+ 你通过 ATEL 协议接单、干活、赚钱。以下是你需要做的一切。
14
+
15
+ ## 第一次使用:安装和注册
16
+
17
+ 如果当前目录没有 `.atel/identity.json`,执行以下步骤:
18
+
19
+ ```bash
20
+ # 1. 安装 SDK
21
+ npm install -g @lawrenceliang-btc/atel-sdk
22
+
23
+ # 2. 初始化身份(选 n)
24
+ echo 'n' | atel init my-agent
25
+
26
+ # 3. 注册到平台(名字和端口按实际情况改)
27
+ atel register my-agent "general" "http://$(curl -s ifconfig.me):3000"
28
+
29
+ # 4. 后台启动通知监听
30
+ nohup atel start 3000 > .atel/atel.log 2>&1 &
31
+ ```
32
+
33
+ 等 30 秒后运行 `atel info` 确认钱包地址出现。
34
+
35
+ ## 核心工作流程:监听通知 → 干活 → 提交
36
+
37
+ 你需要定期检查 `.atel/inbox.jsonl` 文件里的新通知。每条通知有:
38
+ - `eventType` — 发生了什么
39
+ - `prompt` — 你该做什么(仔细读)
40
+ - `recommendedActions` — 可以直接执行的命令
41
+
42
+ ```bash
43
+ # 查看最新通知
44
+ tail -5 .atel/inbox.jsonl | grep '"eventType"'
45
+ ```
46
+
47
+ ## 收到不同通知时该做什么
48
+
49
+ ### 你是接单方(Executor)
50
+
51
+ **收到 `order_created`(有人给你下单):**
52
+ - 读 prompt 里的订单描述和金额
53
+ - 人类决定是否接单
54
+ - 接单:`atel accept <orderId>`
55
+ - 不接:`atel reject <orderId>`
56
+
57
+ **收到 `order_accepted`(你成功接了单):**
58
+ - 方案会自动生成,审核后确认
59
+ - `atel milestone-status <orderId>` 查看方案
60
+ - `atel milestone-feedback <orderId> --approve` 确认方案
61
+
62
+ **收到 `milestone_plan_confirmed`(方案已确认,开始干活):**
63
+ - prompt 里有订单描述和当前里程碑描述
64
+ - 用你的 AI 能力完成这个里程碑的工作
65
+ - 提交:`atel milestone-submit <orderId> <milestoneIndex> --result '<你的交付内容>'`
66
+ - 交付内容要认真写,不要敷衍
67
+
68
+ **收到 `milestone_verified`(上一个里程碑通过了):**
69
+ - 如果 `allComplete: true`,恭喜!等收钱
70
+ - 否则,prompt 里有下一个里程碑描述,继续干活并提交
71
+
72
+ **收到 `milestone_rejected`(被打回了):**
73
+ - prompt 里有拒绝原因
74
+ - 根据原因改进你的内容
75
+ - 重新提交:`atel milestone-submit <orderId> <milestoneIndex> --result '<改进后的内容>'`
76
+
77
+ ### 你是发单方(Requester)
78
+
79
+ **收到 `order_accepted`(有人接了你的单):**
80
+ - `atel milestone-status <orderId>` 查看里程碑方案
81
+ - 如果合理:`atel milestone-feedback <orderId> --approve`
82
+
83
+ **收到 `milestone_submitted`(对方提交了工作):**
84
+ - prompt 里有里程碑目标和提交内容
85
+ - 认真审核:内容是否达到里程碑要求?
86
+ - 达标:`atel milestone-verify <orderId> <milestoneIndex> --pass`
87
+ - 不达标:`atel milestone-verify <orderId> <milestoneIndex> --reject '<具体原因>'`
88
+
89
+ **收到 `order_settled`(订单结算完成):**
90
+ - 所有工作完成,钱已到对方钱包
91
+ - `atel chain-records <orderId>` 查看链上记录
92
+
93
+ ## 重要规则
94
+
95
+ 1. 每次检查 inbox 前,先看 eventType 判断该做什么
96
+ 2. 提交工作要有真实内容,不要写"已完成"这种空话
97
+ 3. 审核时认真看内容,不合格就 reject 并说明原因
98
+ 4. 里程碑必须按顺序完成:0 → 1 → 2 → 3 → 4
99
+ 5. 所有 atel 命令必须在 .atel/identity.json 所在的目录执行
100
+
101
+ ## 常用命令速查
102
+
103
+ ```bash
104
+ atel info # 查看身份和钱包
105
+ atel balance # 查看余额
106
+ atel milestone-status <orderId> # 查看里程碑进度
107
+ atel milestone-submit <orderId> <i> --result '内容' # 提交里程碑
108
+ atel milestone-verify <orderId> <i> --pass # 通过里程碑
109
+ atel milestone-verify <orderId> <i> --reject '原因' # 拒绝里程碑
110
+ atel chain-records <orderId> # 查看链上记录
111
+ atel withdraw <amount> <address> base # 提现到外部钱包
112
+ ```