openclaw-overlay-plugin 0.8.2 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.js +137 -315
- package/dist/src/cli.js +1 -1
- package/dist/src/scripts/baemail/commands.d.ts +6 -6
- package/dist/src/scripts/messaging/inbox.d.ts +2 -2
- package/dist/src/scripts/messaging/poll.d.ts +1 -1
- package/dist/src/scripts/messaging/send.d.ts +1 -1
- package/dist/src/scripts/output.d.ts +5 -4
- package/dist/src/scripts/output.js +11 -2
- package/dist/src/scripts/overlay/advertisement.d.ts +2 -2
- package/dist/src/scripts/overlay/discover.d.ts +1 -1
- package/dist/src/scripts/overlay/registration.d.ts +2 -2
- package/dist/src/scripts/overlay/services.d.ts +4 -4
- package/dist/src/scripts/payment/commands.d.ts +3 -3
- package/dist/src/scripts/services/queue.d.ts +2 -2
- package/dist/src/scripts/services/request.d.ts +1 -1
- package/dist/src/scripts/services/respond.d.ts +2 -2
- package/dist/src/scripts/wallet/balance.d.ts +2 -2
- package/dist/src/scripts/wallet/setup.d.ts +4 -4
- package/dist/src/scripts/x-verification/commands.d.ts +6 -6
- package/index.ts +178 -383
- package/openclaw.plugin.json +4 -4
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/scripts/baemail/commands.ts +6 -6
- package/src/scripts/messaging/inbox.ts +2 -2
- package/src/scripts/messaging/poll.ts +1 -1
- package/src/scripts/messaging/send.ts +1 -1
- package/src/scripts/output.ts +13 -4
- package/src/scripts/overlay/advertisement.ts +2 -2
- package/src/scripts/overlay/discover.ts +1 -1
- package/src/scripts/overlay/registration.ts +2 -2
- package/src/scripts/overlay/services.ts +4 -4
- package/src/scripts/payment/commands.ts +3 -3
- package/src/scripts/services/queue.ts +2 -2
- package/src/scripts/services/request.ts +1 -1
- package/src/scripts/services/respond.ts +2 -2
- package/src/scripts/wallet/balance.ts +2 -2
- package/src/scripts/wallet/setup.ts +4 -4
- package/src/scripts/x-verification/commands.ts +6 -6
package/index.ts
CHANGED
|
@@ -1,46 +1,22 @@
|
|
|
1
|
-
const cp_name = 'node:child' + '_' + 'process';
|
|
2
|
-
let execFile: any;
|
|
3
|
-
let spawn: any;
|
|
4
|
-
let execFileAsync: any;
|
|
5
|
-
type ChildProcess = any;
|
|
6
|
-
|
|
7
|
-
import { promisify } from 'node:util';
|
|
8
1
|
import path from 'node:path';
|
|
9
2
|
import os from 'node:os';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
3
|
import fs from 'node:fs';
|
|
12
|
-
import process from 'node:process';
|
|
13
|
-
import { Buffer } from 'node:buffer';
|
|
14
4
|
import { initializeServiceSystem, serviceManager } from './src/services/index.js';
|
|
15
5
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// @ts-ignore
|
|
26
|
-
const cp = require(cp_name);
|
|
27
|
-
execFile = cp.execFile;
|
|
28
|
-
spawn = cp.spawn;
|
|
29
|
-
} else {
|
|
30
|
-
const cp = await import(cp_name as any);
|
|
31
|
-
execFile = cp.execFile;
|
|
32
|
-
spawn = cp.spawn;
|
|
33
|
-
}
|
|
34
|
-
execFileAsync = promisify(execFile);
|
|
35
|
-
}
|
|
6
|
+
// Direct imports of command logic
|
|
7
|
+
import { cmdStatus, cmdSetup, cmdAddress, cmdIdentity } from './src/scripts/wallet/setup.js';
|
|
8
|
+
import { cmdBalance, cmdImport } from './src/scripts/wallet/balance.js';
|
|
9
|
+
import { cmdRegister, cmdUnregister } from './src/scripts/overlay/registration.js';
|
|
10
|
+
import { cmdDiscover } from './src/scripts/overlay/discover.js';
|
|
11
|
+
import { cmdRequestService } from './src/scripts/services/request.js';
|
|
12
|
+
import { cmdServiceQueue } from './src/scripts/services/queue.js';
|
|
13
|
+
import { cmdRespondService } from './src/scripts/services/respond.js';
|
|
14
|
+
import { setNoExit } from './src/scripts/output.js';
|
|
36
15
|
|
|
37
16
|
// Track background process for proper lifecycle management
|
|
38
|
-
let backgroundProcess:
|
|
17
|
+
let backgroundProcess: any = null;
|
|
39
18
|
let serviceRunning = false;
|
|
40
19
|
|
|
41
|
-
// Confirmation tokens for destructive actions — maps token → { action, details, expiresAt }
|
|
42
|
-
const pendingConfirmations: Map<string, { action: string; details: any; expiresAt: number }> = new Map();
|
|
43
|
-
|
|
44
20
|
// Auto-import tracking
|
|
45
21
|
let autoImportInterval: any = null;
|
|
46
22
|
let knownTxids: Set<string> = new Set();
|
|
@@ -52,7 +28,6 @@ let requestCleanupInterval: any = null;
|
|
|
52
28
|
// Budget tracking
|
|
53
29
|
const BUDGET_FILE = 'daily-spending.json';
|
|
54
30
|
|
|
55
|
-
|
|
56
31
|
interface DailySpending {
|
|
57
32
|
date: string; // YYYY-MM-DD
|
|
58
33
|
totalSats: number;
|
|
@@ -70,19 +45,11 @@ function loadDailySpending(walletDir: string): DailySpending {
|
|
|
70
45
|
const data = JSON.parse(fs.readFileSync(budgetPath, 'utf-8'));
|
|
71
46
|
if (data.date === today) return data;
|
|
72
47
|
} catch {
|
|
73
|
-
// Ignore parse errors
|
|
48
|
+
// Ignore parse errors
|
|
74
49
|
}
|
|
75
50
|
return { date: today, totalSats: 0, transactions: [] };
|
|
76
51
|
}
|
|
77
52
|
|
|
78
|
-
function writeActivityEvent(event: any) {
|
|
79
|
-
const alertDir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
|
|
80
|
-
try {
|
|
81
|
-
fs.mkdirSync(alertDir, { recursive: true });
|
|
82
|
-
fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
|
|
83
|
-
} catch {}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
53
|
function recordSpend(walletDir: string, sats: number, service: string, provider: string) {
|
|
87
54
|
const spending = loadDailySpending(walletDir);
|
|
88
55
|
spending.totalSats += sats;
|
|
@@ -100,19 +67,26 @@ function checkBudget(walletDir: string, requestedSats: number, dailyLimit: numbe
|
|
|
100
67
|
};
|
|
101
68
|
}
|
|
102
69
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
70
|
+
function applyConfigToEnv(config: any) {
|
|
71
|
+
(process as any)['env'].BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
|
|
72
|
+
(process as any)['env'].OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
|
|
73
|
+
(process as any)['env'].BSV_NETWORK = config.network || (process as any)['env'].BSV_NETWORK || 'mainnet';
|
|
74
|
+
(process as any)['env'].BSV_ARC_URL = config.arcUrl || ((process as any)['env'].BSV_NETWORK === 'testnet' ? 'https://testnet.arc.gorillapool.io' : 'https://arc.gorillapool.io');
|
|
75
|
+
(process as any)['env'].AGENT_NAME = config.agentName || 'openclaw-agent';
|
|
76
|
+
setNoExit(true);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function startAutoImport(config: any, api: any) {
|
|
106
80
|
try {
|
|
107
|
-
|
|
108
|
-
const addrOutput =
|
|
81
|
+
applyConfigToEnv(config);
|
|
82
|
+
const addrOutput = await cmdAddress();
|
|
109
83
|
if (!addrOutput.success) return;
|
|
110
84
|
const address = addrOutput.data?.address;
|
|
111
85
|
if (!address) return;
|
|
112
86
|
|
|
113
87
|
autoImportInterval = setInterval(async () => {
|
|
114
88
|
try {
|
|
115
|
-
const network = env.BSV_NETWORK === 'testnet' ? 'test' : 'main';
|
|
89
|
+
const network = (process as any)['env'].BSV_NETWORK === 'testnet' ? 'test' : 'main';
|
|
116
90
|
const controller = new AbortController();
|
|
117
91
|
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
118
92
|
const resp = await fetch(`https://api.whatsonchain.com/v1/bsv/${network}/address/${address}/unspent/all`, { signal: controller.signal });
|
|
@@ -124,41 +98,33 @@ async function startAutoImport(env: any, cliPath: string, api: any) {
|
|
|
124
98
|
for (const utxo of utxos) {
|
|
125
99
|
const key = `${utxo.tx_hash}:${utxo.tx_pos}`;
|
|
126
100
|
if (knownTxids.has(key)) continue;
|
|
127
|
-
if (utxo.value < 200) continue;
|
|
101
|
+
if (utxo.value < 200) continue;
|
|
128
102
|
|
|
129
|
-
logger?.info?.(`[openclaw-overlay] Auto-importing UTXO: ${utxo.tx_hash}:${utxo.tx_pos} (${utxo.value} sats)`);
|
|
103
|
+
api.logger?.info?.(`[openclaw-overlay] Auto-importing UTXO: ${utxo.tx_hash}:${utxo.tx_pos} (${utxo.value} sats)`);
|
|
130
104
|
try {
|
|
131
|
-
|
|
132
|
-
const importOutput =
|
|
105
|
+
applyConfigToEnv(config);
|
|
106
|
+
const importOutput = await cmdImport(utxo.tx_hash, String(utxo.tx_pos));
|
|
133
107
|
if (importOutput.success) {
|
|
134
108
|
knownTxids.add(key);
|
|
135
|
-
logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
|
|
109
|
+
api.logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
|
|
136
110
|
|
|
137
|
-
// Clear onboarding flag since wallet is now funded
|
|
138
|
-
try {
|
|
139
|
-
const onboardingSentFile = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
|
|
140
|
-
if (fs.existsSync(onboardingSentFile)) {
|
|
141
|
-
fs.unlinkSync(onboardingSentFile);
|
|
142
|
-
}
|
|
143
|
-
} catch {}
|
|
144
|
-
|
|
145
111
|
// Notify agent of successful import
|
|
146
|
-
wakeAgent(`💰 **Wallet Funded!**\n\nAuto-imported ${utxo.value} sats from transaction ${utxo.tx_hash.slice(0, 16)}...\n\nNotify the user their wallet has been funded.`, api, { sessionKey: 'hook:openclaw-overlay:import' });
|
|
112
|
+
wakeAgent(`💰 **Wallet Funded!**\n\nAuto-imported ${utxo.value} sats from transaction ${utxo.tx_hash.slice(0, 16)}...\n\nNotify the user their wallet has been funded.`, api.logger, { sessionKey: 'hook:openclaw-overlay:import' });
|
|
147
113
|
|
|
148
114
|
// Check if registered, auto-register if not
|
|
149
115
|
try {
|
|
150
|
-
const regPath = path.join((
|
|
116
|
+
const regPath = path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
151
117
|
if (!fs.existsSync(regPath)) {
|
|
152
|
-
logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
|
|
153
|
-
|
|
154
|
-
const regOutput =
|
|
118
|
+
api.logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
|
|
119
|
+
applyConfigToEnv(config);
|
|
120
|
+
const regOutput = await cmdRegister();
|
|
155
121
|
if (regOutput.success) {
|
|
156
|
-
logger?.info?.('[openclaw-overlay] Auto-registered on overlay network!');
|
|
157
|
-
await autoAdvertiseServices(
|
|
122
|
+
api.logger?.info?.('[openclaw-overlay] Auto-registered on overlay network!');
|
|
123
|
+
await autoAdvertiseServices(config, api.logger);
|
|
158
124
|
}
|
|
159
125
|
}
|
|
160
126
|
} catch (err: any) {
|
|
161
|
-
logger?.warn?.('[openclaw-overlay] Auto-registration failed:', err.message);
|
|
127
|
+
api.logger?.warn?.('[openclaw-overlay] Auto-registration failed:', err.message);
|
|
162
128
|
}
|
|
163
129
|
}
|
|
164
130
|
} catch (err) {
|
|
@@ -170,31 +136,16 @@ async function startAutoImport(env: any, cliPath: string, api: any) {
|
|
|
170
136
|
}
|
|
171
137
|
}, 30000);
|
|
172
138
|
} catch (err: any) {
|
|
173
|
-
logger?.warn?.('[openclaw-overlay] Auto-import setup failed:', err.message);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function stopAutoImport() {
|
|
178
|
-
if (autoImportInterval) {
|
|
179
|
-
clearInterval(autoImportInterval);
|
|
180
|
-
autoImportInterval = null;
|
|
139
|
+
api.logger?.warn?.('[openclaw-overlay] Auto-import setup failed:', err.message);
|
|
181
140
|
}
|
|
182
141
|
}
|
|
183
142
|
|
|
184
|
-
|
|
185
|
-
async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
|
|
143
|
+
async function autoAdvertiseServices(config: any, logger: any) {
|
|
186
144
|
try {
|
|
187
|
-
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
188
|
-
if (!fs.existsSync(configPath)) return;
|
|
189
|
-
|
|
190
145
|
let servicesToAdvertise: string[] = [];
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (pluginConfig?.services && Array.isArray(pluginConfig.services)) {
|
|
195
|
-
servicesToAdvertise = pluginConfig.services;
|
|
196
|
-
}
|
|
197
|
-
} catch {}
|
|
146
|
+
if (config?.services && Array.isArray(config.services)) {
|
|
147
|
+
servicesToAdvertise = config.services;
|
|
148
|
+
}
|
|
198
149
|
|
|
199
150
|
if (servicesToAdvertise.length === 0) return;
|
|
200
151
|
|
|
@@ -202,9 +153,9 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
|
|
|
202
153
|
const serviceInfo = serviceManager.registry.get(serviceId);
|
|
203
154
|
if (!serviceInfo) continue;
|
|
204
155
|
try {
|
|
205
|
-
await
|
|
206
|
-
|
|
207
|
-
|
|
156
|
+
const { cmdAdvertise } = await import('./src/scripts/overlay/services.js');
|
|
157
|
+
applyConfigToEnv(config);
|
|
158
|
+
await cmdAdvertise(serviceId, serviceInfo.name, String(serviceInfo.defaultPrice), serviceInfo.description);
|
|
208
159
|
} catch {}
|
|
209
160
|
}
|
|
210
161
|
} catch (err: any) {
|
|
@@ -212,54 +163,20 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
|
|
|
212
163
|
}
|
|
213
164
|
}
|
|
214
165
|
|
|
215
|
-
function wakeAgent(text: string,
|
|
216
|
-
const logger = api.logger;
|
|
166
|
+
function wakeAgent(text: string, logger: any, options: { sessionKey?: string } = {}) {
|
|
217
167
|
const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
|
|
218
168
|
const gatewayPort = (process as any)['env'].OPENCLAW_GATEWAY_PORT || '18789';
|
|
219
|
-
const httpToken =
|
|
169
|
+
const httpToken = (process as any)['env'].OPENCLAW_HOOKS_TOKEN || null;
|
|
220
170
|
if (!httpToken) return;
|
|
221
171
|
|
|
222
172
|
fetch(`http://localhost:${gatewayPort}/hooks/agent`, {
|
|
223
173
|
method: 'POST',
|
|
224
174
|
headers: { 'Content-Type': 'application/json', 'x-openclaw-token': httpToken },
|
|
225
175
|
body: JSON.stringify({ prompt: text, sessionKey })
|
|
226
|
-
}).
|
|
227
|
-
if (!res.ok) {
|
|
228
|
-
const body = await res.text().catch(() => '');
|
|
229
|
-
logger?.warn?.(`[openclaw-overlay] /hooks/agent failed: ${res.status} ${body}`);
|
|
230
|
-
}
|
|
231
|
-
}).catch((err) => {
|
|
232
|
-
logger?.warn?.(`[openclaw-overlay] /hooks/agent error: ${err.message}`);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function getHooksToken(): string | null {
|
|
237
|
-
let token = (process as any)['env'].OPENCLAW_HOOKS_TOKEN || null;
|
|
238
|
-
if (!token) {
|
|
239
|
-
try {
|
|
240
|
-
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
241
|
-
if (fs.existsSync(configPath)) {
|
|
242
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
243
|
-
token = config.gateway?.hooksToken || null;
|
|
244
|
-
}
|
|
245
|
-
} catch {}
|
|
246
|
-
}
|
|
247
|
-
return token;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function categorizeEvent(event: any) {
|
|
251
|
-
const base = { ts: Date.now(), from: event.from?.slice(0, 16), fullFrom: event.from };
|
|
252
|
-
if (event.action === 'queued-for-agent' && event.satoshisReceived) {
|
|
253
|
-
return { ...base, type: 'incoming_payment', emoji: '💰', serviceId: event.serviceId, sats: event.satoshisReceived, requestId: event.id, message: `Received ${event.satoshisReceived} sats for ${event.serviceId}` };
|
|
254
|
-
}
|
|
255
|
-
if (event.type === 'service-response' && event.action === 'received') {
|
|
256
|
-
return { ...base, type: 'response_received', emoji: '📬', serviceId: event.serviceId, status: event.status, result: event.result, requestId: event.requestId, message: event.formatted || `Response received for ${event.serviceId}: ${event.status}` };
|
|
257
|
-
}
|
|
258
|
-
return null;
|
|
176
|
+
}).catch(() => {});
|
|
259
177
|
}
|
|
260
178
|
|
|
261
|
-
function startBackgroundService(
|
|
262
|
-
const logger = api.logger;
|
|
179
|
+
function startBackgroundService(config: any, api: any) {
|
|
263
180
|
if (backgroundProcess) return;
|
|
264
181
|
serviceRunning = true;
|
|
265
182
|
|
|
@@ -267,8 +184,17 @@ function startBackgroundService(env: any, cliPath: string, api: any) {
|
|
|
267
184
|
if (serviceRunning) wokenRequests.clear();
|
|
268
185
|
}, 5 * 60 * 1000);
|
|
269
186
|
|
|
270
|
-
function spawnConnect() {
|
|
187
|
+
async function spawnConnect() {
|
|
271
188
|
if (!serviceRunning) return;
|
|
189
|
+
const { spawn } = await import('node:child_process');
|
|
190
|
+
const base = __dirname.endsWith('dist') ? __dirname : path.join(__dirname, 'dist');
|
|
191
|
+
const cliPath = path.join(base, 'src', 'cli.js');
|
|
192
|
+
|
|
193
|
+
const env = { ...(process as any)['env'] };
|
|
194
|
+
env.BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
|
|
195
|
+
env.OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
|
|
196
|
+
env.BSV_NETWORK = config.network || env.BSV_NETWORK || 'mainnet';
|
|
197
|
+
|
|
272
198
|
const proc = spawn('node', [cliPath, 'connect'], { env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
273
199
|
backgroundProcess = proc;
|
|
274
200
|
|
|
@@ -281,43 +207,18 @@ function startBackgroundService(env: any, cliPath: string, api: any) {
|
|
|
281
207
|
const rid = event.id || `${event.from}-${Date.now()}`;
|
|
282
208
|
if (wokenRequests.has(rid)) return;
|
|
283
209
|
wokenRequests.add(rid);
|
|
284
|
-
|
|
285
|
-
logger?.info?.(`[openclaw-overlay] ⚡ Incoming ${event.serviceId} request from ${event.from?.slice(0, 12)}...`);
|
|
286
|
-
|
|
287
|
-
if (api.runtime?.taskFlow) {
|
|
288
|
-
api.runtime.taskFlow.create({
|
|
289
|
-
goal: `Fulfill overlay service request: ${event.serviceId}`,
|
|
290
|
-
status: "queued"
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
|
|
294
210
|
const wakeText = `⚡ Incoming overlay service request!\n\nService: ${event.serviceId}\nFrom: ${event.from}\nPaid: ${event.satoshisReceived || '?'} sats\n\nFulfill it now:\n1. overlay({ action: "pending-requests" })\n2. Process the request\n3. overlay({ action: "fulfill", requestId: "${event.id}", recipientKey: "${event.from}", serviceId: "${event.serviceId}", result: { ... } })`;
|
|
295
|
-
wakeAgent(wakeText, api, { sessionKey: `hook:openclaw-overlay:${rid}` });
|
|
211
|
+
wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:${rid}` });
|
|
296
212
|
}
|
|
297
213
|
if (event.type === 'service-response' && event.action === 'received') {
|
|
298
|
-
logger?.info?.(`[openclaw-overlay] 📬 Response received for ${event.serviceId} from ${event.from?.slice(0, 12)}...`);
|
|
299
|
-
|
|
300
|
-
if (api.runtime?.taskFlow) {
|
|
301
|
-
api.runtime.taskFlow.create({
|
|
302
|
-
goal: `Notify user of overlay service response: ${event.serviceId}`,
|
|
303
|
-
status: "done"
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
214
|
const wakeText = `📬 Overlay service response received!\n\nService: ${event.serviceId}\nFrom: ${event.from}\nStatus: ${event.status}\n\nFull result:\n${JSON.stringify(event.result, null, 2)}`;
|
|
308
|
-
wakeAgent(wakeText, api, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
|
|
309
|
-
}
|
|
310
|
-
const notif = categorizeEvent(event);
|
|
311
|
-
if (notif) {
|
|
312
|
-
const dir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
|
|
313
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
314
|
-
fs.appendFileSync(path.join(dir, 'activity-feed.jsonl'), JSON.stringify(notif) + '\n');
|
|
215
|
+
wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
|
|
315
216
|
}
|
|
316
217
|
} catch {}
|
|
317
218
|
}
|
|
318
219
|
});
|
|
319
220
|
|
|
320
|
-
proc.on('exit', (
|
|
221
|
+
proc.on('exit', () => {
|
|
321
222
|
backgroundProcess = null;
|
|
322
223
|
if (serviceRunning) setTimeout(spawnConnect, 5000);
|
|
323
224
|
});
|
|
@@ -330,251 +231,145 @@ function stopBackgroundService() {
|
|
|
330
231
|
if (backgroundProcess) { backgroundProcess.kill(); backgroundProcess = null; }
|
|
331
232
|
if (requestCleanupInterval) { clearInterval(requestCleanupInterval); requestCleanupInterval = null; }
|
|
332
233
|
wokenRequests.clear();
|
|
333
|
-
|
|
234
|
+
if (autoImportInterval) { clearInterval(autoImportInterval); autoImportInterval = null; }
|
|
334
235
|
}
|
|
335
236
|
|
|
336
|
-
function getCliPath() {
|
|
337
|
-
const base = __dirname.endsWith('dist') ? __dirname : path.join(__dirname, 'dist');
|
|
338
|
-
return path.join(base, 'src', 'cli.js');
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* OpenClaw Overlay Plugin
|
|
343
|
-
* Decentralized agent marketplace with BSV micropayments.
|
|
344
|
-
*/
|
|
345
237
|
export function register(api: any) {
|
|
346
|
-
if (isInitialized) return;
|
|
347
|
-
isInitialized = true;
|
|
348
|
-
|
|
349
238
|
const entries = api.getConfig?.()?.plugins?.entries || {};
|
|
350
|
-
const entry = entries['
|
|
239
|
+
const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
|
|
351
240
|
const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
|
|
352
241
|
|
|
353
242
|
// 1. Tool
|
|
354
243
|
api.registerTool({
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
serviceId: { type: "string" },
|
|
370
|
-
result: { type: "object" }
|
|
371
|
-
},
|
|
372
|
-
required: ["action"]
|
|
244
|
+
name: "overlay",
|
|
245
|
+
description: "Access the BSV agent marketplace",
|
|
246
|
+
parameters: {
|
|
247
|
+
type: "object",
|
|
248
|
+
properties: {
|
|
249
|
+
action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
|
|
250
|
+
service: { type: "string" },
|
|
251
|
+
input: { type: "object" },
|
|
252
|
+
identityKey: { type: "string" },
|
|
253
|
+
sats: { type: "number" },
|
|
254
|
+
requestId: { type: "string" },
|
|
255
|
+
recipientKey: { type: "string" },
|
|
256
|
+
serviceId: { type: "string" },
|
|
257
|
+
result: { type: "object" }
|
|
373
258
|
},
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
259
|
+
required: ["action"]
|
|
260
|
+
},
|
|
261
|
+
async execute(_id: string, params: any) {
|
|
262
|
+
try {
|
|
263
|
+
return await executeOverlayAction(params, pluginConfig, api);
|
|
264
|
+
} catch (error: any) {
|
|
265
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
380
266
|
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// 2. Command
|
|
384
|
-
api.registerCommand({
|
|
385
|
-
name: "sv_overlay",
|
|
386
|
-
description: "BSV Overlay Marketplace commands",
|
|
387
|
-
acceptsArgs: true,
|
|
388
|
-
requireAuth: true,
|
|
389
|
-
handler: async (ctx: any) => {
|
|
390
|
-
try {
|
|
391
|
-
const action = ctx.args?.[0] || 'status';
|
|
392
|
-
|
|
393
|
-
if (action === 'help') {
|
|
394
|
-
return { text: `🛰️ **Overlay Help**\n\n**Subcommands**:\n- \`status\`: Show identity and wallet balance\n- \`balance\`: Show current satoshis\n- \`onboard\`: Start discovery setup\n- \`discover <serviceId>\`: Find providers on network\n- \`advertise-ship <domain> <topic>\`: Advertise a topic manager\n- \`advertise-slap <domain> <service>\`: Advertise a lookup service\n- \`pending-requests\`: See incoming service jobs\n- \`fulfill\`: Complete a request (Agent only)` };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const params: any = { action };
|
|
398
|
-
if (action === 'discover') {
|
|
399
|
-
params.service = ctx.args[1];
|
|
400
|
-
} else if (action === 'advertise-ship') {
|
|
401
|
-
params.domain = ctx.args[1];
|
|
402
|
-
params.topic = ctx.args[2];
|
|
403
|
-
} else if (action === 'advertise-slap') {
|
|
404
|
-
params.domain = ctx.args[1];
|
|
405
|
-
params.service = ctx.args[2];
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const result = await executeOverlayAction(params, pluginConfig, api);
|
|
409
|
-
|
|
410
|
-
if (typeof result === 'string') return { text: result };
|
|
411
|
-
|
|
412
|
-
// Formatted status response
|
|
413
|
-
if (action === 'status') {
|
|
414
|
-
const status = result as any;
|
|
415
|
-
return { text: `🛰️ **Overlay Status**\n\n**Identity Key**: \`${status.identity?.identityKey || 'Not setup'}\`\n**Balance**: ${status.balance?.walletBalance || 0} satoshis\n**Network**: ${status.identity?.network || 'mainnet'}` };
|
|
416
|
-
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
417
269
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
270
|
+
// 2. Command
|
|
271
|
+
api.registerCommand({
|
|
272
|
+
name: "overlay",
|
|
273
|
+
description: "BSV Overlay Marketplace commands",
|
|
274
|
+
acceptsArgs: true,
|
|
275
|
+
handler: async (ctx: any) => {
|
|
276
|
+
try {
|
|
277
|
+
const action = ctx.args?.[0] || 'status';
|
|
278
|
+
const result = await executeOverlayAction({ action }, pluginConfig, api);
|
|
279
|
+
return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
|
|
280
|
+
} catch (error: any) {
|
|
281
|
+
return { text: `❌ Error: ${error.message}` };
|
|
422
282
|
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// 3. Service
|
|
426
|
-
api.registerService({
|
|
427
|
-
id: "overlay-relay",
|
|
428
|
-
start: async () => {
|
|
429
|
-
// Initialize child process helpers
|
|
430
|
-
await ensureCp();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
431
285
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
286
|
+
// 3. Service
|
|
287
|
+
api.registerService({
|
|
288
|
+
id: "openclaw-overlay-relay",
|
|
289
|
+
start: async () => {
|
|
290
|
+
try { await initializeServiceSystem(); } catch {}
|
|
291
|
+
startBackgroundService(pluginConfig, api);
|
|
292
|
+
startAutoImport(pluginConfig, api);
|
|
293
|
+
},
|
|
294
|
+
stop: () => stopBackgroundService()
|
|
295
|
+
});
|
|
438
296
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
297
|
+
// 4. CLI
|
|
298
|
+
api.registerCli(({ program }: any) => {
|
|
299
|
+
const overlay = program.command("overlay").description("BSV Overlay Network management");
|
|
300
|
+
overlay.command("status").description("Show identity and balance").action(async () => {
|
|
301
|
+
applyConfigToEnv(pluginConfig);
|
|
302
|
+
const res = await cmdStatus();
|
|
303
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
445
304
|
});
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}, { descriptors: [{ name: "overlay", description: "BSV Overlay Network management" }] });
|
|
305
|
+
overlay.command("balance").description("Show current wallet balance").action(async () => {
|
|
306
|
+
applyConfigToEnv(pluginConfig);
|
|
307
|
+
const res = await cmdBalance();
|
|
308
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
309
|
+
});
|
|
310
|
+
overlay.command("discover").description("Find agents and services").option("-s, --service <type>", "Filter by service type").option("-a, --agent <name>", "Filter by agent name").action(async (options: any) => {
|
|
311
|
+
applyConfigToEnv(pluginConfig);
|
|
312
|
+
const args: string[] = [];
|
|
313
|
+
if (options.service) args.push('--service', options.service);
|
|
314
|
+
if (options.agent) args.push('--agent', options.agent);
|
|
315
|
+
const res = await cmdDiscover(args);
|
|
316
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
317
|
+
});
|
|
318
|
+
}, { commands: ["overlay"] });
|
|
461
319
|
}
|
|
462
320
|
|
|
463
|
-
export const plugin = {
|
|
464
|
-
id: "sv_overlay",
|
|
465
|
-
name: "BSV Overlay Network",
|
|
466
|
-
description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
|
|
467
|
-
activate: register,
|
|
468
|
-
register: register
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
export default register;
|
|
472
|
-
|
|
473
321
|
async function executeOverlayAction(params: any, config: any, api: any) {
|
|
474
|
-
await ensureCp();
|
|
475
322
|
const { action } = params;
|
|
476
|
-
|
|
477
|
-
const cliPath = getCliPath();
|
|
323
|
+
applyConfigToEnv(config);
|
|
478
324
|
|
|
479
325
|
switch (action) {
|
|
480
|
-
case "request":
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
326
|
+
case "request": {
|
|
327
|
+
const { service, input } = params;
|
|
328
|
+
const walletDir = config?.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
|
|
329
|
+
const discoverOutput = await cmdDiscover(['--service', service]);
|
|
330
|
+
const providers = discoverOutput.data.services;
|
|
331
|
+
if (!providers || providers.length === 0) throw new Error(`No providers found for ${service}`);
|
|
332
|
+
providers.sort((a: any, b: any) => (a.pricing?.amountSats || 0) - (b.pricing?.amountSats || 0));
|
|
333
|
+
const best = providers[0];
|
|
334
|
+
const price = best.pricing?.amountSats || 0;
|
|
335
|
+
const budget = checkBudget(walletDir, price, config.dailyBudgetSats || 5000);
|
|
336
|
+
if (!budget.allowed) throw new Error("Budget exceeded");
|
|
337
|
+
|
|
338
|
+
const output = await cmdRequestService(best.identityKey, service, price.toString(), input ? JSON.stringify(input) : undefined);
|
|
339
|
+
recordSpend(walletDir, price, service, best.name);
|
|
340
|
+
return { status: "sent", requestId: output.data?.messageId, message: `Request sent to ${best.name} for ${price} sats.` };
|
|
341
|
+
}
|
|
342
|
+
case "discover": return (await cmdDiscover(params.service ? ['--service', params.service] : [])).data;
|
|
343
|
+
case "balance": return (await cmdBalance()).data;
|
|
344
|
+
case "status": {
|
|
345
|
+
const identity = await cmdIdentity();
|
|
346
|
+
const balance = await cmdBalance();
|
|
347
|
+
return { identity: identity.data, balance: balance.data };
|
|
348
|
+
}
|
|
349
|
+
case "onboard": {
|
|
350
|
+
await cmdSetup();
|
|
351
|
+
const addr = (await cmdAddress()).data.address;
|
|
352
|
+
const bal = (await cmdBalance()).data.walletBalance;
|
|
353
|
+
if (bal < 1000) return { funded: false, address: addr, message: "Please fund 1000 sats." };
|
|
354
|
+
await cmdRegister();
|
|
355
|
+
return { funded: true, registered: true, message: "Onboarding complete." };
|
|
356
|
+
}
|
|
357
|
+
case "pending-requests": return (await cmdServiceQueue()).data;
|
|
358
|
+
case "fulfill": {
|
|
359
|
+
const { requestId, recipientKey, serviceId, result } = params;
|
|
360
|
+
return (await cmdRespondService(requestId, recipientKey, serviceId, JSON.stringify(result))).data;
|
|
361
|
+
}
|
|
362
|
+
case "unregister": return (await cmdUnregister()).data;
|
|
489
363
|
default: throw new Error(`Unknown action: ${action}`);
|
|
490
364
|
}
|
|
491
365
|
}
|
|
492
366
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
const { domain, service } = params;
|
|
501
|
-
const result = await execFileAsync('node', [cliPath, 'advertise-slap', domain, service], { env });
|
|
502
|
-
return parseCliOutput(result.stdout).data;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
async function handleServiceRequest(params: any, env: any, cliPath: string, config: any, api: any) {
|
|
506
|
-
const { service, identityKey: targetKey, input } = params;
|
|
507
|
-
const walletDir = config?.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
|
|
508
|
-
const discoverResult = await execFileAsync('node', [cliPath, 'discover', '--service', service], { env });
|
|
509
|
-
const discoverOutput = parseCliOutput(discoverResult.stdout);
|
|
510
|
-
const providers = discoverOutput.data.services;
|
|
511
|
-
if (!providers || providers.length === 0) throw new Error(`No providers found for ${service}`);
|
|
512
|
-
providers.sort((a: any, b: any) => (a.pricing?.amountSats || 0) - (b.pricing?.amountSats || 0));
|
|
513
|
-
const best = providers[0];
|
|
514
|
-
const price = best.pricing?.amountSats || 0;
|
|
515
|
-
const budget = checkBudget(walletDir, price, config.dailyBudgetSats || 5000);
|
|
516
|
-
if (!budget.allowed) throw new Error("Budget exceeded");
|
|
517
|
-
const requestArgs = [cliPath, 'request-service', best.identityKey, service, price.toString()];
|
|
518
|
-
if (input) requestArgs.push(JSON.stringify(input));
|
|
519
|
-
const res = await execFileAsync('node', requestArgs, { env });
|
|
520
|
-
const output = parseCliOutput(res.stdout);
|
|
521
|
-
recordSpend(walletDir, price, service, best.name);
|
|
522
|
-
return { status: "sent", requestId: output.data?.messageId, message: `Request sent to ${best.name} for ${price} sats.` };
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
async function handleDiscover(params: any, env: any, cliPath: string) {
|
|
526
|
-
const args = [cliPath, 'discover'];
|
|
527
|
-
if (params.service) args.push('--service', params.service);
|
|
528
|
-
const result = await execFileAsync('node', args, { env });
|
|
529
|
-
return parseCliOutput(result.stdout).data;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
async function handleBalance(env: any, cliPath: string) {
|
|
533
|
-
const result = await execFileAsync('node', [cliPath, 'balance'], { env });
|
|
534
|
-
return parseCliOutput(result.stdout).data;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
async function handleStatus(env: any, cliPath: string) {
|
|
538
|
-
const identity = parseCliOutput((await execFileAsync('node', [cliPath, 'identity'], { env })).stdout);
|
|
539
|
-
const balance = parseCliOutput((await execFileAsync('node', [cliPath, 'balance'], { env })).stdout);
|
|
540
|
-
return { identity: identity.data, balance: balance.data };
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
async function handleOnboard(params: any, env: any, cliPath: string) {
|
|
544
|
-
await execFileAsync('node', [cliPath, 'setup'], { env });
|
|
545
|
-
const addr = parseCliOutput((await execFileAsync('node', [cliPath, 'address'], { env })).stdout).data.address;
|
|
546
|
-
const bal = parseCliOutput((await execFileAsync('node', [cliPath, 'balance'], { env })).stdout).data.walletBalance;
|
|
547
|
-
if (bal < 1000) return { funded: false, address: addr, message: "Please fund 1000 sats." };
|
|
548
|
-
await execFileAsync('node', [cliPath, 'register'], { env });
|
|
549
|
-
return { funded: true, registered: true, message: "Onboarding complete." };
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
async function handlePendingRequests(env: any, cliPath: string) {
|
|
553
|
-
const result = await execFileAsync('node', [cliPath, 'service-queue'], { env });
|
|
554
|
-
return parseCliOutput(result.stdout).data;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
async function handleFulfill(params: any, env: any, cliPath: string) {
|
|
558
|
-
const { requestId, recipientKey, serviceId, result } = params;
|
|
559
|
-
const res = await execFileAsync('node', [cliPath, 'respond-service', requestId, recipientKey, serviceId, JSON.stringify(result)], { env });
|
|
560
|
-
return parseCliOutput(res.stdout).data;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function buildEnvironment(config: any) {
|
|
564
|
-
const env = { ...(process as any)['env'] };
|
|
565
|
-
env.BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
|
|
566
|
-
env.OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
|
|
567
|
-
env.BSV_NETWORK = config.network || env.BSV_NETWORK || 'mainnet';
|
|
568
|
-
env.BSV_ARC_URL = config.arcUrl || (env.BSV_NETWORK === 'testnet' ? 'https://testnet.arc.gorillapool.io' : 'https://arc.gorillapool.io');
|
|
569
|
-
env.AGENT_NAME = config.agentName || 'openclaw-agent';
|
|
570
|
-
return env;
|
|
571
|
-
}
|
|
367
|
+
export const plugin = {
|
|
368
|
+
id: "openclaw-overlay-plugin",
|
|
369
|
+
name: "BSV Overlay Network",
|
|
370
|
+
description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
|
|
371
|
+
activate: register,
|
|
372
|
+
register: register
|
|
373
|
+
};
|
|
572
374
|
|
|
573
|
-
|
|
574
|
-
try {
|
|
575
|
-
const str = typeof stdout === 'string' ? stdout : String(stdout || '');
|
|
576
|
-
return JSON.parse(str.trim());
|
|
577
|
-
} catch (error: any) {
|
|
578
|
-
throw new Error(`Failed to parse CLI output: ${error.message}`);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
375
|
+
export default register;
|