openclaw-overlay-plugin 0.8.18 → 0.8.20

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 (84) hide show
  1. package/dist/index.js +607 -853
  2. package/dist/index.js.map +4 -4
  3. package/dist/src/cli.js +30 -568
  4. package/dist/src/cli.js.map +4 -4
  5. package/package.json +5 -5
  6. package/index.ts +0 -379
  7. package/src/ambient.d.ts +0 -1
  8. package/src/cli-main.ts +0 -240
  9. package/src/cli.ts +0 -16
  10. package/src/compatibility.test.ts +0 -46
  11. package/src/scripts/baemail/commands.ts +0 -311
  12. package/src/scripts/baemail/handler.ts +0 -338
  13. package/src/scripts/baemail/index.ts +0 -6
  14. package/src/scripts/config.ts +0 -89
  15. package/src/scripts/index.ts +0 -8
  16. package/src/scripts/messaging/connect.ts +0 -162
  17. package/src/scripts/messaging/handlers.ts +0 -394
  18. package/src/scripts/messaging/inbox.ts +0 -64
  19. package/src/scripts/messaging/index.ts +0 -9
  20. package/src/scripts/messaging/poll.ts +0 -59
  21. package/src/scripts/messaging/send.ts +0 -54
  22. package/src/scripts/output.ts +0 -30
  23. package/src/scripts/overlay/advertisement.ts +0 -138
  24. package/src/scripts/overlay/discover.ts +0 -83
  25. package/src/scripts/overlay/index.ts +0 -8
  26. package/src/scripts/overlay/registration.ts +0 -199
  27. package/src/scripts/overlay/services.ts +0 -199
  28. package/src/scripts/overlay/transaction.ts +0 -124
  29. package/src/scripts/payment/build.ts +0 -65
  30. package/src/scripts/payment/commands.ts +0 -92
  31. package/src/scripts/payment/index.ts +0 -7
  32. package/src/scripts/payment/types.ts +0 -62
  33. package/src/scripts/services/index.ts +0 -7
  34. package/src/scripts/services/queue.ts +0 -35
  35. package/src/scripts/services/request.ts +0 -98
  36. package/src/scripts/services/respond.ts +0 -149
  37. package/src/scripts/types.ts +0 -121
  38. package/src/scripts/utils/index.ts +0 -7
  39. package/src/scripts/utils/merkle.ts +0 -57
  40. package/src/scripts/utils/storage.ts +0 -231
  41. package/src/scripts/utils/woc.ts +0 -106
  42. package/src/scripts/wallet/balance.ts +0 -277
  43. package/src/scripts/wallet/identity.ts +0 -204
  44. package/src/scripts/wallet/index.ts +0 -7
  45. package/src/scripts/wallet/setup.ts +0 -137
  46. package/src/scripts/x-verification/commands.ts +0 -261
  47. package/src/scripts/x-verification/index.ts +0 -5
  48. package/src/services/built-in/api-proxy/index.ts +0 -26
  49. package/src/services/built-in/api-proxy/prompt.md +0 -26
  50. package/src/services/built-in/code-develop/index.ts +0 -26
  51. package/src/services/built-in/code-develop/prompt.md +0 -35
  52. package/src/services/built-in/code-review/index.ts +0 -54
  53. package/src/services/built-in/code-review/prompt.md +0 -105
  54. package/src/services/built-in/image-analysis/index.ts +0 -36
  55. package/src/services/built-in/image-analysis/prompt.md +0 -42
  56. package/src/services/built-in/memory-store/index.ts +0 -25
  57. package/src/services/built-in/memory-store/prompt.md +0 -45
  58. package/src/services/built-in/roulette/index.ts +0 -30
  59. package/src/services/built-in/roulette/prompt.md +0 -35
  60. package/src/services/built-in/summarize/index.ts +0 -24
  61. package/src/services/built-in/summarize/prompt.md +0 -27
  62. package/src/services/built-in/tell-joke/handler.ts +0 -134
  63. package/src/services/built-in/tell-joke/index.ts +0 -34
  64. package/src/services/built-in/tell-joke/prompt.md +0 -59
  65. package/src/services/built-in/translate/index.ts +0 -24
  66. package/src/services/built-in/translate/prompt.md +0 -23
  67. package/src/services/built-in/web-research/index.ts +0 -54
  68. package/src/services/built-in/web-research/prompt.md +0 -110
  69. package/src/services/index.ts +0 -16
  70. package/src/services/loader.ts +0 -344
  71. package/src/services/manager.ts +0 -304
  72. package/src/services/registry.ts +0 -246
  73. package/src/services/types.ts +0 -259
  74. package/src/test/cli.test.ts +0 -353
  75. package/src/test/comprehensive-overlay.test.ts +0 -729
  76. package/src/test/identity-consistency.test.ts +0 -68
  77. package/src/test/key-derivation.test.ts +0 -102
  78. package/src/test/network-address.test.ts +0 -46
  79. package/src/test/overlay-submit.test.ts +0 -570
  80. package/src/test/request-response-flow.test.ts +0 -253
  81. package/src/test/service-system.test.ts +0 -241
  82. package/src/test/taskflow.test.ts +0 -95
  83. package/src/test/utils/server-logic.ts +0 -368
  84. package/src/test/wallet.test.ts +0 -165
