@lawreneliang/atel-sdk 0.4.1 → 0.4.3
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 +239 -247
- package/dist/anchor/base.js +1 -1
- package/dist/anchor/bsc.js +1 -1
- package/dist/anchor/evm.js +1 -1
- package/dist/anchor/index.js +1 -1
- package/dist/anchor/mock.js +1 -1
- package/dist/anchor/solana.js +1 -1
- package/dist/collaboration/index.js +1 -1
- package/dist/crypto/index.js +1 -1
- package/dist/endpoint/index.js +1 -1
- package/dist/envelope/index.js +1 -1
- package/dist/gateway/index.js +1 -1
- package/dist/graph/index.js +1 -1
- package/dist/handshake/index.js +1 -1
- package/dist/identity/index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/negotiation/index.js +1 -1
- package/dist/network/index.d.ts +45 -0
- package/dist/network/index.js +1 -0
- package/dist/orchestrator/index.js +1 -1
- package/dist/policy/index.js +1 -1
- package/dist/proof/index.js +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/rollback/index.js +1 -1
- package/dist/score/index.d.ts +79 -37
- package/dist/score/index.js +1 -1
- package/dist/service/index.js +1 -1
- package/dist/service/server.js +1 -1
- package/dist/trace/index.js +1 -1
- package/dist/trust/index.js +1 -1
- package/dist/trust-sync/index.js +1 -1
- package/package.json +2 -1
- package/skill/SKILL.md +113 -44
package/bin/atel.mjs
CHANGED
|
@@ -4,88 +4,86 @@
|
|
|
4
4
|
* ATEL CLI — Command-line interface for ATEL SDK
|
|
5
5
|
*
|
|
6
6
|
* Commands:
|
|
7
|
-
* atel init [name] Create
|
|
8
|
-
* atel info Show
|
|
9
|
-
* atel start [port] Start endpoint
|
|
7
|
+
* atel init [name] Create agent identity + default policy
|
|
8
|
+
* atel info Show identity, capabilities, policy, network
|
|
9
|
+
* atel start [port] Start endpoint (auto network + auto register)
|
|
10
|
+
* atel setup [port] Network setup only (detect IP, UPnP, verify)
|
|
11
|
+
* atel verify Verify port reachability
|
|
10
12
|
* atel inbox [count] Show received messages
|
|
11
13
|
* atel register [name] [caps] [url] Register on public registry
|
|
12
14
|
* atel search <capability> Search registry for agents
|
|
13
15
|
* atel handshake <endpoint> [did] Handshake with a remote agent
|
|
14
16
|
* atel task <endpoint> <json> Delegate a task to a remote agent
|
|
17
|
+
* atel result <taskId> <json> Submit execution result (from executor)
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
20
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
18
21
|
import { resolve } from 'node:path';
|
|
19
22
|
import {
|
|
20
|
-
AgentIdentity,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
HandshakeManager,
|
|
24
|
-
createMessage,
|
|
25
|
-
RegistryClient,
|
|
26
|
-
ExecutionTrace,
|
|
27
|
-
ProofGenerator,
|
|
28
|
-
SolanaAnchorProvider,
|
|
23
|
+
AgentIdentity, AgentEndpoint, AgentClient, HandshakeManager,
|
|
24
|
+
createMessage, RegistryClient, ExecutionTrace, ProofGenerator,
|
|
25
|
+
SolanaAnchorProvider, autoNetworkSetup, discoverPublicIP, verifyPortReachable,
|
|
29
26
|
} from '@lawreneliang/atel-sdk';
|
|
30
27
|
|
|
31
28
|
const ATEL_DIR = resolve(process.env.ATEL_DIR || '.atel');
|
|
32
29
|
const IDENTITY_FILE = resolve(ATEL_DIR, 'identity.json');
|
|
33
30
|
const REGISTRY_URL = process.env.ATEL_REGISTRY || 'http://47.251.8.19:8100';
|
|
34
|
-
const
|
|
31
|
+
const EXECUTOR_URL = process.env.ATEL_EXECUTOR_URL || '';
|
|
35
32
|
const INBOX_FILE = resolve(ATEL_DIR, 'inbox.jsonl');
|
|
33
|
+
const POLICY_FILE = resolve(ATEL_DIR, 'policy.json');
|
|
34
|
+
const TASKS_FILE = resolve(ATEL_DIR, 'tasks.json');
|
|
35
|
+
const NETWORK_FILE = resolve(ATEL_DIR, 'network.json');
|
|
36
|
+
|
|
37
|
+
const DEFAULT_POLICY = { rateLimit: 60, maxPayloadBytes: 1048576, maxConcurrent: 10, allowedDIDs: [], blockedDIDs: [] };
|
|
36
38
|
|
|
37
39
|
// ─── Helpers ─────────────────────────────────────────────────────
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
function ensureDir() { if (!existsSync(ATEL_DIR)) mkdirSync(ATEL_DIR, { recursive: true }); }
|
|
42
|
+
function log(event) { ensureDir(); appendFileSync(INBOX_FILE, JSON.stringify(event) + '\n'); console.log(JSON.stringify(event)); }
|
|
43
|
+
|
|
44
|
+
function saveIdentity(id) { ensureDir(); writeFileSync(IDENTITY_FILE, JSON.stringify({ agent_id: id.agent_id, did: id.did, publicKey: Buffer.from(id.publicKey).toString('hex'), secretKey: Buffer.from(id.secretKey).toString('hex') }, null, 2)); }
|
|
45
|
+
function loadIdentity() { if (!existsSync(IDENTITY_FILE)) return null; const d = JSON.parse(readFileSync(IDENTITY_FILE, 'utf-8')); return new AgentIdentity({ agent_id: d.agent_id, publicKey: Uint8Array.from(Buffer.from(d.publicKey, 'hex')), secretKey: Uint8Array.from(Buffer.from(d.secretKey, 'hex')) }); }
|
|
46
|
+
function requireIdentity() { const id = loadIdentity(); if (!id) { console.error('No identity. Run: atel init'); process.exit(1); } return id; }
|
|
47
|
+
|
|
48
|
+
function loadCapabilities() { const f = resolve(ATEL_DIR, 'capabilities.json'); if (!existsSync(f)) return []; try { return JSON.parse(readFileSync(f, 'utf-8')); } catch { return []; } }
|
|
49
|
+
function saveCapabilities(c) { ensureDir(); writeFileSync(resolve(ATEL_DIR, 'capabilities.json'), JSON.stringify(c, null, 2)); }
|
|
50
|
+
function loadPolicy() { if (!existsSync(POLICY_FILE)) return { ...DEFAULT_POLICY }; try { return { ...DEFAULT_POLICY, ...JSON.parse(readFileSync(POLICY_FILE, 'utf-8')) }; } catch { return { ...DEFAULT_POLICY }; } }
|
|
51
|
+
function savePolicy(p) { ensureDir(); writeFileSync(POLICY_FILE, JSON.stringify(p, null, 2)); }
|
|
52
|
+
function loadTasks() { if (!existsSync(TASKS_FILE)) return {}; try { return JSON.parse(readFileSync(TASKS_FILE, 'utf-8')); } catch { return {}; } }
|
|
53
|
+
function saveTasks(t) { ensureDir(); writeFileSync(TASKS_FILE, JSON.stringify(t, null, 2)); }
|
|
54
|
+
function loadNetwork() { if (!existsSync(NETWORK_FILE)) return null; try { return JSON.parse(readFileSync(NETWORK_FILE, 'utf-8')); } catch { return null; } }
|
|
55
|
+
function saveNetwork(n) { ensureDir(); writeFileSync(NETWORK_FILE, JSON.stringify(n, null, 2)); }
|
|
56
|
+
|
|
57
|
+
// ─── Policy Enforcer ─────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
class PolicyEnforcer {
|
|
60
|
+
constructor(policy) { this.policy = policy; this.requestLog = []; this.activeTasks = 0; }
|
|
61
|
+
check(message) {
|
|
62
|
+
const p = this.policy, from = message.from, size = JSON.stringify(message.payload || {}).length, now = Date.now();
|
|
63
|
+
if (p.blockedDIDs.length > 0 && p.blockedDIDs.includes(from)) return { allowed: false, reason: `DID blocked` };
|
|
64
|
+
if (p.allowedDIDs.length > 0 && !p.allowedDIDs.includes(from)) return { allowed: false, reason: `DID not in allowlist` };
|
|
65
|
+
this.requestLog = this.requestLog.filter(t => now - t < 60000);
|
|
66
|
+
if (this.requestLog.length >= p.rateLimit) return { allowed: false, reason: `Rate limit (${p.rateLimit}/min)` };
|
|
67
|
+
if (size > p.maxPayloadBytes) return { allowed: false, reason: `Payload too large (${size} > ${p.maxPayloadBytes})` };
|
|
68
|
+
if (this.activeTasks >= p.maxConcurrent) return { allowed: false, reason: `Max concurrent (${p.maxConcurrent})` };
|
|
69
|
+
this.requestLog.push(now);
|
|
70
|
+
return { allowed: true };
|
|
51
71
|
}
|
|
72
|
+
taskStarted() { this.activeTasks++; }
|
|
73
|
+
taskFinished() { this.activeTasks = Math.max(0, this.activeTasks - 1); }
|
|
52
74
|
}
|
|
53
75
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (!existsSync(IDENTITY_FILE)) return null;
|
|
66
|
-
const data = JSON.parse(readFileSync(IDENTITY_FILE, 'utf-8'));
|
|
67
|
-
return new AgentIdentity({
|
|
68
|
-
agent_id: data.agent_id,
|
|
69
|
-
publicKey: Uint8Array.from(Buffer.from(data.publicKey, 'hex')),
|
|
70
|
-
secretKey: Uint8Array.from(Buffer.from(data.secretKey, 'hex')),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function requireIdentity() {
|
|
75
|
-
const id = loadIdentity();
|
|
76
|
-
if (!id) { console.error('No identity found. Run: atel init'); process.exit(1); }
|
|
77
|
-
return id;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Load registered capabilities from .atel/capabilities.json
|
|
81
|
-
function loadCapabilities() {
|
|
82
|
-
const capFile = resolve(ATEL_DIR, 'capabilities.json');
|
|
83
|
-
if (!existsSync(capFile)) return [];
|
|
84
|
-
try { return JSON.parse(readFileSync(capFile, 'utf-8')); } catch { return []; }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function saveCapabilities(caps) {
|
|
88
|
-
writeFileSync(resolve(ATEL_DIR, 'capabilities.json'), JSON.stringify(caps, null, 2));
|
|
76
|
+
// ─── On-chain Anchoring ──────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
async function anchorOnChain(traceRoot, metadata) {
|
|
79
|
+
const key = process.env.ATEL_SOLANA_PRIVATE_KEY;
|
|
80
|
+
if (!key) return null;
|
|
81
|
+
try {
|
|
82
|
+
const s = new SolanaAnchorProvider({ rpcUrl: process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com', privateKey: key });
|
|
83
|
+
const r = await s.anchor(traceRoot, metadata);
|
|
84
|
+
log({ event: 'proof_anchored', chain: 'solana', txHash: r.txHash, block: r.blockNumber, trace_root: traceRoot });
|
|
85
|
+
return r;
|
|
86
|
+
} catch (e) { log({ event: 'anchor_failed', chain: 'solana', error: e.message }); return null; }
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
// ─── Commands ────────────────────────────────────────────────────
|
|
@@ -94,22 +92,39 @@ async function cmdInit(agentId) {
|
|
|
94
92
|
const name = agentId || `agent-${Date.now()}`;
|
|
95
93
|
const identity = new AgentIdentity({ agent_id: name });
|
|
96
94
|
saveIdentity(identity);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
agent_id: identity.agent_id,
|
|
100
|
-
did: identity.did,
|
|
101
|
-
next: 'Run: atel register [name] [capabilities] [endpoint]',
|
|
102
|
-
}, null, 2));
|
|
95
|
+
savePolicy(DEFAULT_POLICY);
|
|
96
|
+
console.log(JSON.stringify({ status: 'created', agent_id: identity.agent_id, did: identity.did, policy: POLICY_FILE, next: 'Run: atel start [port] — auto-configures network and registers' }, null, 2));
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
async function cmdInfo() {
|
|
106
100
|
const id = requireIdentity();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
console.log(JSON.stringify({ agent_id: id.agent_id, did: id.did, capabilities: loadCapabilities(), policy: loadPolicy(), network: loadNetwork(), executor: EXECUTOR_URL || 'not configured' }, null, 2));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function cmdSetup(port) {
|
|
105
|
+
const p = parseInt(port || '3100');
|
|
106
|
+
console.log(JSON.stringify({ event: 'network_setup', port: p }));
|
|
107
|
+
const net = await autoNetworkSetup(p);
|
|
108
|
+
for (const step of net.steps) console.log(JSON.stringify({ event: 'step', message: step }));
|
|
109
|
+
if (net.endpoint) {
|
|
110
|
+
saveNetwork({ publicIP: net.publicIP, port: p, endpoint: net.endpoint, upnp: net.upnpSuccess, reachable: net.reachable, configuredAt: new Date().toISOString() });
|
|
111
|
+
console.log(JSON.stringify({ status: 'ready', endpoint: net.endpoint }));
|
|
112
|
+
} else if (net.publicIP) {
|
|
113
|
+
const ep = `http://${net.publicIP}:${p}`;
|
|
114
|
+
saveNetwork({ publicIP: net.publicIP, port: p, endpoint: ep, upnp: false, reachable: false, needsManualPortForward: true, configuredAt: new Date().toISOString() });
|
|
115
|
+
console.log(JSON.stringify({ status: 'needs_port_forward', publicIP: net.publicIP, port: p, instruction: `Forward external TCP port ${p} to this machine's port ${p} on your router. Then run: atel verify` }));
|
|
116
|
+
} else {
|
|
117
|
+
console.log(JSON.stringify({ status: 'failed', error: 'Could not determine public IP' }));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function cmdVerify() {
|
|
122
|
+
const net = loadNetwork();
|
|
123
|
+
if (!net) { console.error('No network config. Run: atel setup'); process.exit(1); }
|
|
124
|
+
console.log(JSON.stringify({ event: 'verifying', ip: net.publicIP, port: net.port }));
|
|
125
|
+
const result = await verifyPortReachable(net.publicIP, net.port);
|
|
126
|
+
console.log(JSON.stringify({ status: result.reachable ? 'reachable' : 'not_reachable', detail: result.detail }));
|
|
127
|
+
if (result.reachable) { net.reachable = true; net.needsManualPortForward = false; saveNetwork(net); }
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
async function cmdStart(port) {
|
|
@@ -117,138 +132,130 @@ async function cmdStart(port) {
|
|
|
117
132
|
const p = parseInt(port || '3100');
|
|
118
133
|
const caps = loadCapabilities();
|
|
119
134
|
const capTypes = caps.map(c => c.type || c);
|
|
135
|
+
const policy = loadPolicy();
|
|
136
|
+
const enforcer = new PolicyEnforcer(policy);
|
|
137
|
+
const pendingTasks = loadTasks();
|
|
138
|
+
|
|
139
|
+
// ── Network: determine public endpoint ──
|
|
140
|
+
let publicEndpoint = null;
|
|
141
|
+
let net = loadNetwork();
|
|
142
|
+
if (!net) {
|
|
143
|
+
// Auto network setup
|
|
144
|
+
log({ event: 'network_setup', status: 'auto-detecting' });
|
|
145
|
+
const result = await autoNetworkSetup(p);
|
|
146
|
+
for (const step of result.steps) log({ event: 'network_step', message: step });
|
|
147
|
+
if (result.publicIP) {
|
|
148
|
+
publicEndpoint = result.endpoint || `http://${result.publicIP}:${p}`;
|
|
149
|
+
saveNetwork({ publicIP: result.publicIP, port: p, endpoint: publicEndpoint, upnp: result.upnpSuccess, reachable: result.reachable, configuredAt: new Date().toISOString() });
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
publicEndpoint = net.endpoint;
|
|
153
|
+
log({ event: 'network_loaded', endpoint: publicEndpoint });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Start endpoint ──
|
|
120
157
|
const endpoint = new AgentEndpoint(id, { port: p, host: '0.0.0.0' });
|
|
121
158
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
159
|
+
// Result callback: POST /atel/v1/result (executor calls this when done)
|
|
160
|
+
endpoint.app?.post?.('/atel/v1/result', async (req, res) => {
|
|
161
|
+
const { taskId, result, success } = req.body || {};
|
|
162
|
+
if (!taskId || !pendingTasks[taskId]) { res.status(404).json({ error: 'Unknown taskId' }); return; }
|
|
163
|
+
const task = pendingTasks[taskId];
|
|
164
|
+
enforcer.taskFinished();
|
|
125
165
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
timestamp: new Date().toISOString(),
|
|
133
|
-
};
|
|
134
|
-
console.log(JSON.stringify(taskEvent));
|
|
135
|
-
await notify(taskEvent);
|
|
136
|
-
|
|
137
|
-
// ── Capability boundary check ──
|
|
138
|
-
if (capTypes.length > 0 && !capTypes.includes(action) && !capTypes.includes('general')) {
|
|
139
|
-
const rejectEvent = {
|
|
140
|
-
event: 'task_rejected',
|
|
141
|
-
from: message.from,
|
|
142
|
-
action,
|
|
143
|
-
reason: `Action "${action}" is outside my capability boundary. My capabilities: [${capTypes.join(', ')}]`,
|
|
144
|
-
timestamp: new Date().toISOString(),
|
|
145
|
-
};
|
|
146
|
-
console.log(JSON.stringify(rejectEvent));
|
|
147
|
-
await notify(rejectEvent);
|
|
148
|
-
return {
|
|
149
|
-
status: 'rejected',
|
|
150
|
-
error: rejectEvent.reason,
|
|
151
|
-
capabilities: capTypes,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
166
|
+
const trace = new ExecutionTrace(taskId, id);
|
|
167
|
+
trace.append('TASK_ACCEPTED', { from: task.from, action: task.action });
|
|
168
|
+
trace.append('TASK_FORWARDED', { executor_url: EXECUTOR_URL });
|
|
169
|
+
trace.append('TASK_RESULT', { success: success !== false, result });
|
|
170
|
+
if (success !== false) trace.finalize(typeof result === 'object' ? result : { result });
|
|
171
|
+
else trace.fail(new Error(result?.error || 'Execution failed'));
|
|
154
172
|
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
173
|
+
const proofGen = new ProofGenerator(trace, id);
|
|
174
|
+
const proof = proofGen.generate(capTypes.join(',') || 'no-policy', `task-from-${task.from}`, JSON.stringify(result));
|
|
175
|
+
const anchor = await anchorOnChain(proof.trace_root, { proof_id: proof.proof_id, task_from: task.from, action: task.action, taskId });
|
|
158
176
|
|
|
159
|
-
|
|
160
|
-
let success = true;
|
|
177
|
+
log({ event: 'task_completed', taskId, from: task.from, action: task.action, success: success !== false, proof_id: proof.proof_id, anchor_tx: anchor?.txHash || null, timestamp: new Date().toISOString() });
|
|
161
178
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
} catch (err) {
|
|
173
|
-
success = false;
|
|
174
|
-
result = { status: 'error', error: err.message };
|
|
175
|
-
trace.fail(err);
|
|
179
|
+
// Push result back to sender
|
|
180
|
+
if (task.senderEndpoint) {
|
|
181
|
+
try {
|
|
182
|
+
const client = new AgentClient(id);
|
|
183
|
+
const hsManager = new HandshakeManager(id);
|
|
184
|
+
await client.handshake(task.senderEndpoint, hsManager, task.from);
|
|
185
|
+
const msg = createMessage({ type: 'task-result', from: id.did, to: task.from, payload: { taskId, status: success !== false ? 'completed' : 'failed', result, proof: { proof_id: proof.proof_id, trace_root: proof.trace_root }, anchor: anchor ? { chain: 'solana', txHash: anchor.txHash } : null }, secretKey: id.secretKey });
|
|
186
|
+
await client.sendTask(task.senderEndpoint, msg, hsManager);
|
|
187
|
+
log({ event: 'result_pushed', taskId, to: task.from });
|
|
188
|
+
} catch (e) { log({ event: 'result_push_failed', taskId, error: e.message }); }
|
|
176
189
|
}
|
|
177
190
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
191
|
+
delete pendingTasks[taskId]; saveTasks(pendingTasks);
|
|
192
|
+
res.json({ status: 'ok', proof_id: proof.proof_id, anchor_tx: anchor?.txHash || null });
|
|
193
|
+
});
|
|
181
194
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const solana = new SolanaAnchorProvider({
|
|
196
|
-
rpcUrl: process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
|
|
197
|
-
privateKey: solanaKey,
|
|
198
|
-
});
|
|
199
|
-
anchor = await solana.anchor(proof.trace_root, {
|
|
200
|
-
proof_id: proof.proof_id,
|
|
201
|
-
task_from: message.from,
|
|
202
|
-
action,
|
|
203
|
-
});
|
|
204
|
-
console.log(JSON.stringify({
|
|
205
|
-
event: 'proof_anchored',
|
|
206
|
-
chain: 'solana',
|
|
207
|
-
txHash: anchor.txHash,
|
|
208
|
-
block: anchor.blockNumber,
|
|
209
|
-
trace_root: proof.trace_root,
|
|
210
|
-
}));
|
|
211
|
-
} catch (e) {
|
|
212
|
-
console.error(JSON.stringify({ event: 'anchor_failed', chain: 'solana', error: e.message }));
|
|
213
|
-
}
|
|
195
|
+
// Task handler
|
|
196
|
+
endpoint.onTask(async (message, session) => {
|
|
197
|
+
const payload = message.payload || {};
|
|
198
|
+
const action = payload.action || payload.type || 'unknown';
|
|
199
|
+
|
|
200
|
+
// Policy check
|
|
201
|
+
const pc = enforcer.check(message);
|
|
202
|
+
if (!pc.allowed) { log({ event: 'task_rejected', from: message.from, action, reason: pc.reason, timestamp: new Date().toISOString() }); return { status: 'rejected', error: pc.reason }; }
|
|
203
|
+
|
|
204
|
+
// Capability check
|
|
205
|
+
if (capTypes.length > 0 && !capTypes.includes(action) && !capTypes.includes('general')) {
|
|
206
|
+
log({ event: 'task_rejected', from: message.from, action, reason: `Outside capability: [${capTypes.join(',')}]`, timestamp: new Date().toISOString() });
|
|
207
|
+
return { status: 'rejected', error: `Action "${action}" outside capability boundary`, capabilities: capTypes };
|
|
214
208
|
}
|
|
215
209
|
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
210
|
+
const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
211
|
+
enforcer.taskStarted();
|
|
212
|
+
|
|
213
|
+
// Lookup sender endpoint for result push-back
|
|
214
|
+
let senderEndpoint = null;
|
|
215
|
+
try { const r = await fetch(`${REGISTRY_URL}/registry/v1/agent/${encodeURIComponent(message.from)}`); if (r.ok) senderEndpoint = (await r.json()).endpoint; } catch {}
|
|
216
|
+
|
|
217
|
+
pendingTasks[taskId] = { from: message.from, action, payload, senderEndpoint, encrypted: !!session?.encrypted, acceptedAt: new Date().toISOString() };
|
|
218
|
+
saveTasks(pendingTasks);
|
|
219
|
+
log({ event: 'task_accepted', taskId, from: message.from, action, encrypted: !!session?.encrypted, timestamp: new Date().toISOString() });
|
|
220
|
+
|
|
221
|
+
// Forward to executor or echo
|
|
222
|
+
if (EXECUTOR_URL) {
|
|
223
|
+
fetch(EXECUTOR_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taskId, from: message.from, action, payload, encrypted: !!session?.encrypted }) }).catch(e => log({ event: 'executor_forward_failed', taskId, error: e.message }));
|
|
224
|
+
return { status: 'accepted', taskId, message: 'Task accepted. Result will be pushed when ready.' };
|
|
225
|
+
} else {
|
|
226
|
+
// Echo mode
|
|
227
|
+
enforcer.taskFinished();
|
|
228
|
+
const trace = new ExecutionTrace(taskId, id);
|
|
229
|
+
trace.append('TASK_ACCEPTED', { from: message.from, action, payload });
|
|
230
|
+
const result = { status: 'no_executor', agent: id.agent_id, action, received_payload: payload };
|
|
231
|
+
trace.append('TASK_ECHO', { result }); trace.finalize(result);
|
|
232
|
+
const proofGen = new ProofGenerator(trace, id);
|
|
233
|
+
const proof = proofGen.generate(capTypes.join(',') || 'no-policy', `task-from-${message.from}`, JSON.stringify(result));
|
|
234
|
+
const anchor = await anchorOnChain(proof.trace_root, { proof_id: proof.proof_id, task_from: message.from, action, taskId });
|
|
235
|
+
delete pendingTasks[taskId]; saveTasks(pendingTasks);
|
|
236
|
+
log({ event: 'task_completed', taskId, from: message.from, action, mode: 'echo', proof_id: proof.proof_id, anchor_tx: anchor?.txHash || null, timestamp: new Date().toISOString() });
|
|
237
|
+
return { status: 'completed', taskId, result, proof, anchor };
|
|
238
|
+
}
|
|
229
239
|
});
|
|
230
240
|
|
|
231
|
-
endpoint.onProof(async (message,
|
|
232
|
-
const proofEvent = {
|
|
233
|
-
event: 'proof_received',
|
|
234
|
-
from: message.from,
|
|
235
|
-
payload: message.payload,
|
|
236
|
-
timestamp: new Date().toISOString(),
|
|
237
|
-
};
|
|
238
|
-
console.log(JSON.stringify(proofEvent));
|
|
239
|
-
await notify(proofEvent);
|
|
240
|
-
});
|
|
241
|
+
endpoint.onProof(async (message) => { log({ event: 'proof_received', from: message.from, payload: message.payload, timestamp: new Date().toISOString() }); });
|
|
241
242
|
|
|
242
243
|
await endpoint.start();
|
|
244
|
+
|
|
245
|
+
// Auto-register to Registry
|
|
246
|
+
if (publicEndpoint && capTypes.length > 0) {
|
|
247
|
+
try {
|
|
248
|
+
const regClient = new RegistryClient({ registryUrl: REGISTRY_URL });
|
|
249
|
+
await regClient.register({ name: id.agent_id, capabilities: caps, endpoint: publicEndpoint }, id);
|
|
250
|
+
log({ event: 'auto_registered', registry: REGISTRY_URL, endpoint: publicEndpoint });
|
|
251
|
+
} catch (e) { log({ event: 'auto_register_failed', error: e.message }); }
|
|
252
|
+
}
|
|
253
|
+
|
|
243
254
|
console.log(JSON.stringify({
|
|
244
|
-
status: 'listening',
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
port: p,
|
|
249
|
-
capabilities: capTypes,
|
|
250
|
-
inbox: INBOX_FILE,
|
|
251
|
-
webhook: WEBHOOK_URL || 'not configured (set ATEL_WEBHOOK)',
|
|
255
|
+
status: 'listening', agent_id: id.agent_id, did: id.did,
|
|
256
|
+
endpoint: publicEndpoint || `http://0.0.0.0:${p}`, port: p, capabilities: capTypes,
|
|
257
|
+
policy: { rateLimit: policy.rateLimit, maxPayloadBytes: policy.maxPayloadBytes, maxConcurrent: policy.maxConcurrent, allowedDIDs: policy.allowedDIDs.length, blockedDIDs: policy.blockedDIDs.length },
|
|
258
|
+
executor: EXECUTOR_URL || 'echo mode', inbox: INBOX_FILE,
|
|
252
259
|
}, null, 2));
|
|
253
260
|
|
|
254
261
|
process.on('SIGINT', async () => { await endpoint.stop(); process.exit(0); });
|
|
@@ -257,10 +264,7 @@ async function cmdStart(port) {
|
|
|
257
264
|
|
|
258
265
|
async function cmdInbox(count) {
|
|
259
266
|
const n = parseInt(count || '20');
|
|
260
|
-
if (!existsSync(INBOX_FILE)) {
|
|
261
|
-
console.log(JSON.stringify({ messages: [], count: 0 }));
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
267
|
+
if (!existsSync(INBOX_FILE)) { console.log(JSON.stringify({ messages: [], count: 0 })); return; }
|
|
264
268
|
const lines = readFileSync(INBOX_FILE, 'utf-8').trim().split('\n').filter(Boolean);
|
|
265
269
|
const messages = lines.slice(-n).map(l => JSON.parse(l));
|
|
266
270
|
console.log(JSON.stringify({ messages, count: messages.length, total: lines.length }, null, 2));
|
|
@@ -268,24 +272,14 @@ async function cmdInbox(count) {
|
|
|
268
272
|
|
|
269
273
|
async function cmdRegister(name, capabilities, endpointUrl) {
|
|
270
274
|
const id = requireIdentity();
|
|
271
|
-
const
|
|
272
|
-
const caps = (capabilities || 'general').split(',').map(c => ({
|
|
273
|
-
type: c.trim(), description: c.trim(),
|
|
274
|
-
}));
|
|
275
|
-
// Save capabilities locally for boundary checking
|
|
275
|
+
const caps = (capabilities || 'general').split(',').map(c => ({ type: c.trim(), description: c.trim() }));
|
|
276
276
|
saveCapabilities(caps);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}, id);
|
|
282
|
-
console.log(JSON.stringify({
|
|
283
|
-
status: 'registered',
|
|
284
|
-
did: entry.did,
|
|
285
|
-
name: entry.name,
|
|
286
|
-
capabilities: caps.map(c => c.type),
|
|
287
|
-
registry: REGISTRY_URL,
|
|
288
|
-
}, null, 2));
|
|
277
|
+
// If no endpoint provided, use saved network config
|
|
278
|
+
let ep = endpointUrl;
|
|
279
|
+
if (!ep) { const net = loadNetwork(); ep = net?.endpoint || 'http://localhost:3100'; }
|
|
280
|
+
const client = new RegistryClient({ registryUrl: REGISTRY_URL });
|
|
281
|
+
const entry = await client.register({ name: name || id.agent_id, capabilities: caps, endpoint: ep }, id);
|
|
282
|
+
console.log(JSON.stringify({ status: 'registered', did: entry.did, name: entry.name, capabilities: caps.map(c => c.type), endpoint: ep, registry: REGISTRY_URL }, null, 2));
|
|
289
283
|
}
|
|
290
284
|
|
|
291
285
|
async function cmdSearch(capability) {
|
|
@@ -299,64 +293,56 @@ async function cmdHandshake(remoteEndpoint, remoteDid) {
|
|
|
299
293
|
const client = new AgentClient(id);
|
|
300
294
|
const hsManager = new HandshakeManager(id);
|
|
301
295
|
let did = remoteDid;
|
|
302
|
-
if (!did) {
|
|
303
|
-
const health = await client.health(remoteEndpoint);
|
|
304
|
-
did = health.did;
|
|
305
|
-
}
|
|
296
|
+
if (!did) { const h = await client.health(remoteEndpoint); did = h.did; }
|
|
306
297
|
const session = await client.handshake(remoteEndpoint, hsManager, did);
|
|
307
|
-
console.log(JSON.stringify({
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
remoteDid: did,
|
|
311
|
-
encrypted: session.encrypted,
|
|
312
|
-
}, null, 2));
|
|
313
|
-
const sessFile = resolve(ATEL_DIR, 'sessions.json');
|
|
314
|
-
let sessions = {};
|
|
315
|
-
if (existsSync(sessFile)) sessions = JSON.parse(readFileSync(sessFile, 'utf-8'));
|
|
298
|
+
console.log(JSON.stringify({ status: 'handshake_complete', sessionId: session.sessionId, remoteDid: did, encrypted: session.encrypted }, null, 2));
|
|
299
|
+
const sf = resolve(ATEL_DIR, 'sessions.json');
|
|
300
|
+
let sessions = {}; if (existsSync(sf)) sessions = JSON.parse(readFileSync(sf, 'utf-8'));
|
|
316
301
|
sessions[remoteEndpoint] = { did, sessionId: session.sessionId, encrypted: session.encrypted };
|
|
317
|
-
writeFileSync(
|
|
302
|
+
writeFileSync(sf, JSON.stringify(sessions, null, 2));
|
|
318
303
|
}
|
|
319
304
|
|
|
320
305
|
async function cmdTask(remoteEndpoint, taskJson) {
|
|
321
306
|
const id = requireIdentity();
|
|
322
307
|
const client = new AgentClient(id);
|
|
323
308
|
const hsManager = new HandshakeManager(id);
|
|
324
|
-
const
|
|
309
|
+
const sf = resolve(ATEL_DIR, 'sessions.json');
|
|
325
310
|
let remoteDid;
|
|
326
|
-
if (existsSync(
|
|
327
|
-
const sessions = JSON.parse(readFileSync(sessFile, 'utf-8'));
|
|
328
|
-
if (sessions[remoteEndpoint]) remoteDid = sessions[remoteEndpoint].did;
|
|
329
|
-
}
|
|
311
|
+
if (existsSync(sf)) { const s = JSON.parse(readFileSync(sf, 'utf-8')); if (s[remoteEndpoint]) remoteDid = s[remoteEndpoint].did; }
|
|
330
312
|
if (!remoteDid) {
|
|
331
|
-
const
|
|
332
|
-
remoteDid = health.did;
|
|
313
|
+
const h = await client.health(remoteEndpoint); remoteDid = h.did;
|
|
333
314
|
const session = await client.handshake(remoteEndpoint, hsManager, remoteDid);
|
|
334
|
-
let sessions = {};
|
|
335
|
-
if (existsSync(sessFile)) sessions = JSON.parse(readFileSync(sessFile, 'utf-8'));
|
|
315
|
+
let sessions = {}; if (existsSync(sf)) sessions = JSON.parse(readFileSync(sf, 'utf-8'));
|
|
336
316
|
sessions[remoteEndpoint] = { did: remoteDid, sessionId: session.sessionId, encrypted: session.encrypted };
|
|
337
|
-
writeFileSync(
|
|
338
|
-
} else {
|
|
339
|
-
await client.handshake(remoteEndpoint, hsManager, remoteDid);
|
|
340
|
-
}
|
|
317
|
+
writeFileSync(sf, JSON.stringify(sessions, null, 2));
|
|
318
|
+
} else { await client.handshake(remoteEndpoint, hsManager, remoteDid); }
|
|
341
319
|
const payload = typeof taskJson === 'string' ? JSON.parse(taskJson) : taskJson;
|
|
342
320
|
const msg = createMessage({ type: 'task', from: id.did, to: remoteDid, payload, secretKey: id.secretKey });
|
|
343
321
|
const result = await client.sendTask(remoteEndpoint, msg, hsManager);
|
|
344
322
|
console.log(JSON.stringify({ status: 'task_sent', remoteDid, result }, null, 2));
|
|
345
323
|
}
|
|
346
324
|
|
|
325
|
+
async function cmdResult(taskId, resultJson) {
|
|
326
|
+
const result = typeof resultJson === 'string' ? JSON.parse(resultJson) : resultJson;
|
|
327
|
+
const resp = await fetch(`http://localhost:${process.env.ATEL_PORT || '3100'}/atel/v1/result`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taskId, result, success: true }) });
|
|
328
|
+
console.log(JSON.stringify(await resp.json(), null, 2));
|
|
329
|
+
}
|
|
330
|
+
|
|
347
331
|
// ─── Main ────────────────────────────────────────────────────────
|
|
348
332
|
|
|
349
333
|
const [,, cmd, ...args] = process.argv;
|
|
350
|
-
|
|
351
334
|
const commands = {
|
|
352
335
|
init: () => cmdInit(args[0]),
|
|
353
336
|
info: () => cmdInfo(),
|
|
337
|
+
setup: () => cmdSetup(args[0]),
|
|
338
|
+
verify: () => cmdVerify(),
|
|
354
339
|
start: () => cmdStart(args[0]),
|
|
355
340
|
inbox: () => cmdInbox(args[0]),
|
|
356
341
|
register: () => cmdRegister(args[0], args[1], args[2]),
|
|
357
342
|
search: () => cmdSearch(args[0]),
|
|
358
343
|
handshake: () => cmdHandshake(args[0], args[1]),
|
|
359
344
|
task: () => cmdTask(args[0], args[1]),
|
|
345
|
+
result: () => cmdResult(args[0], args[1]),
|
|
360
346
|
};
|
|
361
347
|
|
|
362
348
|
if (!cmd || !commands[cmd]) {
|
|
@@ -365,23 +351,29 @@ if (!cmd || !commands[cmd]) {
|
|
|
365
351
|
Usage: atel <command> [args]
|
|
366
352
|
|
|
367
353
|
Commands:
|
|
368
|
-
init [name] Create agent identity
|
|
369
|
-
info Show
|
|
370
|
-
|
|
354
|
+
init [name] Create agent identity + security policy
|
|
355
|
+
info Show identity, capabilities, network, policy
|
|
356
|
+
setup [port] Configure network (detect IP, UPnP, verify)
|
|
357
|
+
verify Verify port reachability
|
|
358
|
+
start [port] Start endpoint (auto network + auto register)
|
|
371
359
|
inbox [count] Show received messages (default: 20)
|
|
372
360
|
register [name] [caps] [endpoint] Register on public registry
|
|
373
361
|
search <capability> Search registry for agents
|
|
374
362
|
handshake <endpoint> [did] Handshake with remote agent
|
|
375
363
|
task <endpoint> <json> Delegate task to remote agent
|
|
364
|
+
result <taskId> <json> Submit execution result (from executor)
|
|
376
365
|
|
|
377
366
|
Environment:
|
|
378
|
-
ATEL_DIR
|
|
379
|
-
ATEL_REGISTRY
|
|
380
|
-
|
|
367
|
+
ATEL_DIR Identity directory (default: .atel)
|
|
368
|
+
ATEL_REGISTRY Registry URL (default: http://47.251.8.19:8100)
|
|
369
|
+
ATEL_EXECUTOR_URL Local executor HTTP endpoint
|
|
370
|
+
ATEL_SOLANA_PRIVATE_KEY Solana key for on-chain anchoring
|
|
371
|
+
ATEL_SOLANA_RPC_URL Solana RPC (default: mainnet-beta)
|
|
372
|
+
|
|
373
|
+
Network: atel start auto-detects public IP, attempts UPnP port mapping,
|
|
374
|
+
and registers to the Registry. If UPnP fails, configure port forwarding
|
|
375
|
+
on your router and run: atel verify`);
|
|
381
376
|
process.exit(cmd ? 1 : 0);
|
|
382
377
|
}
|
|
383
378
|
|
|
384
|
-
commands[cmd]().catch(err => {
|
|
385
|
-
console.error(JSON.stringify({ error: err.message }));
|
|
386
|
-
process.exit(1);
|
|
387
|
-
});
|
|
379
|
+
commands[cmd]().catch(err => { console.error(JSON.stringify({ error: err.message })); process.exit(1); });
|
package/dist/anchor/base.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var _0x483110=_0x4cb5;(function(_0x1fd45e,_0x567a4e){var _0x19d003=_0x4cb5,_0x108dcc=_0x1fd45e();while(!![]){try{var _0x1929b7=parseInt(_0x19d003(0x18b))/0x1+-parseInt(_0x19d003(0x18c))/0x2*(parseInt(_0x19d003(0x197))/0x3)+-parseInt(_0x19d003(0x18f))/0x4+parseInt(_0x19d003(0x194))/0x5*(-parseInt(_0x19d003(0x198))/0x6)+parseInt(_0x19d003(0x196))/0x7*(-parseInt(_0x19d003(0x18d))/0x8)+-parseInt(_0x19d003(0x191))/0x9+parseInt(_0x19d003(0x193))/0xa;if(_0x1929b7===_0x567a4e)break;else _0x108dcc['push'](_0x108dcc['shift']());}catch(_0xd6d414){_0x108dcc['push'](_0x108dcc['shift']());}}}(_0x4f55,0xba4a9));import{EvmAnchorProvider}from'./evm.js';function _0x4cb5(_0x533730,_0x3072ed){_0x533730=_0x533730-0x18b;var _0x4f5540=_0x4f55();var _0x4cb5c6=_0x4f5540[_0x533730];if(_0x4cb5['DnpPFI']===undefined){var _0x709a45=function(_0x1c066a){var _0x33535='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x3fa596='',_0xc3f8db='';for(var _0x165f94=0x0,_0x2b7516,_0x26e591,_0x21cdcd=0x0;_0x26e591=_0x1c066a['charAt'](_0x21cdcd++);~_0x26e591&&(_0x2b7516=_0x165f94%0x4?_0x2b7516*0x40+_0x26e591:_0x26e591,_0x165f94++%0x4)?_0x3fa596+=String['fromCharCode'](0xff&_0x2b7516>>(-0x2*_0x165f94&0x6)):0x0){_0x26e591=_0x33535['indexOf'](_0x26e591);}for(var _0x251e1b=0x0,_0x38bdda=_0x3fa596['length'];_0x251e1b<_0x38bdda;_0x251e1b++){_0xc3f8db+='%'+('00'+_0x3fa596['charCodeAt'](_0x251e1b)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xc3f8db);};_0x4cb5['hrDzih']=_0x709a45,_0x4cb5['kntpEe']={},_0x4cb5['DnpPFI']=!![];}var _0x16c097=_0x4f5540[0x0],_0x2f96b0=_0x533730+_0x16c097,_0x3db64a=_0x4cb5['kntpEe'][_0x2f96b0];return!_0x3db64a?(_0x4cb5c6=_0x4cb5['hrDzih'](_0x4cb5c6),_0x4cb5['kntpEe'][_0x2f96b0]=_0x4cb5c6):_0x4cb5c6=_0x3db64a,_0x4cb5c6;}function _0x4f55(){var _0x2285b9=['nJe5nZeXDKfZrwTT','mtb1uKfQvvO','oeHxqxLzqW','yMfZzq','ndeYntKYogHcCuPtvW','revgqvvmvf9suenFvvjm','mZq5otC4nwnwvwnyBW','qMfZzq','ndq0odiWmZb1EMPlCei','mte4nde1r2r5s3LM','zenuqKW','otG0ode3ne5VCgvjra','nZG3mteZz3H0tKj4','ndjpBvbhzuO'];_0x4f55=function(){return _0x2285b9;};return _0x4f55();}export class BaseAnchorProvider extends EvmAnchorProvider{static [_0x483110(0x190)]='https://mainnet.base.org';constructor(_0x5c6a7f){var _0x4ae5bb=_0x483110,_0x11bae9={};_0x11bae9[_0x4ae5bb(0x195)]=_0x4ae5bb(0x192);var _0x2bc7fe=_0x11bae9,_0x566c4f={};_0x566c4f['rpcUrl']=_0x5c6a7f?.['rpcUrl']??BaseAnchorProvider[_0x4ae5bb(0x190)],_0x566c4f['privateKey']=_0x5c6a7f?.['privateKey'],super(_0x2bc7fe['dCTBL'],_0x4ae5bb(0x18e),_0x566c4f);}}
|