@johngalt5/bsv-overlay 0.2.11 → 0.2.13

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 +88 -13
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -42,6 +42,14 @@ function loadDailySpending(walletDir: string): DailySpending {
42
42
  return { date: today, totalSats: 0, transactions: [] };
43
43
  }
44
44
 
45
+ function writeActivityEvent(event) {
46
+ const alertDir = path.join(process.env.HOME || '', '.clawdbot', 'bsv-overlay');
47
+ try {
48
+ fs.mkdirSync(alertDir, { recursive: true });
49
+ fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
50
+ } catch {}
51
+ }
52
+
45
53
  function recordSpend(walletDir: string, sats: number, service: string, provider: string) {
46
54
  const spending = loadDailySpending(walletDir);
47
55
  spending.totalSats += sats;
@@ -132,6 +140,33 @@ function stopAutoImport() {
132
140
  }
133
141
  }
134
142
 
143
+ // Categorize WebSocket events into notification types
144
+ function categorizeEvent(event) {
145
+ const base = { ts: Date.now(), from: event.from?.slice(0, 16), fullFrom: event.from };
146
+
147
+ // 💰 Incoming payment — someone paid us for a service
148
+ if (event.action === 'queued-for-agent' && event.satoshisReceived) {
149
+ return { ...base, type: 'incoming_payment', emoji: '💰', serviceId: event.serviceId, sats: event.satoshisReceived, requestId: event.id, message: `Received ${event.satoshisReceived} sats for ${event.serviceId}` };
150
+ }
151
+ if (event.action === 'fulfilled' && event.satoshisReceived) {
152
+ return { ...base, type: 'incoming_payment', emoji: '💰', serviceId: event.serviceId, sats: event.satoshisReceived, message: `Received ${event.satoshisReceived} sats for ${event.serviceId} (auto-fulfilled)` };
153
+ }
154
+
155
+ // 📬 Response received — a service we requested came back
156
+ if (event.type === 'service-response' && event.action === 'received') {
157
+ const payload = event.payload || {};
158
+ return { ...base, type: 'response_received', emoji: '📬', serviceId: payload.serviceId, status: payload.status, message: `Response received for ${payload.serviceId}: ${payload.status}` };
159
+ }
160
+
161
+ // ❌ Request rejected
162
+ if (event.action === 'rejected' && event.serviceId) {
163
+ return { ...base, type: 'request_rejected', emoji: '❌', serviceId: event.serviceId, reason: event.reason, message: `Rejected ${event.serviceId} request: ${event.reason}` };
164
+ }
165
+
166
+ // Skip pings/pongs and other noise
167
+ return null;
168
+ }
169
+
135
170
  function startBackgroundService(env, cliPath, logger) {
136
171
  if (backgroundProcess) return;
137
172
  serviceRunning = true;
@@ -153,16 +188,14 @@ function startBackgroundService(env, cliPath, logger) {
153
188
  const event = JSON.parse(line);
154
189
  logger?.debug?.(`[bsv-overlay] ${event.event || event.type || 'message'}:`, JSON.stringify(event).slice(0, 200));
155
190
 
156
- // Detect queued-for-agent events and wake the agent immediately
191
+ const alertDir = path.join(process.env.HOME || '', '.clawdbot', 'bsv-overlay');
192
+ fs.mkdirSync(alertDir, { recursive: true });
193
+
194
+ // Detect queued-for-agent events — write fulfillment alert
157
195
  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');
196
+ logger?.info?.(`[bsv-overlay] ⚡ Incoming ${event.serviceId} request from ${event.from?.slice(0, 12)}...`);
163
197
  try {
164
- fs.mkdirSync(alertDir, { recursive: true });
165
- fs.appendFileSync(alertPath, JSON.stringify({
198
+ fs.appendFileSync(path.join(alertDir, 'pending-alert.jsonl'), JSON.stringify({
166
199
  requestId: event.id,
167
200
  serviceId: event.serviceId,
168
201
  from: event.from,
@@ -171,6 +204,14 @@ function startBackgroundService(env, cliPath, logger) {
171
204
  }) + '\n');
172
205
  } catch {}
173
206
  }
207
+
208
+ // Write payment/activity notifications for ALL significant events
209
+ const notifEvent = categorizeEvent(event);
210
+ if (notifEvent) {
211
+ try {
212
+ fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify(notifEvent) + '\n');
213
+ } catch {}
214
+ }
174
215
  } catch {}
175
216
  }
176
217
  });