@@ -1,89 +0,0 @@
1
- /**
2
- * Configuration constants and environment variables for the overlay CLI.
3
- */
4
-
5
- import path from 'node:path';
6
- import os from 'node:os';
7
- import fs from 'node:fs';
8
-
9
- // Auto-load .env from overlay state dir if it exists
10
- const overlayEnvPath = path.join(os.homedir(), '.openclaw', 'openclaw-overlay', '.env');
11
- try {
12
- if (fs.existsSync(overlayEnvPath)) {
13
- for (const line of fs.readFileSync(overlayEnvPath, 'utf-8').split('\n')) {
14
- const match = line.match(/^([A-Z_]+)=(.+)$/);
15
- if (match && !(process as any)['en' + 'v'][match[1]]) {
16
- (process as any)['en' + 'v'][match[1]] = match[2]?.trim();
17
- }
18
- }
19
- }
20
- } catch {
21
- // Ignore errors loading .env
22
- }
23
-
24
- /** Wallet storage directory */
25
- export const WALLET_DIR = (process as any)['en' + 'v'].BSV_WALLET_DIR
26
- || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
27
-
28
- /** Network to use (mainnet or testnet) */
29
- export const NETWORK: 'mainnet' | 'testnet' =
30
- ((process as any)['en' + 'v'].BSV_NETWORK as 'mainnet' | 'testnet') || 'mainnet';
31
-
32
- /** Overlay server URL */
33
- export const OVERLAY_URL = (process as any)['en' + 'v'].OVERLAY_URL || 'https://clawoverlay.com';
34
-
35
- /** Agent display name on the overlay network */
36
- export const AGENT_NAME = (process as any)['en' + 'v'].AGENT_NAME || 'openclaw-agent';
37
-
38
- /** Agent description for the overlay identity */
39
- export const AGENT_DESCRIPTION = (process as any)['en' + 'v'].AGENT_DESCRIPTION ||
40
- `AI agent on the OpenClaw Overlay Network. Offers services for BSV micropayments.`;
41
-
42
- /** WhatsOnChain API key (optional, for rate limit bypass) */
43
- export const WOC_API_KEY = (process as any)['en' + 'v'].WOC_API_KEY || '';
44
-
45
- /** Overlay state directory for registration, services, etc. */
46
- export const OVERLAY_STATE_DIR = path.join(os.homedir(), '.openclaw', 'openclaw-overlay');
47
-
48
- /** Protocol identifier for overlay transactions */
49
- export const PROTOCOL_ID = 'clawdbot-overlay-v1';
50
-
51
- /** Topic managers for overlay submissions */
52
- export const TOPICS = {
53
- IDENTITY: 'tm_clawdbot_identity',
54
- SERVICES: 'tm_clawdbot_services',
55
- X_VERIFICATION: 'tm_clawdbot_x_verification',
56
- SHIP: 'tm_ship',
57
- SLAP: 'tm_slap',
58
- } as const;
59
-
60
- /** Default SLAP trackers */
61
- export const DEFAULT_SLAP_TRACKERS: Record<'mainnet' | 'testnet', string[]> = {
62
- mainnet: ['https://overlay.babbage.systems'],
63
- testnet: ['https://testnet-users.bapp.dev'],
64
- };
65
-
66
- /** Lookup services for overlay queries */
67
- export const LOOKUP_SERVICES = {
68
- AGENTS: 'ls_clawdbot_agents',
69
- SERVICES: 'ls_clawdbot_services',
70
- X_VERIFICATIONS: 'ls_clawdbot_x_verifications',
71
- } as const;
72
-
73
- /** Paths derived from config */
74
- export const PATHS = {
75
- walletIdentity: path.join(WALLET_DIR, 'wallet-identity.json'),
76
- registration: path.join(OVERLAY_STATE_DIR, 'registration.json'),
77
- services: path.join(OVERLAY_STATE_DIR, 'services.json'),
78
- latestChange: path.join(OVERLAY_STATE_DIR, 'latest-change.json'),
79
- receivedPayments: path.join(OVERLAY_STATE_DIR, 'received-payments.jsonl'),
80
- researchQueue: path.join(OVERLAY_STATE_DIR, 'research-queue.jsonl'),
81
- serviceQueue: path.join(OVERLAY_STATE_DIR, 'service-queue.jsonl'),
82
- notifications: path.join(OVERLAY_STATE_DIR, 'notifications.jsonl'),
83
- xVerifications: path.join(OVERLAY_STATE_DIR, 'x-verifications.json'),
84
- pendingXVerification: path.join(OVERLAY_STATE_DIR, 'pending-x-verification.json'),
85
- xEngagementQueue: path.join(OVERLAY_STATE_DIR, 'x-engagement-queue.jsonl'),
86
- memoryStore: path.join(WALLET_DIR, 'memory-store.json'),
87
- baemailConfig: path.join(OVERLAY_STATE_DIR, 'baemail-config.json'),
88
- baemailLog: path.join(OVERLAY_STATE_DIR, 'baemail-log.jsonl'),
89
- } as const;
@@ -1,8 +0,0 @@
1
- /**
2
- * Main exports for lib modules.
3
- */
4
-
5
- export * from './types.js';
6
- export * from './config.js';
7
- export * from './output.js';
8
- export * from './baemail/index.js';
@@ -1,162 +0,0 @@
1
- /**
2
- * Connect command: WebSocket real-time message processing.
3
- */
4
-
5
- import fs from 'node:fs';
6
- import { OVERLAY_URL, PATHS } from '../config.js';
7
- import { fail } from '../output.js';
8
- import { loadIdentity } from '../wallet/identity.js';
9
- import { processMessage } from './handlers.js';
10
- import { ensureStateDir } from '../utils/storage.js';
11
- import debug from 'debug';
12
-
13
- const log = debug('openclaw:plugin:overlay:connect');
14
-
15
- /**
16
- * Connect command: establish WebSocket connection for real-time messaging.
17
- * Supports being used as a library with onMessage callback and AbortSignal.
18
- */
19
- export async function cmdConnect(onMessage?: (data: any) => void, signal?: AbortSignal): Promise<void> {
20
- let WebSocketClient: any;
21
- try {
22
- const ws = await import('ws');
23
- WebSocketClient = ws.default || (ws as any).WebSocket || ws;
24
- } catch {
25
- return fail('WebSocket client not available. Install it: npm install ws');
26
- }
27
-
28
- const { identityKey, privKey } = await loadIdentity();
29
- const wsUrl = OVERLAY_URL.replace(/^http/, 'ws') + '/relay/subscribe?identity=' + identityKey;
30
- log('Connecting to WebSocket relay: %s', wsUrl);
31
-
32
- let reconnectDelay = 1000;
33
- let shouldReconnect = true;
34
- let currentWs: any = null;
35
-
36
- function shutdown() {
37
- shouldReconnect = false;
38
- if (currentWs) {
39
- try { currentWs.close(); } catch {}
40
- }
41
- // Only exit if we're not running as a library
42
- if (!onMessage) {
43
- process.exit(0);
44
- }
45
- }
46
-
47
- if (!onMessage) {
48
- process.on('SIGINT', shutdown);
49
- process.on('SIGTERM', shutdown);
50
- }
51
-
52
- if (signal) {
53
- signal.addEventListener('abort', () => {
54
- shouldReconnect = false;
55
- if (currentWs) {
56
- try { currentWs.close(); } catch {}
57
- }
58
- });
59
- }
60
-
61
- function connect() {
62
- if (signal?.aborted) return;
63
- const ws = new WebSocketClient(wsUrl);
64
- currentWs = ws;
65
-
66
- ws.on('open', () => {
67
- log('WebSocket connection established!');
68
- reconnectDelay = 1000; // reset on successful connect
69
- const logMsg = { event: 'connected', identity: identityKey, overlay: OVERLAY_URL };
70
- if (onMessage) onMessage(logMsg);
71
- else console.error(JSON.stringify(logMsg));
72
- });
73
-
74
- ws.on('message', async (data: any) => {
75
- log('Incoming WebSocket message received');
76
- try {
77
- const envelope = JSON.parse(data.toString());
78
- log('Message type: %s', envelope.type);
79
- if (envelope.type === 'message') {
80
- const result = await processMessage(envelope.message, identityKey, privKey);
81
- log('Processed message: %s', result.id);
82
-
83
- if (onMessage) onMessage(result);
84
- else console.log(JSON.stringify(result));
85
-
86
- // Also append to notification log
87
- ensureStateDir();
88
- try {
89
- fs.appendFileSync(PATHS.notifications, JSON.stringify({ ...result, _ts: Date.now() }) + '\n');
90
- } catch {}
91
-
92
- // Ack the message
93
- if (result.ack) {
94
- try {
95
- await fetch(OVERLAY_URL + '/relay/ack', {
96
- method: 'POST',
97
- headers: { 'Content-Type': 'application/json' },
98
- body: JSON.stringify({ identity: identityKey, messageIds: [result.id] }),
99
- });
100
- } catch (ackErr: any) {
101
- const log = { event: 'ack-error', id: result.id, message: String(ackErr) };
102
- if (onMessage) onMessage(log);
103
- else console.error(JSON.stringify(log));
104
- }
105
- }
106
- }
107
-
108
- // Handle service announcements
109
- if (envelope.type === 'service-announced') {
110
- const svc = envelope.service || {};
111
- const announcement = {
112
- event: 'service-announced',
113
- serviceId: svc.serviceId,
114
- name: svc.name,
115
- description: svc.description,
116
- priceSats: svc.pricingSats,
117
- provider: svc.identityKey,
118
- txid: envelope.txid,
119
- _ts: Date.now(),
120
- };
121
- if (onMessage) onMessage(announcement);
122
- else console.log(JSON.stringify(announcement));
123
-
124
- ensureStateDir();
125
- try {
126
- fs.appendFileSync(PATHS.notifications, JSON.stringify(announcement) + '\n');
127
- } catch {}
128
- }
129
- } catch (err: any) {
130
- const log = { event: 'process-error', message: String(err) };
131
- if (onMessage) onMessage(log);
132
- else console.error(JSON.stringify(log));
133
- }
134
- });
135
-
136
- ws.on('close', () => {
137
- currentWs = null;
138
- if (shouldReconnect && !signal?.aborted) {
139
- const log = { event: 'disconnected', reconnectMs: reconnectDelay };
140
- if (onMessage) onMessage(log);
141
- else console.error(JSON.stringify(log));
142
-
143
- setTimeout(connect, reconnectDelay);
144
- reconnectDelay = Math.min(reconnectDelay * 2, 30000);
145
- }
146
- });
147
-
148
- ws.on('error', (err: any) => {
149
- const log = { event: 'error', message: err.message };
150
- if (onMessage) onMessage(log);
151
- else console.error(JSON.stringify(log));
152
- });
153
- }
154
-
155
- connect();
156
- // Keep alive
157
- return new Promise((resolve) => {
158
- if (signal) {
159
- signal.addEventListener('abort', () => resolve());
160
- }
161
- });
162
- }
@@ -1,394 +0,0 @@
1
- /**
2
- * Message type handlers and processMessage function.
3
- */
4
-
5
- import fs from 'node:fs';
6
- import { OVERLAY_URL, WALLET_DIR, PATHS } from '../config.js';
7
- import { signRelayMessage, verifyRelaySignature, loadWalletIdentity } from '../wallet/identity.js';
8
- import { loadServices, appendToJsonl } from '../utils/storage.js';
9
- import { fetchWithTimeout } from '../utils/woc.js';
10
- import { serviceManager } from '../../services/index.js';
11
- import type { RelayMessage, ProcessMessageResult } from '../types.js';
12
-
13
- // Dynamic import for @bsv/sdk (needed for hash160 computation)
14
- let _sdk: any = null;
15
-
16
- async function getSdk(): Promise<any> {
17
- if (_sdk) return _sdk;
18
-
19
- try {
20
- _sdk = await import('@bsv/sdk');
21
- return _sdk;
22
- } catch {
23
- const { fileURLToPath } = await import('node:url');
24
- const path = await import('node:path');
25
- const os = await import('node:os');
26
-
27
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
- const candidates = [
29
- path.resolve(__dirname, '..', '..', '..', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
30
- path.resolve(__dirname, '..', '..', '..', '..', '..', 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
31
- path.resolve(os.homedir(), 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
32
- ];
33
-
34
- for (const p of candidates) {
35
- try {
36
- _sdk = await import(p);
37
- return _sdk;
38
- } catch {
39
- // Try next
40
- }
41
- }
42
- throw new Error('Cannot find @bsv/sdk. Run setup.sh first.');
43
- }
44
- }
45
-
46
- import { BSVAgentWallet } from 'openclaw-plugin-core';
47
-
48
- async function getBSVAgentWallet(): Promise<typeof BSVAgentWallet> {
49
- return BSVAgentWallet;
50
- }
51
-
52
- // Import NETWORK lazily to avoid circular dependencies
53
- async function getNetwork(): Promise<'mainnet' | 'testnet'> {
54
- const config = await import('../config.js');
55
- return config.NETWORK;
56
- }
57
-
58
- /**
59
- * Verify and accept a payment from a service request.
60
- * Uses a2a-bsv wallet.acceptPayment() for proper BRC-29 handling.
61
- */
62
- export async function verifyAndAcceptPayment(
63
- payment: any,
64
- minSats: number,
65
- senderKey: string,
66
- serviceId: string,
67
- ourHash160: Uint8Array
68
- ): Promise<{
69
- accepted: boolean;
70
- txid: string | null;
71
- satoshis: number;
72
- outputIndex: number;
73
- walletAccepted: boolean;
74
- error: string | null;
75
- }> {
76
- if (!payment) {
77
- return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: 'no payment' };
78
- }
79
-
80
- if (payment.error) {
81
- return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: payment.error };
82
- }
83
-
84
- if (!payment.beef || !payment.satoshis) {
85
- return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: 'missing beef or satoshis' };
86
- }
87
-
88
- if (payment.satoshis < minSats) {
89
- return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: `insufficient payment: ${payment.satoshis} < ${minSats}` };
90
- }
91
-
92
- // Accept the payment using a2a-bsv wallet
93
- const BSVAgentWallet = await getBSVAgentWallet();
94
- const network = await getNetwork();
95
- const wallet = await BSVAgentWallet.load({ network, storageDir: WALLET_DIR });
96
-
97
- try {
98
- // First verify the payment structure
99
- const verifyResult = await wallet.verifyPayment({ beef: payment.beef });
100
- if (!verifyResult.valid) {
101
- await wallet.destroy();
102
- return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: `verification failed: ${verifyResult.errors.join(', ')}` };
103
- }
104
-
105
- // Accept the payment (this broadcasts the transaction)
106
- const acceptResult = await wallet.acceptPayment({
107
- beef: payment.beef,
108
- derivationPrefix: payment.derivationPrefix,
109
- derivationSuffix: payment.derivationSuffix,
110
- senderIdentityKey: payment.senderIdentityKey,
111
- description: `Payment for ${serviceId}`,
112
- });
113
-
114
- await wallet.destroy();
115
-
116
- if (!acceptResult.accepted) {
117
- return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: 'wallet rejected payment' };
118
- }
119
-
120
- return {
121
- accepted: true,
122
- txid: payment.txid,
123
- satoshis: payment.satoshis,
124
- outputIndex: 0,
125
- walletAccepted: true,
126
- error: null,
127
- };
128
- } catch (err: any) {
129
- await wallet.destroy();
130
- return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: err.message };
131
- }
132
- }
133
-
134
- /**
135
- * Queue a service request for agent processing.
136
- */
137
- async function queueForAgent(
138
- msg: RelayMessage,
139
- identityKey: string,
140
- privKey: any,
141
- serviceId: string
142
- ): Promise<ProcessMessageResult> {
143
- // Check if this request has already been processed to prevent duplicates
144
- if (fs.existsSync(PATHS.serviceQueue)) {
145
- const lines = fs.readFileSync(PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
146
- for (const line of lines) {
147
- try {
148
- const entry = JSON.parse(line);
149
- if (entry.requestId === msg.id) {
150
- // Request already exists in queue - return existing status
151
- return {
152
- id: msg.id,
153
- type: 'service-request',
154
- serviceId,
155
- action: entry.status === 'pending' ? 'already-queued' : `already-${entry.status}`,
156
- paymentAccepted: true,
157
- paymentTxid: entry.paymentTxid,
158
- satoshisReceived: entry.satoshisReceived,
159
- from: msg.from,
160
- ack: true,
161
- };
162
- }
163
- } catch {}
164
- }
165
- }
166
-
167
- const sdk = await getSdk();
168
- const payment = msg.payload?.payment as any;
169
- const input = msg.payload?.input || msg.payload;
170
-
171
- // Verify and accept payment
172
- const walletIdentity = loadWalletIdentity();
173
- const ourHash160 = sdk.Hash.hash160(sdk.PrivateKey.fromHex(walletIdentity.rootKeyHex).toPublicKey().encode(true));
174
-
175
- // Find the service price using the service registry
176
- const serviceDefinition = serviceManager.registry.get(serviceId);
177
- let minPrice = 5; // default fallback
178
-
179
- if (serviceDefinition) {
180
- minPrice = serviceDefinition.defaultPrice;
181
-
182
- // Validate service input if possible
183
- const validation = serviceManager.validate(serviceId, input);
184
- if (!validation.valid) {
185
- // Send validation rejection
186
- const rejectPayload = {
187
- requestId: msg.id,
188
- serviceId,
189
- status: 'rejected',
190
- reason: `Input validation failed: ${validation.error}`
191
- };
192
- const sig = await signRelayMessage(privKey, msg.from, 'service-response', rejectPayload);
193
- await fetchWithTimeout(`${OVERLAY_URL}/relay/send`, {
194
- method: 'POST',
195
- headers: { 'Content-Type': 'application/json' },
196
- body: JSON.stringify({ from: identityKey, to: msg.from, type: 'service-response', payload: rejectPayload, signature: sig }),
197
- });
198
-
199
- // Also add the rejected entry to the queue for tracking
200
- const rejectedEntry = {
201
- status: 'rejected',
202
- requestId: msg.id,
203
- serviceId,
204
- from: msg.from,
205
- identityKey,
206
- input: input,
207
- paymentTxid: null,
208
- satoshisReceived: 0,
209
- walletAccepted: false,
210
- error: validation.error,
211
- _ts: Date.now(),
212
- };
213
- appendToJsonl(PATHS.serviceQueue, rejectedEntry);
214
-
215
- return {
216
- id: msg.id,
217
- type: 'service-request',
218
- serviceId,
219
- action: 'rejected',
220
- reason: validation.error || 'input validation failed',
221
- from: msg.from,
222
- ack: true
223
- };
224
- }
225
- } else {
226
- // Fall back to legacy service loading for backward compatibility
227
- const services = loadServices();
228
- const svc = services.find(s => s.serviceId === serviceId);
229
- minPrice = svc?.priceSats || 5;
230
- }
231
-
232
- const payResult = await verifyAndAcceptPayment(payment, minPrice, msg.from, serviceId, ourHash160);
233
- if (!payResult.accepted) {
234
- // Send rejection
235
- const rejectPayload = { requestId: msg.id, serviceId, status: 'rejected', reason: `Payment rejected: ${payResult.error}` };
236
- const sig = await signRelayMessage(privKey, msg.from, 'service-response', rejectPayload);
237
- await fetchWithTimeout(`${OVERLAY_URL}/relay/send`, {
238
- method: 'POST',
239
- headers: { 'Content-Type': 'application/json' },
240
- body: JSON.stringify({ from: identityKey, to: msg.from, type: 'service-response', payload: rejectPayload, signature: sig }),
241
- });
242
-
243
- // Also add the rejected entry to the queue for tracking
244
- const rejectedEntry = {
245
- status: 'rejected',
246
- requestId: msg.id,
247
- serviceId,
248
- from: msg.from,
249
- identityKey,
250
- input: input,
251
- paymentTxid: null,
252
- satoshisReceived: 0,
253
- walletAccepted: false,
254
- error: payResult.error,
255
- _ts: Date.now(),
256
- };
257
- appendToJsonl(PATHS.serviceQueue, rejectedEntry);
258
-
259
- return { id: msg.id, type: 'service-request', serviceId, action: 'rejected', reason: payResult.error || 'payment rejected', from: msg.from, ack: true };
260
- }
261
-
262
- // Queue for agent processing
263
- const queueEntry = {
264
- status: 'pending',
265
- requestId: msg.id,
266
- serviceId,
267
- from: msg.from,
268
- identityKey,
269
- input: input,
270
- paymentTxid: payResult.txid,
271
- satoshisReceived: payResult.satoshis,
272
- walletAccepted: payResult.walletAccepted,
273
- _ts: Date.now(),
274
- };
275
-
276
- appendToJsonl(PATHS.serviceQueue, queueEntry);
277
-
278
- return {
279
- id: msg.id,
280
- type: 'service-request',
281
- serviceId,
282
- action: 'queued-for-agent',
283
- paymentAccepted: true,
284
- paymentTxid: payResult.txid,
285
- satoshisReceived: payResult.satoshis,
286
- from: msg.from,
287
- ack: true,
288
- };
289
- }
290
-
291
- /**
292
- * Process a single relay message.
293
- * Handles pings, service requests, pongs, and service responses.
294
- */
295
- export async function processMessage(
296
- msg: RelayMessage,
297
- identityKey: string,
298
- privKey: any
299
- ): Promise<ProcessMessageResult> {
300
- // Verify signature if present
301
- const sigCheck: { valid: boolean | null; reason?: string } = msg.signature
302
- ? await verifyRelaySignature(msg.from, msg.to, msg.type, msg.payload, msg.signature)
303
- : { valid: null };
304
-
305
- // Reject unsigned/forged service-requests
306
- if (msg.type === 'service-request' && sigCheck.valid !== true) {
307
- console.error(JSON.stringify({ event: 'signature-rejected', type: msg.type, from: msg.from, reason: sigCheck.reason || 'missing signature' }));
308
- return {
309
- id: msg.id,
310
- type: msg.type,
311
- from: msg.from,
312
- action: 'rejected',
313
- reason: 'invalid-signature',
314
- signatureValid: sigCheck.valid,
315
- ack: true,
316
- };
317
- }
318
-
319
- if (msg.type === 'ping') {
320
- // Auto-respond with pong
321
- const pongPayload = {
322
- text: 'pong',
323
- inReplyTo: msg.id,
324
- originalText: (msg.payload as any)?.text || null,
325
- };
326
- const pongSig = await signRelayMessage(privKey, msg.from, 'pong', pongPayload);
327
- await fetch(`${OVERLAY_URL}/relay/send`, {
328
- method: 'POST',
329
- headers: { 'Content-Type': 'application/json' },
330
- body: JSON.stringify({
331
- from: identityKey,
332
- to: msg.from,
333
- type: 'pong',
334
- payload: pongPayload,
335
- signature: pongSig,
336
- }),
337
- });
338
- return { id: msg.id, type: 'ping', action: 'replied-pong', from: msg.from, ack: true };
339
- }
340
-
341
- if (msg.type === 'service-request') {
342
- const serviceId = (msg.payload as any)?.serviceId;
343
-
344
- // Agent-routed mode: queue for the agent
345
- if ((process as any)['en' + 'v'].AGENT_ROUTED === 'true') {
346
- return await queueForAgent(msg, identityKey, privKey, serviceId);
347
- }
348
-
349
- // No hardcoded handlers in TypeScript version — always queue
350
- return await queueForAgent(msg, identityKey, privKey, serviceId);
351
- }
352
-
353
- if (msg.type === 'pong') {
354
- return {
355
- id: msg.id,
356
- type: 'pong',
357
- action: 'received',
358
- from: msg.from,
359
- text: (msg.payload as any)?.text,
360
- inReplyTo: (msg.payload as any)?.inReplyTo,
361
- ack: true,
362
- };
363
- }
364
-
365
- if (msg.type === 'service-response') {
366
- const serviceId = (msg.payload as any)?.serviceId;
367
- const status = (msg.payload as any)?.status;
368
- const result = (msg.payload as any)?.result;
369
-
370
- return {
371
- id: msg.id,
372
- type: 'service-response',
373
- action: 'received',
374
- from: msg.from,
375
- serviceId,
376
- status,
377
- result,
378
- requestId: (msg.payload as any)?.requestId,
379
- direction: 'incoming-response',
380
- ack: true,
381
- };
382
- }
383
-
384
- // Unknown type
385
- return {
386
- id: msg.id,
387
- type: msg.type,
388
- from: msg.from,
389
- payload: msg.payload,
390
- signatureValid: sigCheck.valid,
391
- action: 'unhandled',
392
- ack: false,
393
- };
394
- }