@johngalt5/bsv-overlay 0.2.12 → 0.2.14
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/index.ts +97 -16
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { promisify } from 'util';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import fs from 'fs';
|
|
6
|
+
import WebSocket from 'ws';
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = path.dirname(__filename);
|
|
@@ -42,6 +43,14 @@ function loadDailySpending(walletDir: string): DailySpending {
|
|
|
42
43
|
return { date: today, totalSats: 0, transactions: [] };
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function writeActivityEvent(event) {
|
|
47
|
+
const alertDir = path.join(process.env.HOME || '', '.clawdbot', 'bsv-overlay');
|
|
48
|
+
try {
|
|
49
|
+
fs.mkdirSync(alertDir, { recursive: true });
|
|
50
|
+
fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
|
|
51
|
+
} catch {}
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
function recordSpend(walletDir: string, sats: number, service: string, provider: string) {
|
|
46
55
|
const spending = loadDailySpending(walletDir);
|
|
47
56
|
spending.totalSats += sats;
|
|
@@ -132,6 +141,58 @@ function stopAutoImport() {
|
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
143
|
|
|
144
|
+
// Wake the agent via gateway WebSocket JSON-RPC (event-driven, zero polling)
|
|
145
|
+
function wakeAgent(text: string, logger?: any) {
|
|
146
|
+
try {
|
|
147
|
+
const ws = new WebSocket('ws://127.0.0.1:18789');
|
|
148
|
+
const timeout = setTimeout(() => { try { ws.close(); } catch {} }, 5000);
|
|
149
|
+
ws.on('open', () => {
|
|
150
|
+
ws.send(JSON.stringify({
|
|
151
|
+
jsonrpc: '2.0',
|
|
152
|
+
id: `overlay-wake-${Date.now()}`,
|
|
153
|
+
method: 'cron.wake',
|
|
154
|
+
params: { mode: 'now', text },
|
|
155
|
+
}));
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
setTimeout(() => { try { ws.close(); } catch {} }, 500);
|
|
158
|
+
logger?.info?.('[bsv-overlay] Agent woken via WebSocket');
|
|
159
|
+
});
|
|
160
|
+
ws.on('error', (err) => {
|
|
161
|
+
clearTimeout(timeout);
|
|
162
|
+
logger?.warn?.('[bsv-overlay] WebSocket wake failed:', err.message);
|
|
163
|
+
});
|
|
164
|
+
} catch (err: any) {
|
|
165
|
+
logger?.warn?.('[bsv-overlay] Wake failed:', err.message);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Categorize WebSocket events into notification types
|
|
170
|
+
function categorizeEvent(event) {
|
|
171
|
+
const base = { ts: Date.now(), from: event.from?.slice(0, 16), fullFrom: event.from };
|
|
172
|
+
|
|
173
|
+
// 💰 Incoming payment — someone paid us for a service
|
|
174
|
+
if (event.action === 'queued-for-agent' && event.satoshisReceived) {
|
|
175
|
+
return { ...base, type: 'incoming_payment', emoji: '💰', serviceId: event.serviceId, sats: event.satoshisReceived, requestId: event.id, message: `Received ${event.satoshisReceived} sats for ${event.serviceId}` };
|
|
176
|
+
}
|
|
177
|
+
if (event.action === 'fulfilled' && event.satoshisReceived) {
|
|
178
|
+
return { ...base, type: 'incoming_payment', emoji: '💰', serviceId: event.serviceId, sats: event.satoshisReceived, message: `Received ${event.satoshisReceived} sats for ${event.serviceId} (auto-fulfilled)` };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 📬 Response received — a service we requested came back
|
|
182
|
+
if (event.type === 'service-response' && event.action === 'received') {
|
|
183
|
+
const payload = event.payload || {};
|
|
184
|
+
return { ...base, type: 'response_received', emoji: '📬', serviceId: payload.serviceId, status: payload.status, message: `Response received for ${payload.serviceId}: ${payload.status}` };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ❌ Request rejected
|
|
188
|
+
if (event.action === 'rejected' && event.serviceId) {
|
|
189
|
+
return { ...base, type: 'request_rejected', emoji: '❌', serviceId: event.serviceId, reason: event.reason, message: `Rejected ${event.serviceId} request: ${event.reason}` };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Skip pings/pongs and other noise
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
135
196
|
function startBackgroundService(env, cliPath, logger) {
|
|
136
197
|
if (backgroundProcess) return;
|
|
137
198
|
serviceRunning = true;
|
|
@@ -153,22 +214,21 @@ function startBackgroundService(env, cliPath, logger) {
|
|
|
153
214
|
const event = JSON.parse(line);
|
|
154
215
|
logger?.debug?.(`[bsv-overlay] ${event.event || event.type || 'message'}:`, JSON.stringify(event).slice(0, 200));
|
|
155
216
|
|
|
156
|
-
|
|
217
|
+
const alertDir = path.join(process.env.HOME || '', '.clawdbot', 'bsv-overlay');
|
|
218
|
+
fs.mkdirSync(alertDir, { recursive: true });
|
|
219
|
+
|
|
220
|
+
// Detect queued-for-agent events — wake agent via WebSocket
|
|
157
221
|
if (event.action === 'queued-for-agent' && event.serviceId) {
|
|
158
|
-
logger?.info?.(`[bsv-overlay] ⚡ Incoming ${event.serviceId} request from ${event.from?.slice(0, 12)}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
222
|
+
logger?.info?.(`[bsv-overlay] ⚡ Incoming ${event.serviceId} request from ${event.from?.slice(0, 12)}...`);
|
|
223
|
+
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 ${event.serviceId} request using your capabilities\n3. overlay({ action: "fulfill", requestId: "${event.id}", recipientKey: "${event.from}", serviceId: "${event.serviceId}", result: { ... } })`;
|
|
224
|
+
wakeAgent(wakeText, logger);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Write payment/activity notifications for ALL significant events
|
|
228
|
+
const notifEvent = categorizeEvent(event);
|
|
229
|
+
if (notifEvent) {
|
|
163
230
|
try {
|
|
164
|
-
fs.
|
|
165
|
-
fs.appendFileSync(alertPath, JSON.stringify({
|
|
166
|
-
requestId: event.id,
|
|
167
|
-
serviceId: event.serviceId,
|
|
168
|
-
from: event.from,
|
|
169
|
-
satoshis: event.satoshisReceived,
|
|
170
|
-
ts: Date.now(),
|
|
171
|
-
}) + '\n');
|
|
231
|
+
fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify(notifEvent) + '\n');
|
|
172
232
|
} catch {}
|
|
173
233
|
}
|
|
174
234
|
} catch {}
|
|
@@ -601,6 +661,9 @@ async function executeOverlayAction(params, config, api) {
|
|
|
601
661
|
case "pending-requests":
|
|
602
662
|
return await handlePendingRequests(env, cliPath);
|
|
603
663
|
|
|
664
|
+
case "activity":
|
|
665
|
+
return handleActivity();
|
|
666
|
+
|
|
604
667
|
case "fulfill":
|
|
605
668
|
return await handleFulfill(params, env, cliPath);
|
|
606
669
|
|
|
@@ -698,8 +761,9 @@ async function handleServiceRequest(params, env, cliPath, config, api) {
|
|
|
698
761
|
for (const msg of messages) {
|
|
699
762
|
if (msg.type === 'service-response' && msg.from === bestProvider.identityKey) {
|
|
700
763
|
api.logger.info(`Received response from ${bestProvider.agentName}`);
|
|
701
|
-
// Record the spending
|
|
702
764
|
recordSpend(walletDir, price, service, bestProvider.agentName);
|
|
765
|
+
writeActivityEvent({ type: 'outgoing_payment', emoji: '💸', sats: price, service, provider: bestProvider.agentName, message: `Paid ${price} sats to ${bestProvider.agentName} for ${service}` });
|
|
766
|
+
writeActivityEvent({ type: 'response_received', emoji: '📬', service, provider: bestProvider.agentName, status: msg.payload?.status, message: `${service} response received from ${bestProvider.agentName}` });
|
|
703
767
|
return {
|
|
704
768
|
provider: bestProvider.agentName,
|
|
705
769
|
cost: price,
|
|
@@ -715,8 +779,8 @@ async function handleServiceRequest(params, env, cliPath, config, api) {
|
|
|
715
779
|
}
|
|
716
780
|
|
|
717
781
|
// Don't throw — the response may still arrive via WebSocket
|
|
718
|
-
// Record the spend since payment was already sent
|
|
719
782
|
recordSpend(walletDir, price, service, bestProvider.agentName);
|
|
783
|
+
writeActivityEvent({ type: 'outgoing_payment', emoji: '💸', sats: price, service, provider: bestProvider.agentName, message: `Paid ${price} sats to ${bestProvider.agentName} for ${service} (awaiting response)` });
|
|
720
784
|
return {
|
|
721
785
|
provider: bestProvider.agentName,
|
|
722
786
|
cost: price,
|
|
@@ -812,6 +876,7 @@ async function handleDirectPay(params, env, cliPath, config) {
|
|
|
812
876
|
|
|
813
877
|
// Record the spending
|
|
814
878
|
recordSpend(walletDir, sats, 'direct-payment', identityKey);
|
|
879
|
+
writeActivityEvent({ type: 'outgoing_payment', emoji: '💸', sats, service: 'direct-payment', provider: identityKey?.slice(0, 16), message: `Direct payment: ${sats} sats sent` });
|
|
815
880
|
|
|
816
881
|
return output.data;
|
|
817
882
|
}
|
|
@@ -1080,6 +1145,19 @@ async function handlePendingRequests(env, cliPath) {
|
|
|
1080
1145
|
return output.data;
|
|
1081
1146
|
}
|
|
1082
1147
|
|
|
1148
|
+
function handleActivity() {
|
|
1149
|
+
const feedPath = path.join(process.env.HOME || '', '.clawdbot', 'bsv-overlay', 'activity-feed.jsonl');
|
|
1150
|
+
if (!fs.existsSync(feedPath)) return { events: [], count: 0 };
|
|
1151
|
+
|
|
1152
|
+
const lines = fs.readFileSync(feedPath, 'utf-8').trim().split('\n').filter(Boolean);
|
|
1153
|
+
const events = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
1154
|
+
|
|
1155
|
+
// Clear the feed after reading
|
|
1156
|
+
fs.writeFileSync(feedPath, '');
|
|
1157
|
+
|
|
1158
|
+
return { events, count: events.length };
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1083
1161
|
async function handleFulfill(params, env, cliPath) {
|
|
1084
1162
|
const { requestId, recipientKey, serviceId, result } = params;
|
|
1085
1163
|
if (!requestId || !recipientKey || !serviceId || !result) {
|
|
@@ -1091,6 +1169,9 @@ async function handleFulfill(params, env, cliPath) {
|
|
|
1091
1169
|
], { env });
|
|
1092
1170
|
const output = parseCliOutput(cliResult.stdout);
|
|
1093
1171
|
if (!output.success) throw new Error(`Fulfill failed: ${output.error}`);
|
|
1172
|
+
|
|
1173
|
+
writeActivityEvent({ type: 'service_fulfilled', emoji: '✅', serviceId, recipientKey: recipientKey?.slice(0, 16), message: `Fulfilled ${serviceId} request — response sent` });
|
|
1174
|
+
|
|
1094
1175
|
return output.data;
|
|
1095
1176
|
}
|
|
1096
1177
|
|