@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.
- package/index.ts +88 -13
- 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
|
-
|
|
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)}
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|