openclaw-overlay-plugin 0.7.22
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/README.md +406 -0
- package/SKILL.md +78 -0
- package/clawdbot.plugin.json +106 -0
- package/dist/cli-main.d.ts +7 -0
- package/dist/cli-main.js +192 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +14 -0
- package/dist/core/config.d.ts +11 -0
- package/dist/core/config.js +13 -0
- package/dist/core/index.d.ts +25 -0
- package/dist/core/index.js +26 -0
- package/dist/core/payment.d.ts +16 -0
- package/dist/core/payment.js +94 -0
- package/dist/core/types.d.ts +94 -0
- package/dist/core/types.js +4 -0
- package/dist/core/verify.d.ts +28 -0
- package/dist/core/verify.js +104 -0
- package/dist/core/wallet.d.ts +99 -0
- package/dist/core/wallet.js +219 -0
- package/dist/scripts/baemail/commands.d.ts +64 -0
- package/dist/scripts/baemail/commands.js +258 -0
- package/dist/scripts/baemail/handler.d.ts +36 -0
- package/dist/scripts/baemail/handler.js +284 -0
- package/dist/scripts/baemail/index.d.ts +5 -0
- package/dist/scripts/baemail/index.js +5 -0
- package/dist/scripts/config.d.ts +48 -0
- package/dist/scripts/config.js +68 -0
- package/dist/scripts/index.d.ts +7 -0
- package/dist/scripts/index.js +7 -0
- package/dist/scripts/messaging/connect.d.ts +8 -0
- package/dist/scripts/messaging/connect.js +114 -0
- package/dist/scripts/messaging/handlers.d.ts +21 -0
- package/dist/scripts/messaging/handlers.js +334 -0
- package/dist/scripts/messaging/inbox.d.ts +11 -0
- package/dist/scripts/messaging/inbox.js +51 -0
- package/dist/scripts/messaging/index.d.ts +8 -0
- package/dist/scripts/messaging/index.js +8 -0
- package/dist/scripts/messaging/poll.d.ts +7 -0
- package/dist/scripts/messaging/poll.js +52 -0
- package/dist/scripts/messaging/send.d.ts +7 -0
- package/dist/scripts/messaging/send.js +43 -0
- package/dist/scripts/output.d.ts +12 -0
- package/dist/scripts/output.js +19 -0
- package/dist/scripts/overlay/discover.d.ts +7 -0
- package/dist/scripts/overlay/discover.js +72 -0
- package/dist/scripts/overlay/index.d.ts +7 -0
- package/dist/scripts/overlay/index.js +7 -0
- package/dist/scripts/overlay/registration.d.ts +19 -0
- package/dist/scripts/overlay/registration.js +176 -0
- package/dist/scripts/overlay/services.d.ts +29 -0
- package/dist/scripts/overlay/services.js +167 -0
- package/dist/scripts/overlay/transaction.d.ts +42 -0
- package/dist/scripts/overlay/transaction.js +103 -0
- package/dist/scripts/payment/build.d.ts +24 -0
- package/dist/scripts/payment/build.js +54 -0
- package/dist/scripts/payment/commands.d.ts +15 -0
- package/dist/scripts/payment/commands.js +73 -0
- package/dist/scripts/payment/index.d.ts +6 -0
- package/dist/scripts/payment/index.js +6 -0
- package/dist/scripts/payment/types.d.ts +56 -0
- package/dist/scripts/payment/types.js +4 -0
- package/dist/scripts/services/index.d.ts +6 -0
- package/dist/scripts/services/index.js +6 -0
- package/dist/scripts/services/queue.d.ts +11 -0
- package/dist/scripts/services/queue.js +28 -0
- package/dist/scripts/services/request.d.ts +7 -0
- package/dist/scripts/services/request.js +82 -0
- package/dist/scripts/services/respond.d.ts +11 -0
- package/dist/scripts/services/respond.js +132 -0
- package/dist/scripts/types.d.ts +107 -0
- package/dist/scripts/types.js +4 -0
- package/dist/scripts/utils/index.d.ts +6 -0
- package/dist/scripts/utils/index.js +6 -0
- package/dist/scripts/utils/merkle.d.ts +12 -0
- package/dist/scripts/utils/merkle.js +47 -0
- package/dist/scripts/utils/storage.d.ts +66 -0
- package/dist/scripts/utils/storage.js +211 -0
- package/dist/scripts/utils/woc.d.ts +26 -0
- package/dist/scripts/utils/woc.js +91 -0
- package/dist/scripts/wallet/balance.d.ts +22 -0
- package/dist/scripts/wallet/balance.js +240 -0
- package/dist/scripts/wallet/identity.d.ts +70 -0
- package/dist/scripts/wallet/identity.js +151 -0
- package/dist/scripts/wallet/index.d.ts +6 -0
- package/dist/scripts/wallet/index.js +6 -0
- package/dist/scripts/wallet/setup.d.ts +15 -0
- package/dist/scripts/wallet/setup.js +105 -0
- package/dist/scripts/x-verification/commands.d.ts +27 -0
- package/dist/scripts/x-verification/commands.js +222 -0
- package/dist/scripts/x-verification/index.d.ts +4 -0
- package/dist/scripts/x-verification/index.js +4 -0
- package/dist/services/built-in/api-proxy/index.d.ts +6 -0
- package/dist/services/built-in/api-proxy/index.js +23 -0
- package/dist/services/built-in/code-develop/index.d.ts +6 -0
- package/dist/services/built-in/code-develop/index.js +23 -0
- package/dist/services/built-in/code-review/index.d.ts +10 -0
- package/dist/services/built-in/code-review/index.js +51 -0
- package/dist/services/built-in/image-analysis/index.d.ts +6 -0
- package/dist/services/built-in/image-analysis/index.js +33 -0
- package/dist/services/built-in/memory-store/index.d.ts +6 -0
- package/dist/services/built-in/memory-store/index.js +22 -0
- package/dist/services/built-in/roulette/index.d.ts +6 -0
- package/dist/services/built-in/roulette/index.js +27 -0
- package/dist/services/built-in/summarize/index.d.ts +6 -0
- package/dist/services/built-in/summarize/index.js +21 -0
- package/dist/services/built-in/tell-joke/handler.d.ts +7 -0
- package/dist/services/built-in/tell-joke/handler.js +122 -0
- package/dist/services/built-in/tell-joke/index.d.ts +9 -0
- package/dist/services/built-in/tell-joke/index.js +31 -0
- package/dist/services/built-in/translate/index.d.ts +6 -0
- package/dist/services/built-in/translate/index.js +21 -0
- package/dist/services/built-in/web-research/index.d.ts +9 -0
- package/dist/services/built-in/web-research/index.js +51 -0
- package/dist/services/index.d.ts +13 -0
- package/dist/services/index.js +14 -0
- package/dist/services/loader.d.ts +77 -0
- package/dist/services/loader.js +292 -0
- package/dist/services/manager.d.ts +86 -0
- package/dist/services/manager.js +255 -0
- package/dist/services/registry.d.ts +98 -0
- package/dist/services/registry.js +204 -0
- package/dist/services/types.d.ts +230 -0
- package/dist/services/types.js +30 -0
- package/dist/test/cli.test.d.ts +7 -0
- package/dist/test/cli.test.js +329 -0
- package/dist/test/comprehensive-overlay.test.d.ts +13 -0
- package/dist/test/comprehensive-overlay.test.js +593 -0
- package/dist/test/key-derivation.test.d.ts +12 -0
- package/dist/test/key-derivation.test.js +86 -0
- package/dist/test/overlay-submit.test.d.ts +10 -0
- package/dist/test/overlay-submit.test.js +460 -0
- package/dist/test/request-response-flow.test.d.ts +5 -0
- package/dist/test/request-response-flow.test.js +209 -0
- package/dist/test/service-system.test.d.ts +5 -0
- package/dist/test/service-system.test.js +190 -0
- package/dist/test/utils/server-logic.d.ts +98 -0
- package/dist/test/utils/server-logic.js +286 -0
- package/dist/test/wallet.test.d.ts +7 -0
- package/dist/test/wallet.test.js +146 -0
- package/index.ts +1965 -0
- package/openclaw.plugin.json +106 -0
- package/package.json +73 -0
- package/src/cli-main.ts +230 -0
- package/src/cli.ts +16 -0
- package/src/core/README.md +246 -0
- package/src/core/config.ts +21 -0
- package/src/core/index.ts +42 -0
- package/src/core/payment.ts +111 -0
- package/src/core/types.ts +102 -0
- package/src/core/verify.ts +119 -0
- package/src/core/wallet.ts +282 -0
- package/src/scripts/baemail/commands.ts +326 -0
- package/src/scripts/baemail/handler.ts +338 -0
- package/src/scripts/baemail/index.ts +6 -0
- package/src/scripts/config.ts +81 -0
- package/src/scripts/index.ts +8 -0
- package/src/scripts/messaging/connect.ts +121 -0
- package/src/scripts/messaging/handlers.ts +394 -0
- package/src/scripts/messaging/inbox.ts +64 -0
- package/src/scripts/messaging/index.ts +9 -0
- package/src/scripts/messaging/poll.ts +59 -0
- package/src/scripts/messaging/send.ts +54 -0
- package/src/scripts/output.ts +21 -0
- package/src/scripts/overlay/discover.ts +81 -0
- package/src/scripts/overlay/index.ts +8 -0
- package/src/scripts/overlay/registration.ts +199 -0
- package/src/scripts/overlay/services.ts +199 -0
- package/src/scripts/overlay/transaction.ts +124 -0
- package/src/scripts/payment/build.ts +65 -0
- package/src/scripts/payment/commands.ts +92 -0
- package/src/scripts/payment/index.ts +7 -0
- package/src/scripts/payment/types.ts +62 -0
- package/src/scripts/services/index.ts +7 -0
- package/src/scripts/services/queue.ts +35 -0
- package/src/scripts/services/request.ts +98 -0
- package/src/scripts/services/respond.ts +149 -0
- package/src/scripts/types.ts +121 -0
- package/src/scripts/utils/index.ts +7 -0
- package/src/scripts/utils/merkle.ts +57 -0
- package/src/scripts/utils/storage.ts +231 -0
- package/src/scripts/utils/woc.ts +106 -0
- package/src/scripts/wallet/balance.ts +277 -0
- package/src/scripts/wallet/identity.ts +203 -0
- package/src/scripts/wallet/index.ts +7 -0
- package/src/scripts/wallet/setup.ts +121 -0
- package/src/scripts/x-verification/commands.ts +256 -0
- package/src/scripts/x-verification/index.ts +5 -0
- package/src/services/built-in/api-proxy/index.ts +26 -0
- package/src/services/built-in/api-proxy/prompt.md +26 -0
- package/src/services/built-in/code-develop/index.ts +26 -0
- package/src/services/built-in/code-develop/prompt.md +35 -0
- package/src/services/built-in/code-review/index.ts +54 -0
- package/src/services/built-in/code-review/prompt.md +105 -0
- package/src/services/built-in/image-analysis/index.ts +36 -0
- package/src/services/built-in/image-analysis/prompt.md +42 -0
- package/src/services/built-in/memory-store/index.ts +25 -0
- package/src/services/built-in/memory-store/prompt.md +45 -0
- package/src/services/built-in/roulette/index.ts +30 -0
- package/src/services/built-in/roulette/prompt.md +35 -0
- package/src/services/built-in/summarize/index.ts +24 -0
- package/src/services/built-in/summarize/prompt.md +27 -0
- package/src/services/built-in/tell-joke/handler.ts +134 -0
- package/src/services/built-in/tell-joke/index.ts +34 -0
- package/src/services/built-in/tell-joke/prompt.md +59 -0
- package/src/services/built-in/translate/index.ts +24 -0
- package/src/services/built-in/translate/prompt.md +23 -0
- package/src/services/built-in/web-research/index.ts +54 -0
- package/src/services/built-in/web-research/prompt.md +110 -0
- package/src/services/index.ts +16 -0
- package/src/services/loader.ts +344 -0
- package/src/services/manager.ts +304 -0
- package/src/services/registry.ts +246 -0
- package/src/services/types.ts +259 -0
- package/src/test/cli.test.ts +352 -0
- package/src/test/comprehensive-overlay.test.ts +729 -0
- package/src/test/key-derivation.test.ts +102 -0
- package/src/test/overlay-submit.test.ts +570 -0
- package/src/test/request-response-flow.test.ts +252 -0
- package/src/test/service-system.test.ts +241 -0
- package/src/test/utils/server-logic.ts +368 -0
- package/src/test/wallet.test.ts +166 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message type handlers and processMessage function.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { OVERLAY_URL, WALLET_DIR, PATHS } from '../config.js';
|
|
6
|
+
import { signRelayMessage, verifyRelaySignature, loadWalletIdentity } from '../wallet/identity.js';
|
|
7
|
+
import { loadServices, appendToJsonl } from '../utils/storage.js';
|
|
8
|
+
import { fetchWithTimeout } from '../utils/woc.js';
|
|
9
|
+
import { serviceManager } from '../../services/index.js';
|
|
10
|
+
// Dynamic import for @bsv/sdk (needed for hash160 computation)
|
|
11
|
+
let _sdk = null;
|
|
12
|
+
async function getSdk() {
|
|
13
|
+
if (_sdk)
|
|
14
|
+
return _sdk;
|
|
15
|
+
try {
|
|
16
|
+
_sdk = await import('@bsv/sdk');
|
|
17
|
+
return _sdk;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
const { fileURLToPath } = await import('node:url');
|
|
21
|
+
const path = await import('node:path');
|
|
22
|
+
const os = await import('node:os');
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const candidates = [
|
|
25
|
+
path.resolve(__dirname, '..', '..', '..', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
|
|
26
|
+
path.resolve(__dirname, '..', '..', '..', '..', '..', 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
|
|
27
|
+
path.resolve(os.homedir(), 'a2a-bsv', 'packages', 'core', 'node_modules', '@bsv', 'sdk', 'dist', 'esm', 'mod.js'),
|
|
28
|
+
];
|
|
29
|
+
for (const p of candidates) {
|
|
30
|
+
try {
|
|
31
|
+
_sdk = await import(p);
|
|
32
|
+
return _sdk;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Try next
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw new Error('Cannot find @bsv/sdk. Run setup.sh first.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
import { BSVAgentWallet } from '../../core/index.js';
|
|
42
|
+
async function getBSVAgentWallet() {
|
|
43
|
+
return BSVAgentWallet;
|
|
44
|
+
}
|
|
45
|
+
// Import NETWORK lazily to avoid circular dependencies
|
|
46
|
+
async function getNetwork() {
|
|
47
|
+
const config = await import('../config.js');
|
|
48
|
+
return config.NETWORK;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Verify and accept a payment from a service request.
|
|
52
|
+
* Uses a2a-bsv wallet.acceptPayment() for proper BRC-29 handling.
|
|
53
|
+
*/
|
|
54
|
+
export async function verifyAndAcceptPayment(payment, minSats, senderKey, serviceId, ourHash160) {
|
|
55
|
+
if (!payment) {
|
|
56
|
+
return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: 'no payment' };
|
|
57
|
+
}
|
|
58
|
+
if (payment.error) {
|
|
59
|
+
return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: payment.error };
|
|
60
|
+
}
|
|
61
|
+
if (!payment.beef || !payment.satoshis) {
|
|
62
|
+
return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: 'missing beef or satoshis' };
|
|
63
|
+
}
|
|
64
|
+
if (payment.satoshis < minSats) {
|
|
65
|
+
return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: `insufficient payment: ${payment.satoshis} < ${minSats}` };
|
|
66
|
+
}
|
|
67
|
+
// Accept the payment using a2a-bsv wallet
|
|
68
|
+
const BSVAgentWallet = await getBSVAgentWallet();
|
|
69
|
+
const network = await getNetwork();
|
|
70
|
+
const wallet = await BSVAgentWallet.load({ network, storageDir: WALLET_DIR });
|
|
71
|
+
try {
|
|
72
|
+
// First verify the payment structure
|
|
73
|
+
const verifyResult = await wallet.verifyPayment({ beef: payment.beef });
|
|
74
|
+
if (!verifyResult.valid) {
|
|
75
|
+
await wallet.destroy();
|
|
76
|
+
return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: `verification failed: ${verifyResult.errors.join(', ')}` };
|
|
77
|
+
}
|
|
78
|
+
// Accept the payment (this broadcasts the transaction)
|
|
79
|
+
const acceptResult = await wallet.acceptPayment({
|
|
80
|
+
beef: payment.beef,
|
|
81
|
+
derivationPrefix: payment.derivationPrefix,
|
|
82
|
+
derivationSuffix: payment.derivationSuffix,
|
|
83
|
+
senderIdentityKey: payment.senderIdentityKey,
|
|
84
|
+
description: `Payment for ${serviceId}`,
|
|
85
|
+
});
|
|
86
|
+
await wallet.destroy();
|
|
87
|
+
if (!acceptResult.accepted) {
|
|
88
|
+
return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: 'wallet rejected payment' };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
accepted: true,
|
|
92
|
+
txid: payment.txid,
|
|
93
|
+
satoshis: payment.satoshis,
|
|
94
|
+
outputIndex: 0,
|
|
95
|
+
walletAccepted: true,
|
|
96
|
+
error: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
await wallet.destroy();
|
|
101
|
+
return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: err.message };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Queue a service request for agent processing.
|
|
106
|
+
*/
|
|
107
|
+
async function queueForAgent(msg, identityKey, privKey, serviceId) {
|
|
108
|
+
// Check if this request has already been processed to prevent duplicates
|
|
109
|
+
if (fs.existsSync(PATHS.serviceQueue)) {
|
|
110
|
+
const lines = fs.readFileSync(PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
try {
|
|
113
|
+
const entry = JSON.parse(line);
|
|
114
|
+
if (entry.requestId === msg.id) {
|
|
115
|
+
// Request already exists in queue - return existing status
|
|
116
|
+
return {
|
|
117
|
+
id: msg.id,
|
|
118
|
+
type: 'service-request',
|
|
119
|
+
serviceId,
|
|
120
|
+
action: entry.status === 'pending' ? 'already-queued' : `already-${entry.status}`,
|
|
121
|
+
paymentAccepted: true,
|
|
122
|
+
paymentTxid: entry.paymentTxid,
|
|
123
|
+
satoshisReceived: entry.satoshisReceived,
|
|
124
|
+
from: msg.from,
|
|
125
|
+
ack: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch { }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const sdk = await getSdk();
|
|
133
|
+
const payment = msg.payload?.payment;
|
|
134
|
+
const input = msg.payload?.input || msg.payload;
|
|
135
|
+
// Verify and accept payment
|
|
136
|
+
const walletIdentity = loadWalletIdentity();
|
|
137
|
+
const ourHash160 = sdk.Hash.hash160(sdk.PrivateKey.fromHex(walletIdentity.rootKeyHex).toPublicKey().encode(true));
|
|
138
|
+
// Find the service price using the service registry
|
|
139
|
+
const serviceDefinition = serviceManager.registry.get(serviceId);
|
|
140
|
+
let minPrice = 5; // default fallback
|
|
141
|
+
if (serviceDefinition) {
|
|
142
|
+
minPrice = serviceDefinition.defaultPrice;
|
|
143
|
+
// Validate service input if possible
|
|
144
|
+
const validation = serviceManager.validate(serviceId, input);
|
|
145
|
+
if (!validation.valid) {
|
|
146
|
+
// Send validation rejection
|
|
147
|
+
const rejectPayload = {
|
|
148
|
+
requestId: msg.id,
|
|
149
|
+
serviceId,
|
|
150
|
+
status: 'rejected',
|
|
151
|
+
reason: `Input validation failed: ${validation.error}`
|
|
152
|
+
};
|
|
153
|
+
const sig = await signRelayMessage(privKey, msg.from, 'service-response', rejectPayload);
|
|
154
|
+
await fetchWithTimeout(`${OVERLAY_URL}/relay/send`, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: { 'Content-Type': 'application/json' },
|
|
157
|
+
body: JSON.stringify({ from: identityKey, to: msg.from, type: 'service-response', payload: rejectPayload, signature: sig }),
|
|
158
|
+
});
|
|
159
|
+
// Also add the rejected entry to the queue for tracking
|
|
160
|
+
const rejectedEntry = {
|
|
161
|
+
status: 'rejected',
|
|
162
|
+
requestId: msg.id,
|
|
163
|
+
serviceId,
|
|
164
|
+
from: msg.from,
|
|
165
|
+
identityKey,
|
|
166
|
+
input: input,
|
|
167
|
+
paymentTxid: null,
|
|
168
|
+
satoshisReceived: 0,
|
|
169
|
+
walletAccepted: false,
|
|
170
|
+
error: validation.error,
|
|
171
|
+
_ts: Date.now(),
|
|
172
|
+
};
|
|
173
|
+
appendToJsonl(PATHS.serviceQueue, rejectedEntry);
|
|
174
|
+
return {
|
|
175
|
+
id: msg.id,
|
|
176
|
+
type: 'service-request',
|
|
177
|
+
serviceId,
|
|
178
|
+
action: 'rejected',
|
|
179
|
+
reason: validation.error || 'input validation failed',
|
|
180
|
+
from: msg.from,
|
|
181
|
+
ack: true
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// Fall back to legacy service loading for backward compatibility
|
|
187
|
+
const services = loadServices();
|
|
188
|
+
const svc = services.find(s => s.serviceId === serviceId);
|
|
189
|
+
minPrice = svc?.priceSats || 5;
|
|
190
|
+
}
|
|
191
|
+
const payResult = await verifyAndAcceptPayment(payment, minPrice, msg.from, serviceId, ourHash160);
|
|
192
|
+
if (!payResult.accepted) {
|
|
193
|
+
// Send rejection
|
|
194
|
+
const rejectPayload = { requestId: msg.id, serviceId, status: 'rejected', reason: `Payment rejected: ${payResult.error}` };
|
|
195
|
+
const sig = await signRelayMessage(privKey, msg.from, 'service-response', rejectPayload);
|
|
196
|
+
await fetchWithTimeout(`${OVERLAY_URL}/relay/send`, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers: { 'Content-Type': 'application/json' },
|
|
199
|
+
body: JSON.stringify({ from: identityKey, to: msg.from, type: 'service-response', payload: rejectPayload, signature: sig }),
|
|
200
|
+
});
|
|
201
|
+
// Also add the rejected entry to the queue for tracking
|
|
202
|
+
const rejectedEntry = {
|
|
203
|
+
status: 'rejected',
|
|
204
|
+
requestId: msg.id,
|
|
205
|
+
serviceId,
|
|
206
|
+
from: msg.from,
|
|
207
|
+
identityKey,
|
|
208
|
+
input: input,
|
|
209
|
+
paymentTxid: null,
|
|
210
|
+
satoshisReceived: 0,
|
|
211
|
+
walletAccepted: false,
|
|
212
|
+
error: payResult.error,
|
|
213
|
+
_ts: Date.now(),
|
|
214
|
+
};
|
|
215
|
+
appendToJsonl(PATHS.serviceQueue, rejectedEntry);
|
|
216
|
+
return { id: msg.id, type: 'service-request', serviceId, action: 'rejected', reason: payResult.error || 'payment rejected', from: msg.from, ack: true };
|
|
217
|
+
}
|
|
218
|
+
// Queue for agent processing
|
|
219
|
+
const queueEntry = {
|
|
220
|
+
status: 'pending',
|
|
221
|
+
requestId: msg.id,
|
|
222
|
+
serviceId,
|
|
223
|
+
from: msg.from,
|
|
224
|
+
identityKey,
|
|
225
|
+
input: input,
|
|
226
|
+
paymentTxid: payResult.txid,
|
|
227
|
+
satoshisReceived: payResult.satoshis,
|
|
228
|
+
walletAccepted: payResult.walletAccepted,
|
|
229
|
+
_ts: Date.now(),
|
|
230
|
+
};
|
|
231
|
+
appendToJsonl(PATHS.serviceQueue, queueEntry);
|
|
232
|
+
return {
|
|
233
|
+
id: msg.id,
|
|
234
|
+
type: 'service-request',
|
|
235
|
+
serviceId,
|
|
236
|
+
action: 'queued-for-agent',
|
|
237
|
+
paymentAccepted: true,
|
|
238
|
+
paymentTxid: payResult.txid,
|
|
239
|
+
satoshisReceived: payResult.satoshis,
|
|
240
|
+
from: msg.from,
|
|
241
|
+
ack: true,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Process a single relay message.
|
|
246
|
+
* Handles pings, service requests, pongs, and service responses.
|
|
247
|
+
*/
|
|
248
|
+
export async function processMessage(msg, identityKey, privKey) {
|
|
249
|
+
// Verify signature if present
|
|
250
|
+
const sigCheck = msg.signature
|
|
251
|
+
? await verifyRelaySignature(msg.from, msg.to, msg.type, msg.payload, msg.signature)
|
|
252
|
+
: { valid: null };
|
|
253
|
+
// Reject unsigned/forged service-requests
|
|
254
|
+
if (msg.type === 'service-request' && sigCheck.valid !== true) {
|
|
255
|
+
console.error(JSON.stringify({ event: 'signature-rejected', type: msg.type, from: msg.from, reason: sigCheck.reason || 'missing signature' }));
|
|
256
|
+
return {
|
|
257
|
+
id: msg.id,
|
|
258
|
+
type: msg.type,
|
|
259
|
+
from: msg.from,
|
|
260
|
+
action: 'rejected',
|
|
261
|
+
reason: 'invalid-signature',
|
|
262
|
+
signatureValid: sigCheck.valid,
|
|
263
|
+
ack: true,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (msg.type === 'ping') {
|
|
267
|
+
// Auto-respond with pong
|
|
268
|
+
const pongPayload = {
|
|
269
|
+
text: 'pong',
|
|
270
|
+
inReplyTo: msg.id,
|
|
271
|
+
originalText: msg.payload?.text || null,
|
|
272
|
+
};
|
|
273
|
+
const pongSig = await signRelayMessage(privKey, msg.from, 'pong', pongPayload);
|
|
274
|
+
await fetch(`${OVERLAY_URL}/relay/send`, {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: { 'Content-Type': 'application/json' },
|
|
277
|
+
body: JSON.stringify({
|
|
278
|
+
from: identityKey,
|
|
279
|
+
to: msg.from,
|
|
280
|
+
type: 'pong',
|
|
281
|
+
payload: pongPayload,
|
|
282
|
+
signature: pongSig,
|
|
283
|
+
}),
|
|
284
|
+
});
|
|
285
|
+
return { id: msg.id, type: 'ping', action: 'replied-pong', from: msg.from, ack: true };
|
|
286
|
+
}
|
|
287
|
+
if (msg.type === 'service-request') {
|
|
288
|
+
const serviceId = msg.payload?.serviceId;
|
|
289
|
+
// Agent-routed mode: queue for the agent
|
|
290
|
+
if (process.env.AGENT_ROUTED === 'true') {
|
|
291
|
+
return await queueForAgent(msg, identityKey, privKey, serviceId);
|
|
292
|
+
}
|
|
293
|
+
// No hardcoded handlers in TypeScript version — always queue
|
|
294
|
+
return await queueForAgent(msg, identityKey, privKey, serviceId);
|
|
295
|
+
}
|
|
296
|
+
if (msg.type === 'pong') {
|
|
297
|
+
return {
|
|
298
|
+
id: msg.id,
|
|
299
|
+
type: 'pong',
|
|
300
|
+
action: 'received',
|
|
301
|
+
from: msg.from,
|
|
302
|
+
text: msg.payload?.text,
|
|
303
|
+
inReplyTo: msg.payload?.inReplyTo,
|
|
304
|
+
ack: true,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (msg.type === 'service-response') {
|
|
308
|
+
const serviceId = msg.payload?.serviceId;
|
|
309
|
+
const status = msg.payload?.status;
|
|
310
|
+
const result = msg.payload?.result;
|
|
311
|
+
return {
|
|
312
|
+
id: msg.id,
|
|
313
|
+
type: 'service-response',
|
|
314
|
+
action: 'received',
|
|
315
|
+
from: msg.from,
|
|
316
|
+
serviceId,
|
|
317
|
+
status,
|
|
318
|
+
result,
|
|
319
|
+
requestId: msg.payload?.requestId,
|
|
320
|
+
direction: 'incoming-response',
|
|
321
|
+
ack: true,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
// Unknown type
|
|
325
|
+
return {
|
|
326
|
+
id: msg.id,
|
|
327
|
+
type: msg.type,
|
|
328
|
+
from: msg.from,
|
|
329
|
+
payload: msg.payload,
|
|
330
|
+
signatureValid: sigCheck.valid,
|
|
331
|
+
action: 'unhandled',
|
|
332
|
+
ack: false,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbox and ack commands.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Inbox command: fetch pending messages.
|
|
6
|
+
*/
|
|
7
|
+
export declare function cmdInbox(args: string[]): Promise<never>;
|
|
8
|
+
/**
|
|
9
|
+
* Ack command: acknowledge processed messages.
|
|
10
|
+
*/
|
|
11
|
+
export declare function cmdAck(messageIds: string[]): Promise<never>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbox and ack commands.
|
|
3
|
+
*/
|
|
4
|
+
import { OVERLAY_URL } from '../config.js';
|
|
5
|
+
import { ok, fail } from '../output.js';
|
|
6
|
+
import { loadIdentity, verifyRelaySignature } from '../wallet/identity.js';
|
|
7
|
+
/**
|
|
8
|
+
* Inbox command: fetch pending messages.
|
|
9
|
+
*/
|
|
10
|
+
export async function cmdInbox(args) {
|
|
11
|
+
const { identityKey } = await loadIdentity();
|
|
12
|
+
let since = '';
|
|
13
|
+
for (let i = 0; i < args.length; i++) {
|
|
14
|
+
if (args[i] === '--since' && args[i + 1])
|
|
15
|
+
since = `&since=${args[++i]}`;
|
|
16
|
+
}
|
|
17
|
+
const resp = await fetch(`${OVERLAY_URL}/relay/inbox?identity=${identityKey}${since}`);
|
|
18
|
+
if (!resp.ok) {
|
|
19
|
+
const body = await resp.text();
|
|
20
|
+
return fail(`Relay inbox failed (${resp.status}): ${body}`);
|
|
21
|
+
}
|
|
22
|
+
const result = await resp.json();
|
|
23
|
+
// Verify signatures on received messages
|
|
24
|
+
const messages = await Promise.all(result.messages.map(async (msg) => ({
|
|
25
|
+
...msg,
|
|
26
|
+
signatureValid: msg.signature
|
|
27
|
+
? (await verifyRelaySignature(msg.from, msg.to, msg.type, msg.payload, msg.signature)).valid
|
|
28
|
+
: null,
|
|
29
|
+
})));
|
|
30
|
+
return ok({ messages, count: messages.length, identityKey });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Ack command: acknowledge processed messages.
|
|
34
|
+
*/
|
|
35
|
+
export async function cmdAck(messageIds) {
|
|
36
|
+
if (!messageIds || messageIds.length === 0) {
|
|
37
|
+
return fail('Usage: ack <messageId> [messageId2 ...]');
|
|
38
|
+
}
|
|
39
|
+
const { identityKey } = await loadIdentity();
|
|
40
|
+
const resp = await fetch(`${OVERLAY_URL}/relay/ack`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
body: JSON.stringify({ identity: identityKey, messageIds }),
|
|
44
|
+
});
|
|
45
|
+
if (!resp.ok) {
|
|
46
|
+
const body = await resp.text();
|
|
47
|
+
return fail(`Relay ack failed (${resp.status}): ${body}`);
|
|
48
|
+
}
|
|
49
|
+
const result = await resp.json();
|
|
50
|
+
return ok({ acked: result.acked, messageIds });
|
|
51
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll command: fetch and process all pending messages.
|
|
3
|
+
*/
|
|
4
|
+
import { OVERLAY_URL } from '../config.js';
|
|
5
|
+
import { ok, fail } from '../output.js';
|
|
6
|
+
import { loadIdentity } from '../wallet/identity.js';
|
|
7
|
+
import { processMessage } from './handlers.js';
|
|
8
|
+
/**
|
|
9
|
+
* Poll command: fetch all pending messages and process them.
|
|
10
|
+
*/
|
|
11
|
+
export async function cmdPoll() {
|
|
12
|
+
const { identityKey, privKey } = await loadIdentity();
|
|
13
|
+
// Fetch inbox
|
|
14
|
+
const inboxResp = await fetch(`${OVERLAY_URL}/relay/inbox?identity=${identityKey}`);
|
|
15
|
+
if (!inboxResp.ok) {
|
|
16
|
+
const body = await inboxResp.text();
|
|
17
|
+
return fail(`Relay inbox failed (${inboxResp.status}): ${body}`);
|
|
18
|
+
}
|
|
19
|
+
const inbox = await inboxResp.json();
|
|
20
|
+
if (inbox.count === 0) {
|
|
21
|
+
return ok({ processed: 0, messages: [], summary: 'No pending messages.' });
|
|
22
|
+
}
|
|
23
|
+
const processed = [];
|
|
24
|
+
const ackedIds = [];
|
|
25
|
+
const unhandled = [];
|
|
26
|
+
for (const msg of inbox.messages) {
|
|
27
|
+
const result = await processMessage(msg, identityKey, privKey);
|
|
28
|
+
if (result.ack) {
|
|
29
|
+
ackedIds.push(result.id);
|
|
30
|
+
processed.push(result);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
unhandled.push(result);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// ACK processed messages
|
|
37
|
+
if (ackedIds.length > 0) {
|
|
38
|
+
await fetch(`${OVERLAY_URL}/relay/ack`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
body: JSON.stringify({ identity: identityKey, messageIds: ackedIds }),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return ok({
|
|
45
|
+
processed: processed.length,
|
|
46
|
+
unhandled: unhandled.length,
|
|
47
|
+
total: inbox.count,
|
|
48
|
+
messages: processed,
|
|
49
|
+
unhandledMessages: unhandled,
|
|
50
|
+
ackedIds,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send relay message command.
|
|
3
|
+
*/
|
|
4
|
+
import { OVERLAY_URL } from '../config.js';
|
|
5
|
+
import { ok, fail } from '../output.js';
|
|
6
|
+
import { loadIdentity, signRelayMessage } from '../wallet/identity.js';
|
|
7
|
+
/**
|
|
8
|
+
* Send command: send a typed message to another agent.
|
|
9
|
+
*/
|
|
10
|
+
export async function cmdSend(targetKey, type, payloadStr) {
|
|
11
|
+
if (!targetKey || !type || !payloadStr) {
|
|
12
|
+
return fail('Usage: send <identityKey> <type> <json_payload>');
|
|
13
|
+
}
|
|
14
|
+
if (!/^0[23][0-9a-fA-F]{64}$/.test(targetKey)) {
|
|
15
|
+
return fail('Target must be a compressed public key (66 hex chars, 02/03 prefix)');
|
|
16
|
+
}
|
|
17
|
+
let payload;
|
|
18
|
+
try {
|
|
19
|
+
payload = JSON.parse(payloadStr);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return fail('payload must be valid JSON');
|
|
23
|
+
}
|
|
24
|
+
const { identityKey, privKey } = await loadIdentity();
|
|
25
|
+
const signature = await signRelayMessage(privKey, targetKey, type, payload);
|
|
26
|
+
const resp = await fetch(`${OVERLAY_URL}/relay/send`, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
from: identityKey,
|
|
31
|
+
to: targetKey,
|
|
32
|
+
type,
|
|
33
|
+
payload,
|
|
34
|
+
signature,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
if (!resp.ok) {
|
|
38
|
+
const body = await resp.text();
|
|
39
|
+
return fail(`Relay send failed (${resp.status}): ${body}`);
|
|
40
|
+
}
|
|
41
|
+
const result = await resp.json();
|
|
42
|
+
return ok({ sent: true, messageId: result.id, to: targetKey, type, signed: true });
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON output helpers for CLI commands.
|
|
3
|
+
* All CLI output follows the { success, data/error } wrapper format.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Output a successful result and exit.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ok<T>(data: T): never;
|
|
9
|
+
/**
|
|
10
|
+
* Output an error and exit.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fail(error: string | Error): never;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON output helpers for CLI commands.
|
|
3
|
+
* All CLI output follows the { success, data/error } wrapper format.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Output a successful result and exit.
|
|
7
|
+
*/
|
|
8
|
+
export function ok(data) {
|
|
9
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Output an error and exit.
|
|
14
|
+
*/
|
|
15
|
+
export function fail(error) {
|
|
16
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay discovery commands.
|
|
3
|
+
*/
|
|
4
|
+
import { OVERLAY_URL, LOOKUP_SERVICES } from '../config.js';
|
|
5
|
+
import { ok } from '../output.js';
|
|
6
|
+
import { lookupOverlay, parseOverlayOutput } from './transaction.js';
|
|
7
|
+
/**
|
|
8
|
+
* Discover command: query the overlay for agents and services.
|
|
9
|
+
*/
|
|
10
|
+
export async function cmdDiscover(args) {
|
|
11
|
+
// Parse flags
|
|
12
|
+
let serviceFilter = null;
|
|
13
|
+
let agentFilter = null;
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
if (args[i] === '--service' && args[i + 1])
|
|
16
|
+
serviceFilter = args[++i];
|
|
17
|
+
else if (args[i] === '--agent' && args[i + 1])
|
|
18
|
+
agentFilter = args[++i];
|
|
19
|
+
}
|
|
20
|
+
const results = { agents: [], services: [] };
|
|
21
|
+
// Query agents
|
|
22
|
+
if (!serviceFilter) {
|
|
23
|
+
try {
|
|
24
|
+
const agentQuery = agentFilter ? { name: agentFilter } : { type: 'list' };
|
|
25
|
+
const agentResult = await lookupOverlay(LOOKUP_SERVICES.AGENTS, agentQuery);
|
|
26
|
+
if (agentResult.outputs) {
|
|
27
|
+
for (const output of agentResult.outputs) {
|
|
28
|
+
try {
|
|
29
|
+
const { data, txid } = await parseOverlayOutput(output.beef, output.outputIndex);
|
|
30
|
+
if (data?.type === 'identity') {
|
|
31
|
+
results.agents.push({ ...data, txid });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch { /* ignore */ }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
results.agentError = String(err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Query services
|
|
43
|
+
if (!agentFilter) {
|
|
44
|
+
try {
|
|
45
|
+
const serviceQuery = serviceFilter ? { serviceType: serviceFilter } : {};
|
|
46
|
+
const serviceResult = await lookupOverlay(LOOKUP_SERVICES.SERVICES, serviceQuery);
|
|
47
|
+
if (serviceResult.outputs) {
|
|
48
|
+
for (const output of serviceResult.outputs) {
|
|
49
|
+
try {
|
|
50
|
+
const { data, txid } = await parseOverlayOutput(output.beef, output.outputIndex);
|
|
51
|
+
if (data?.type === 'service') {
|
|
52
|
+
results.services.push({ ...data, txid });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch { /* ignore */ }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
results.serviceError = String(err);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return ok({
|
|
64
|
+
overlayUrl: OVERLAY_URL,
|
|
65
|
+
agentCount: results.agents.length,
|
|
66
|
+
serviceCount: results.services.length,
|
|
67
|
+
agents: results.agents,
|
|
68
|
+
services: results.services,
|
|
69
|
+
...(results.agentError && { agentError: results.agentError }),
|
|
70
|
+
...(results.serviceError && { serviceError: results.serviceError }),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay registration commands: register, unregister.
|
|
3
|
+
*
|
|
4
|
+
* Registration creates an identity record on the overlay network with:
|
|
5
|
+
* - identityKey: compressed public key (66 hex chars)
|
|
6
|
+
* - name: agent display name
|
|
7
|
+
* - description: what the agent does
|
|
8
|
+
* - channels: contact methods (e.g., { overlay: "https://..." })
|
|
9
|
+
* - capabilities: what the agent can do (e.g., ["services", "jokes"])
|
|
10
|
+
* - timestamp: ISO 8601 registration time
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Register command: register this agent on the overlay network.
|
|
14
|
+
*/
|
|
15
|
+
export declare function cmdRegister(): Promise<never>;
|
|
16
|
+
/**
|
|
17
|
+
* Unregister command: submit revocation tx to remove agent from overlay network.
|
|
18
|
+
*/
|
|
19
|
+
export declare function cmdUnregister(): Promise<never>;
|