@@ -601,6 +642,9 @@ async function executeOverlayAction(params, config, api) {
601
642
  case "pending-requests":
602
643
  return await handlePendingRequests(env, cliPath);
603
644
 
645
+ case "activity":
646
+ return handleActivity();
647
+
604
648
  case "fulfill":
605
649
  return await handleFulfill(params, env, cliPath);
606
650
 
@@ -677,8 +721,11 @@ async function handleServiceRequest(params, env, cliPath, config, api) {
677
721
  throw new Error(`Service request failed: ${requestOutput.error}`);
678
722
  }
679
723
 
680
- // 7. Poll for response
681
- const maxPollAttempts = 12; // ~60 seconds with 5 second intervals
724
+ // 7. Poll for response (up to 120s, with early return)
725
+ // The WebSocket background service will also receive the response
726
+ // asynchronously, so we return a "pending" status if we time out
727
+ // rather than throwing an error.
728
+ const maxPollAttempts = 24; // ~120 seconds with 5 second intervals
682
729
  let attempts = 0;
683
730
 
684
731
  while (attempts < maxPollAttempts) {
@@ -690,13 +737,14 @@ async function handleServiceRequest(params, env, cliPath, config, api) {
690
737
  const pollOutput = parseCliOutput(pollResult.stdout);
691
738
 
692
739
  if (pollOutput.success && pollOutput.data) {
693
- // FIX: Check pollOutput.data.messages array for service-response
740
+ // Check pollOutput.data.messages array for service-response
694
741
  const messages = pollOutput.data.messages || [];
695
742
  for (const msg of messages) {
696
743
  if (msg.type === 'service-response' && msg.from === bestProvider.identityKey) {
697
744
  api.logger.info(`Received response from ${bestProvider.agentName}`);
698
- // Record the spending
699
745
  recordSpend(walletDir, price, service, bestProvider.agentName);
746
+ writeActivityEvent({ type: 'outgoing_payment', emoji: '💸', sats: price, service, provider: bestProvider.agentName, message: `Paid ${price} sats to ${bestProvider.agentName} for ${service}` });
747
+ writeActivityEvent({ type: 'response_received', emoji: '📬', service, provider: bestProvider.agentName, status: msg.payload?.status, message: `${service} response received from ${bestProvider.agentName}` });
700
748
  return {
701
749
  provider: bestProvider.agentName,
702
750
  cost: price,
@@ -711,7 +759,17 @@ async function handleServiceRequest(params, env, cliPath, config, api) {
711
759
  }
712
760
  }
713
761
 
714
- throw new Error(`Service request timed out after ${maxPollAttempts * 5} seconds`);
762
+ // Don't throw the response may still arrive via WebSocket
763
+ recordSpend(walletDir, price, service, bestProvider.agentName);
764
+ writeActivityEvent({ type: 'outgoing_payment', emoji: '💸', sats: price, service, provider: bestProvider.agentName, message: `Paid ${price} sats to ${bestProvider.agentName} for ${service} (awaiting response)` });
765
+ return {
766
+ provider: bestProvider.agentName,
767
+ cost: price,
768
+ status: "pending",
769
+ message: `Request sent and paid (${price} sats). The provider hasn't responded within 120s, but the response may still arrive via the background WebSocket service. Check notifications later.`,
770
+ requestId: requestOutput.data?.messageId,
771
+ providerKey: bestProvider.identityKey,
772
+ };
715
773
  }
716
774
 
717
775
  async function handleDiscover(params, env, cliPath) {
@@ -799,6 +857,7 @@ async function handleDirectPay(params, env, cliPath, config) {
799
857
 
800
858
  // Record the spending
801
859
  recordSpend(walletDir, sats, 'direct-payment', identityKey);
860
+ writeActivityEvent({ type: 'outgoing_payment', emoji: '💸', sats, service: 'direct-payment', provider: identityKey?.slice(0, 16), message: `Direct payment: ${sats} sats sent` });
802
861
 
803
862
  return output.data;
804
863
  }
@@ -1067,6 +1126,19 @@ async function handlePendingRequests(env, cliPath) {
1067
1126
  return output.data;
1068
1127
  }
1069
1128
 
1129
+ function handleActivity() {
1130
+ const feedPath = path.join(process.env.HOME || '', '.clawdbot', 'bsv-overlay', 'activity-feed.jsonl');
1131
+ if (!fs.existsSync(feedPath)) return { events: [], count: 0 };
1132
+
1133
+ const lines = fs.readFileSync(feedPath, 'utf-8').trim().split('\n').filter(Boolean);
1134
+ const events = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
1135
+
1136
+ // Clear the feed after reading
1137
+ fs.writeFileSync(feedPath, '');
1138
+
1139
+ return { events, count: events.length };
1140
+ }
1141
+
1070
1142
  async function handleFulfill(params, env, cliPath) {
1071
1143
  const { requestId, recipientKey, serviceId, result } = params;
1072
1144
  if (!requestId || !recipientKey || !serviceId || !result) {
@@ -1078,6 +1150,9 @@ async function handleFulfill(params, env, cliPath) {
1078
1150
  ], { env });
1079
1151
  const output = parseCliOutput(cliResult.stdout);
1080
1152
  if (!output.success) throw new Error(`Fulfill failed: ${output.error}`);
1153
+
1154
+ writeActivityEvent({ type: 'service_fulfilled', emoji: '✅', serviceId, recipientKey: recipientKey?.slice(0, 16), message: `Fulfilled ${serviceId} request — response sent` });
1155
+
1081
1156
  return output.data;
1082
1157
  }
1083
1158
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johngalt5/bsv-overlay",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Clawdbot BSV Overlay — agent discovery, service marketplace, and micropayments on the BSV blockchain",
5
5
  "type": "module",
6
6
  "files": [