openclaw-overlay-plugin 0.7.65 → 0.7.67
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/dist/index.d.ts +12 -2
- package/dist/index.js +97 -85
- package/dist/src/compatibility.test.d.ts +4 -0
- package/dist/src/compatibility.test.js +52 -0
- package/dist/src/scripts/baemail/commands.d.ts +18 -47
- package/dist/src/scripts/baemail/commands.js +112 -101
- package/dist/src/scripts/baemail/handler.js +2 -2
- package/dist/src/scripts/services/respond.js +1 -1
- package/dist/src/scripts/utils/storage.js +1 -1
- package/dist/src/test/request-response-flow.test.js +8 -7
- package/index.ts +106 -90
- package/openclaw.plugin.json +14 -50
- package/package.json +1 -1
- package/src/compatibility.test.ts +57 -0
- package/src/scripts/baemail/commands.ts +118 -146
- package/src/scripts/baemail/handler.ts +2 -2
- package/src/scripts/services/respond.ts +1 -1
- package/src/scripts/utils/storage.ts +1 -1
- package/src/test/request-response-flow.test.ts +8 -7
|
@@ -1,126 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
-
import
|
|
5
|
+
import process from 'node:process';
|
|
6
6
|
import { ok, fail } from '../output.js';
|
|
7
7
|
import { loadIdentity } from '../wallet/identity.js';
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
// Define paths relative to home directory
|
|
11
|
+
const PATHS = {
|
|
12
|
+
walletIdentity: path.join(os.homedir(), '.openclaw', 'bsv-wallet', 'wallet-identity.json'),
|
|
13
|
+
baemailLog: path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'baemail-deliveries.jsonl'),
|
|
14
|
+
baemailConfig: path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'baemail-config.json'),
|
|
15
|
+
baemailBlocklist: path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'baemail-blocklist.json'),
|
|
16
|
+
};
|
|
10
17
|
/**
|
|
11
|
-
*
|
|
18
|
+
* Log a baemail delivery event.
|
|
12
19
|
*/
|
|
13
|
-
export function
|
|
20
|
+
export function logBaemailDelivery(entry) {
|
|
14
21
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
const dir = path.dirname(PATHS.baemailLog);
|
|
23
|
+
if (!fs.existsSync(dir))
|
|
24
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
25
|
+
fs.appendFileSync(PATHS.baemailLog, JSON.stringify({ ...entry, ts: Date.now() }) + '\n');
|
|
18
26
|
}
|
|
19
|
-
catch
|
|
20
|
-
console.warn(`[baemail] Warning: Could not read config: ${err.message}`);
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Save baemail configuration.
|
|
26
|
-
*/
|
|
27
|
-
export function saveBaemailConfig(config) {
|
|
28
|
-
ensureStateDir();
|
|
29
|
-
fs.writeFileSync(PATHS.baemailConfig, JSON.stringify(config, null, 2));
|
|
27
|
+
catch { }
|
|
30
28
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return fail('Usage: baemail-setup <channel> <standardSats> [prioritySats] [urgentSats]');
|
|
37
|
-
}
|
|
38
|
-
const standard = parseInt(standardStr, 10);
|
|
39
|
-
const priority = priorityStr ? parseInt(priorityStr, 10) : standard * 2;
|
|
40
|
-
const urgent = urgentStr ? parseInt(urgentStr, 10) : standard * 5;
|
|
41
|
-
if (isNaN(standard) || standard < 1) {
|
|
42
|
-
return fail('Standard rate must be a positive integer (sats)');
|
|
43
|
-
}
|
|
44
|
-
if (priority < standard) {
|
|
45
|
-
return fail('Priority rate must be >= standard rate');
|
|
46
|
-
}
|
|
47
|
-
if (urgent < priority) {
|
|
48
|
-
return fail('Urgent rate must be >= priority rate');
|
|
49
|
-
}
|
|
50
|
-
const config = {
|
|
51
|
-
deliveryChannel: channel,
|
|
52
|
-
tiers: { standard, priority, urgent },
|
|
53
|
-
maxMessageLength: 4000,
|
|
29
|
+
export async function loadBaemailConfig() {
|
|
30
|
+
const defaults = {
|
|
31
|
+
enabled: true,
|
|
32
|
+
priceSats: 100,
|
|
33
|
+
autoRefund: true,
|
|
54
34
|
blocklist: [],
|
|
55
|
-
|
|
56
|
-
|
|
35
|
+
maxMessageLength: 4000,
|
|
36
|
+
deliveryChannel: 'agent-hook',
|
|
37
|
+
tiers: {
|
|
38
|
+
standard: 100,
|
|
39
|
+
priority: 500,
|
|
40
|
+
urgent: 1000
|
|
41
|
+
}
|
|
57
42
|
};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
configured: true,
|
|
61
|
-
deliveryChannel: channel,
|
|
62
|
-
tiers: config.tiers,
|
|
63
|
-
note: `Advertise with: cli advertise baemail "Baemail" "Paid message forwarding. Pay ${standard}+ sats to reach me." ${standard}`,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* View current baemail configuration.
|
|
68
|
-
*/
|
|
69
|
-
export async function cmdBaemailConfig() {
|
|
70
|
-
const config = loadBaemailConfig();
|
|
71
|
-
if (!config) {
|
|
72
|
-
return fail('Baemail not configured. Run: baemail-setup <channel> <standardSats> [prioritySats] [urgentSats]');
|
|
43
|
+
if (!fs.existsSync(PATHS.baemailConfig)) {
|
|
44
|
+
return defaults;
|
|
73
45
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
* Block a sender from using baemail.
|
|
78
|
-
*/
|
|
79
|
-
export async function cmdBaemailBlock(identityKey) {
|
|
80
|
-
if (!identityKey)
|
|
81
|
-
return fail('Usage: baemail-block <identityKey>');
|
|
82
|
-
const config = loadBaemailConfig();
|
|
83
|
-
if (!config) {
|
|
84
|
-
return fail('Baemail not configured. Run baemail-setup first.');
|
|
46
|
+
try {
|
|
47
|
+
const config = JSON.parse(fs.readFileSync(PATHS.baemailConfig, 'utf-8'));
|
|
48
|
+
return { ...defaults, ...config };
|
|
85
49
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (config.blocklist.includes(identityKey)) {
|
|
89
|
-
return fail('Identity already blocked');
|
|
50
|
+
catch {
|
|
51
|
+
return defaults;
|
|
90
52
|
}
|
|
91
|
-
config.blocklist.push(identityKey);
|
|
92
|
-
config.updatedAt = new Date().toISOString();
|
|
93
|
-
saveBaemailConfig(config);
|
|
94
|
-
return ok({ blocked: identityKey, totalBlocked: config.blocklist.length });
|
|
95
53
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
54
|
+
async function fetchWithTimeout(url, options = {}) {
|
|
55
|
+
const { timeout = 15000 } = options;
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const id = setTimeout(() => controller.abort(), timeout);
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch(url, {
|
|
60
|
+
...options,
|
|
61
|
+
signal: controller.signal
|
|
62
|
+
});
|
|
63
|
+
clearTimeout(id);
|
|
64
|
+
return response;
|
|
105
65
|
}
|
|
106
|
-
|
|
107
|
-
|
|
66
|
+
catch (err) {
|
|
67
|
+
clearTimeout(id);
|
|
68
|
+
throw err;
|
|
108
69
|
}
|
|
109
|
-
config.blocklist = config.blocklist.filter(k => k !== identityKey);
|
|
110
|
-
config.updatedAt = new Date().toISOString();
|
|
111
|
-
saveBaemailConfig(config);
|
|
112
|
-
return ok({ unblocked: identityKey, totalBlocked: config.blocklist.length });
|
|
113
70
|
}
|
|
114
71
|
/**
|
|
115
|
-
*
|
|
72
|
+
* List recent baemail deliveries.
|
|
116
73
|
*/
|
|
117
74
|
export async function cmdBaemailLog(limitStr) {
|
|
118
75
|
const limit = parseInt(limitStr || '20', 10) || 20;
|
|
119
76
|
if (!fs.existsSync(PATHS.baemailLog)) {
|
|
120
77
|
return ok({ log: [], count: 0 });
|
|
121
78
|
}
|
|
122
|
-
const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter(l => l.trim());
|
|
123
|
-
const entries = lines.map(l => {
|
|
79
|
+
const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter((l) => l.trim());
|
|
80
|
+
const entries = lines.map((l) => {
|
|
124
81
|
try {
|
|
125
82
|
return JSON.parse(l);
|
|
126
83
|
}
|
|
@@ -131,6 +88,60 @@ export async function cmdBaemailLog(limitStr) {
|
|
|
131
88
|
const recent = entries.slice(-limit).reverse();
|
|
132
89
|
return ok({ log: recent, count: entries.length, showing: recent.length });
|
|
133
90
|
}
|
|
91
|
+
export async function cmdBaemailSetup(priceSatsStr, prioritySats, urgentSats, channel) {
|
|
92
|
+
const standard = parseInt(priceSatsStr, 10) || 100;
|
|
93
|
+
const priority = parseInt(prioritySats || '', 10) || (standard * 5);
|
|
94
|
+
const urgent = parseInt(urgentSats || '', 10) || (standard * 10);
|
|
95
|
+
const config = {
|
|
96
|
+
enabled: true,
|
|
97
|
+
priceSats: standard,
|
|
98
|
+
autoRefund: true,
|
|
99
|
+
deliveryChannel: channel || 'agent-hook',
|
|
100
|
+
tiers: { standard, priority, urgent }
|
|
101
|
+
};
|
|
102
|
+
const dir = path.dirname(PATHS.baemailConfig);
|
|
103
|
+
if (!fs.existsSync(dir))
|
|
104
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
105
|
+
fs.writeFileSync(PATHS.baemailConfig, JSON.stringify(config, null, 2));
|
|
106
|
+
return ok({ message: `Baemail setup complete.`, config });
|
|
107
|
+
}
|
|
108
|
+
export async function cmdBaemailConfig() {
|
|
109
|
+
const config = await loadBaemailConfig();
|
|
110
|
+
return ok(config);
|
|
111
|
+
}
|
|
112
|
+
export async function cmdBaemailBlock(pubkey) {
|
|
113
|
+
if (!pubkey)
|
|
114
|
+
return fail('Usage: baemail-block <pubkey>');
|
|
115
|
+
let blocklist = [];
|
|
116
|
+
if (fs.existsSync(PATHS.baemailBlocklist)) {
|
|
117
|
+
try {
|
|
118
|
+
blocklist = JSON.parse(fs.readFileSync(PATHS.baemailBlocklist, 'utf-8'));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
blocklist = [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!blocklist.includes(pubkey))
|
|
125
|
+
blocklist.push(pubkey);
|
|
126
|
+
fs.writeFileSync(PATHS.baemailBlocklist, JSON.stringify(blocklist, null, 2));
|
|
127
|
+
return ok({ blocked: true, pubkey, count: blocklist.length });
|
|
128
|
+
}
|
|
129
|
+
export async function cmdBaemailUnblock(pubkey) {
|
|
130
|
+
if (!pubkey)
|
|
131
|
+
return fail('Usage: baemail-unblock <pubkey>');
|
|
132
|
+
let blocklist = [];
|
|
133
|
+
if (fs.existsSync(PATHS.baemailBlocklist)) {
|
|
134
|
+
try {
|
|
135
|
+
blocklist = JSON.parse(fs.readFileSync(PATHS.baemailBlocklist, 'utf-8'));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
blocklist = [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
blocklist = blocklist.filter(p => p !== pubkey);
|
|
142
|
+
fs.writeFileSync(PATHS.baemailBlocklist, JSON.stringify(blocklist, null, 2));
|
|
143
|
+
return ok({ unblocked: true, pubkey, count: blocklist.length });
|
|
144
|
+
}
|
|
134
145
|
/**
|
|
135
146
|
* Refund a failed baemail delivery.
|
|
136
147
|
*/
|
|
@@ -141,7 +152,7 @@ export async function cmdBaemailRefund(requestId) {
|
|
|
141
152
|
return fail('No baemail log found');
|
|
142
153
|
}
|
|
143
154
|
// Find the entry
|
|
144
|
-
const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter(l => l.trim());
|
|
155
|
+
const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter((l) => l.trim());
|
|
145
156
|
const entries = lines.map((l, idx) => {
|
|
146
157
|
try {
|
|
147
158
|
return { ...JSON.parse(l), _lineIdx: idx };
|
|
@@ -225,7 +236,7 @@ export async function cmdBaemailRefund(requestId) {
|
|
|
225
236
|
}
|
|
226
237
|
await tx.sign();
|
|
227
238
|
// Broadcast using configured ARC/Arcade URL or fallback to WhatsOnChain
|
|
228
|
-
const arcUrl = process['
|
|
239
|
+
const arcUrl = process['env'].BSV_ARC_URL;
|
|
229
240
|
let broadcastResp;
|
|
230
241
|
if (arcUrl) {
|
|
231
242
|
broadcastResp = await fetchWithTimeout(`${arcUrl.replace(/\/$/, '')}/v1/tx`, {
|
|
@@ -30,7 +30,7 @@ export async function processBaemail(msg, identityKey, privKey) {
|
|
|
30
30
|
const input = (msg.payload?.input || msg.payload);
|
|
31
31
|
const payment = msg.payload?.payment;
|
|
32
32
|
// Load config
|
|
33
|
-
const config = loadBaemailConfig();
|
|
33
|
+
const config = await loadBaemailConfig();
|
|
34
34
|
if (!config) {
|
|
35
35
|
const rejectPayload = {
|
|
36
36
|
requestId: msg.id,
|
|
@@ -238,7 +238,7 @@ _Reply via overlay: \`cli send ${replyKey} ping "your reply"\`_`;
|
|
|
238
238
|
deliverySuccess,
|
|
239
239
|
deliveryError: deliveryError ?? null,
|
|
240
240
|
paymentTxid: payResult.txid || '',
|
|
241
|
-
refundStatus: deliverySuccess ?
|
|
241
|
+
refundStatus: deliverySuccess ? undefined : 'pending',
|
|
242
242
|
timestamp: new Date().toISOString(),
|
|
243
243
|
};
|
|
244
244
|
fs.appendFileSync(PATHS.baemailLog, JSON.stringify(logEntry) + '\n');
|
|
@@ -118,7 +118,7 @@ export async function cmdResearchRespond(resultJsonPath) {
|
|
|
118
118
|
// Remove from queue
|
|
119
119
|
if (fs.existsSync(PATHS.researchQueue)) {
|
|
120
120
|
const lines = fs.readFileSync(PATHS.researchQueue, 'utf-8').trim().split('\n').filter(Boolean);
|
|
121
|
-
const remaining = lines.filter(l => {
|
|
121
|
+
const remaining = lines.filter((l) => {
|
|
122
122
|
try {
|
|
123
123
|
return JSON.parse(l).requestId !== requestId;
|
|
124
124
|
}
|
|
@@ -97,7 +97,7 @@ export function readJsonl(filePath) {
|
|
|
97
97
|
if (!fs.existsSync(filePath))
|
|
98
98
|
return [];
|
|
99
99
|
const lines = fs.readFileSync(filePath, 'utf-8').trim().split('\n').filter(Boolean);
|
|
100
|
-
return lines.map(line => {
|
|
100
|
+
return lines.map((line) => {
|
|
101
101
|
try {
|
|
102
102
|
return JSON.parse(line);
|
|
103
103
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import process from 'node:process';
|
|
6
7
|
// Simple test runner (matching existing pattern)
|
|
7
8
|
let passed = 0;
|
|
8
9
|
let failed = 0;
|
|
@@ -106,12 +107,12 @@ async function run() {
|
|
|
106
107
|
mockCleanupServiceQueue(24 * 60 * 60 * 1000, 2 * 60 * 60 * 1000);
|
|
107
108
|
// Check remaining entries
|
|
108
109
|
const lines = fs.readFileSync(TEST_PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
|
|
109
|
-
const remaining = lines.map(line => JSON.parse(line));
|
|
110
|
+
const remaining = lines.map((line) => JSON.parse(line));
|
|
110
111
|
assert(remaining.length === 2, `Expected 2 remaining entries, got ${remaining.length}`);
|
|
111
|
-
assert(remaining.find(e => e.requestId === 'pending-1') !== undefined, 'Should keep recent pending');
|
|
112
|
-
assert(remaining.find(e => e.requestId === 'recent-fulfilled') !== undefined, 'Should keep recent fulfilled');
|
|
113
|
-
assert(remaining.find(e => e.requestId === 'old-fulfilled') === undefined, 'Should remove old fulfilled');
|
|
114
|
-
assert(remaining.find(e => e.requestId === 'old-rejected') === undefined, 'Should remove old rejected');
|
|
112
|
+
assert(remaining.find((e) => e.requestId === 'pending-1') !== undefined, 'Should keep recent pending');
|
|
113
|
+
assert(remaining.find((e) => e.requestId === 'recent-fulfilled') !== undefined, 'Should keep recent fulfilled');
|
|
114
|
+
assert(remaining.find((e) => e.requestId === 'old-fulfilled') === undefined, 'Should remove old fulfilled');
|
|
115
|
+
assert(remaining.find((e) => e.requestId === 'old-rejected') === undefined, 'Should remove old rejected');
|
|
115
116
|
cleanupTestEnv();
|
|
116
117
|
});
|
|
117
118
|
await test('updateServiceQueueStatus updates request status atomically', async () => {
|
|
@@ -131,7 +132,7 @@ async function run() {
|
|
|
131
132
|
return false;
|
|
132
133
|
const lines = fs.readFileSync(TEST_PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
|
|
133
134
|
let updated = false;
|
|
134
|
-
const updatedLines = lines.map(line => {
|
|
135
|
+
const updatedLines = lines.map((line) => {
|
|
135
136
|
try {
|
|
136
137
|
const entryData = JSON.parse(line);
|
|
137
138
|
if (entryData.requestId === requestId) {
|
|
@@ -178,7 +179,7 @@ async function run() {
|
|
|
178
179
|
return false;
|
|
179
180
|
const lines = fs.readFileSync(TEST_PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
|
|
180
181
|
let updated = false;
|
|
181
|
-
lines.map(line => {
|
|
182
|
+
lines.map((line) => {
|
|
182
183
|
try {
|
|
183
184
|
const entry = JSON.parse(line);
|
|
184
185
|
if (entry.requestId === requestId) {
|
package/index.ts
CHANGED
|
@@ -9,10 +9,15 @@ import path from 'node:path';
|
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import fs from 'node:fs';
|
|
12
|
+
import process from 'node:process';
|
|
13
|
+
import { Buffer } from 'node:buffer';
|
|
12
14
|
import { initializeServiceSystem, serviceManager } from './src/services/index.js';
|
|
15
|
+
|
|
13
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
17
|
const __dirname = path.dirname(__filename);
|
|
15
18
|
|
|
19
|
+
let isInitialized = false;
|
|
20
|
+
|
|
16
21
|
async function ensureCp() {
|
|
17
22
|
if (execFileAsync) return;
|
|
18
23
|
// @ts-ignore
|
|
@@ -71,7 +76,7 @@ function loadDailySpending(walletDir: string): DailySpending {
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
function writeActivityEvent(event: any) {
|
|
74
|
-
const alertDir = path.join((process as any)['
|
|
79
|
+
const alertDir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
|
|
75
80
|
try {
|
|
76
81
|
fs.mkdirSync(alertDir, { recursive: true });
|
|
77
82
|
fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
|
|
@@ -130,7 +135,7 @@ async function startAutoImport(env: any, cliPath: string, logger: any) {
|
|
|
130
135
|
|
|
131
136
|
// Clear onboarding flag since wallet is now funded
|
|
132
137
|
try {
|
|
133
|
-
const onboardingSentFile = path.join((process as any)['
|
|
138
|
+
const onboardingSentFile = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
|
|
134
139
|
if (fs.existsSync(onboardingSentFile)) {
|
|
135
140
|
fs.unlinkSync(onboardingSentFile);
|
|
136
141
|
}
|
|
@@ -141,7 +146,7 @@ async function startAutoImport(env: any, cliPath: string, logger: any) {
|
|
|
141
146
|
|
|
142
147
|
// Check if registered, auto-register if not
|
|
143
148
|
try {
|
|
144
|
-
const regPath = path.join((process as any)['
|
|
149
|
+
const regPath = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
145
150
|
if (!fs.existsSync(regPath)) {
|
|
146
151
|
logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
|
|
147
152
|
const regResult = await execFileAsync('node', [cliPath, 'register'], { env, timeout: 60000 });
|
|
@@ -208,7 +213,7 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
|
|
|
208
213
|
|
|
209
214
|
function wakeAgent(text: string, logger: any, options: { sessionKey?: string } = {}) {
|
|
210
215
|
const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
|
|
211
|
-
const gatewayPort = (process as any)['
|
|
216
|
+
const gatewayPort = (process as any)['env'].OPENCLAW_GATEWAY_PORT || '18789';
|
|
212
217
|
const httpToken = getHooksToken();
|
|
213
218
|
if (!httpToken) return;
|
|
214
219
|
|
|
@@ -220,7 +225,7 @@ function wakeAgent(text: string, logger: any, options: { sessionKey?: string } =
|
|
|
220
225
|
}
|
|
221
226
|
|
|
222
227
|
function getHooksToken(): string | null {
|
|
223
|
-
let token = (process as any)['
|
|
228
|
+
let token = (process as any)['env'].OPENCLAW_HOOKS_TOKEN || null;
|
|
224
229
|
if (!token) {
|
|
225
230
|
try {
|
|
226
231
|
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
@@ -275,7 +280,7 @@ function startBackgroundService(env: any, cliPath: string, logger: any) {
|
|
|
275
280
|
}
|
|
276
281
|
const notif = categorizeEvent(event);
|
|
277
282
|
if (notif) {
|
|
278
|
-
const dir = path.join((process as any)['
|
|
283
|
+
const dir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
|
|
279
284
|
fs.mkdirSync(dir, { recursive: true });
|
|
280
285
|
fs.appendFileSync(path.join(dir, 'activity-feed.jsonl'), JSON.stringify(notif) + '\n');
|
|
281
286
|
}
|
|
@@ -299,101 +304,112 @@ function stopBackgroundService() {
|
|
|
299
304
|
stopAutoImport();
|
|
300
305
|
}
|
|
301
306
|
|
|
302
|
-
export async function activate(api: any) {
|
|
303
|
-
return register(api);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
let isInitialized = false;
|
|
307
|
-
|
|
308
307
|
function getCliPath() {
|
|
309
308
|
const base = __dirname.endsWith('dist') ? __dirname : path.join(__dirname, 'dist');
|
|
310
309
|
return path.join(base, 'src', 'cli.js');
|
|
311
310
|
}
|
|
312
311
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
api
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
312
|
+
/**
|
|
313
|
+
* OpenClaw Overlay Plugin
|
|
314
|
+
* Decentralized agent marketplace with BSV micropayments.
|
|
315
|
+
*/
|
|
316
|
+
export const plugin = {
|
|
317
|
+
id: "openclaw-overlay-plugin",
|
|
318
|
+
name: "BSV Overlay Network",
|
|
319
|
+
description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
|
|
320
|
+
|
|
321
|
+
async activate(api: any) {
|
|
322
|
+
return this.register(api);
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
register(api: any) {
|
|
326
|
+
if (isInitialized) return;
|
|
327
|
+
isInitialized = true;
|
|
328
|
+
|
|
329
|
+
const entries = api.getConfig?.()?.plugins?.entries || {};
|
|
330
|
+
const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
|
|
331
|
+
const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
|
|
332
|
+
|
|
333
|
+
// 1. Tool
|
|
334
|
+
api.registerTool({
|
|
335
|
+
name: "overlay",
|
|
336
|
+
description: "Access the BSV agent marketplace",
|
|
337
|
+
parameters: {
|
|
338
|
+
type: "object",
|
|
339
|
+
properties: {
|
|
340
|
+
action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
|
|
341
|
+
service: { type: "string" },
|
|
342
|
+
input: { type: "object" },
|
|
343
|
+
identityKey: { type: "string" },
|
|
344
|
+
sats: { type: "number" },
|
|
345
|
+
requestId: { type: "string" },
|
|
346
|
+
recipientKey: { type: "string" },
|
|
347
|
+
serviceId: { type: "string" },
|
|
348
|
+
result: { type: "object" }
|
|
349
|
+
},
|
|
350
|
+
required: ["action"]
|
|
337
351
|
},
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
352
|
+
async execute(_id: string, params: any) {
|
|
353
|
+
try {
|
|
354
|
+
return await executeOverlayAction(params, pluginConfig, api);
|
|
355
|
+
} catch (error: any) {
|
|
356
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
357
|
+
}
|
|
345
358
|
}
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// 2. Command
|
|
350
|
-
api.registerCommand({
|
|
351
|
-
name: "overlay",
|
|
352
|
-
description: "BSV Overlay Marketplace commands",
|
|
353
|
-
acceptsArgs: true,
|
|
354
|
-
handler: async (ctx: any) => {
|
|
355
|
-
try {
|
|
356
|
-
const action = ctx.args?.[0] || 'status';
|
|
359
|
+
});
|
|
357
360
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
+
// 2. Command
|
|
362
|
+
api.registerCommand({
|
|
363
|
+
name: "overlay",
|
|
364
|
+
description: "BSV Overlay Marketplace commands",
|
|
365
|
+
acceptsArgs: true,
|
|
366
|
+
requireAuth: true,
|
|
367
|
+
handler: async (ctx: any) => {
|
|
368
|
+
try {
|
|
369
|
+
const action = ctx.args?.[0] || 'status';
|
|
361
370
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
371
|
+
if (action === 'help') {
|
|
372
|
+
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- \`pending-requests\`: See incoming service jobs\n- \`fulfill\`: Complete a request (Agent only)` };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const result = await executeOverlayAction({ action }, pluginConfig, api);
|
|
376
|
+
return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
|
|
377
|
+
} catch (error: any) {
|
|
378
|
+
return { text: `❌ Error: ${error.message}` };
|
|
379
|
+
}
|
|
366
380
|
}
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// 3. Service
|
|
371
|
-
api.registerService({
|
|
372
|
-
id: "openclaw-overlay-relay",
|
|
373
|
-
start: async () => {
|
|
374
|
-
const env = buildEnvironment(pluginConfig);
|
|
375
|
-
const cliPath = getCliPath();
|
|
376
|
-
startBackgroundService(env, cliPath, api.logger);
|
|
377
|
-
startAutoImport(env, cliPath, api.logger);
|
|
378
|
-
},
|
|
379
|
-
stop: () => stopBackgroundService()
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
// 4. CLI
|
|
383
|
-
api.registerCli(({ program }: any) => {
|
|
384
|
-
const overlay = program.command("overlay").description("BSV Overlay Network management");
|
|
385
|
-
overlay.command("status").action(async () => {
|
|
386
|
-
await ensureCp();
|
|
387
|
-
const result = await handleStatus(buildEnvironment(pluginConfig), getCliPath());
|
|
388
|
-
console.log(JSON.stringify(result, null, 2));
|
|
389
381
|
});
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
382
|
+
|
|
383
|
+
// 3. Service
|
|
384
|
+
api.registerService({
|
|
385
|
+
id: "openclaw-overlay-relay",
|
|
386
|
+
start: async () => {
|
|
387
|
+
const env = buildEnvironment(pluginConfig);
|
|
388
|
+
const cliPath = getCliPath();
|
|
389
|
+
startBackgroundService(env, cliPath, api.logger);
|
|
390
|
+
startAutoImport(env, cliPath, api.logger);
|
|
391
|
+
},
|
|
392
|
+
stop: () => stopBackgroundService()
|
|
394
393
|
});
|
|
395
|
-
|
|
396
|
-
|
|
394
|
+
|
|
395
|
+
// 4. CLI
|
|
396
|
+
api.registerCli(({ program }: any) => {
|
|
397
|
+
const overlay = program.command("overlay").description("BSV Overlay Network management");
|
|
398
|
+
overlay.command("status").action(async () => {
|
|
399
|
+
await ensureCp();
|
|
400
|
+
const result = await handleStatus(buildEnvironment(pluginConfig), getCliPath());
|
|
401
|
+
console.log(JSON.stringify(result, null, 2));
|
|
402
|
+
});
|
|
403
|
+
overlay.command("balance").action(async () => {
|
|
404
|
+
await ensureCp();
|
|
405
|
+
const result = await handleBalance(buildEnvironment(pluginConfig), getCliPath());
|
|
406
|
+
console.log(JSON.stringify(result, null, 2));
|
|
407
|
+
});
|
|
408
|
+
}, { descriptors: [{ name: "overlay", description: "BSV Overlay Network management" }] });
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
export default plugin;
|
|
397
413
|
|
|
398
414
|
async function executeOverlayAction(params: any, config: any, api: any) {
|
|
399
415
|
await ensureCp();
|
|
@@ -472,7 +488,7 @@ async function handleFulfill(params: any, env: any, cliPath: string) {
|
|
|
472
488
|
}
|
|
473
489
|
|
|
474
490
|
function buildEnvironment(config: any) {
|
|
475
|
-
const env = { ...(process as any)['
|
|
491
|
+
const env = { ...(process as any)['env'] };
|
|
476
492
|
env.BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
|
|
477
493
|
env.OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
|
|
478
494
|
env.BSV_NETWORK = env.BSV_NETWORK || 'mainnet';
|