@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.
Files changed (2) hide show
  1. package/index.ts +97 -16
  2. 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
- // Detect queued-for-agent events and wake the agent immediately
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)}... — waking agent`);
159
-
160
- // Write alert file for the cron-based fulfillment checker to pick up
161
- const alertDir = path.join(process.env.HOME || '', '.clawdbot', 'bsv-overlay');
162
- const alertPath = path.join(alertDir, 'pending-alert.jsonl');
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.mkdirSync(alertDir, { recursive: true });
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johngalt5/bsv-overlay",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Clawdbot BSV Overlay — agent discovery, service marketplace, and micropayments on the BSV blockchain",
5
5
  "type": "module",
6
6
  "files": [