2020117-agent 0.4.1 → 0.4.3
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/dist/agent.js +43 -6
- package/dist/session.js +49 -8
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -333,6 +333,10 @@ async function publishHandlerInfo(label) {
|
|
|
333
333
|
about: state.skill?.description || `DVM agent (kind ${KIND})`,
|
|
334
334
|
pricing: { [String(KIND)]: SATS_PER_CHUNK * CHUNKS_PER_PAYMENT },
|
|
335
335
|
};
|
|
336
|
+
if (LIGHTNING_ADDRESS) {
|
|
337
|
+
content.payment = { lightning_address: LIGHTNING_ADDRESS };
|
|
338
|
+
content.lud16 = LIGHTNING_ADDRESS;
|
|
339
|
+
}
|
|
336
340
|
const event = signEvent({
|
|
337
341
|
kind: 31990,
|
|
338
342
|
tags: [
|
|
@@ -449,17 +453,43 @@ async function handleDvmRequest(label, event) {
|
|
|
449
453
|
console.log(`[${label}] DVM result: ${result.length} chars`);
|
|
450
454
|
// Send result (Kind 6xxx = request kind + 1000)
|
|
451
455
|
const resultKind = KIND + 1000;
|
|
456
|
+
const resultTags = [
|
|
457
|
+
['p', event.pubkey],
|
|
458
|
+
['e', event.id],
|
|
459
|
+
['request', JSON.stringify(event)],
|
|
460
|
+
['amount', String(SATS_PER_CHUNK * CHUNKS_PER_PAYMENT * 1000)], // msats
|
|
461
|
+
];
|
|
462
|
+
if (LIGHTNING_ADDRESS) {
|
|
463
|
+
resultTags.push(['lightning_address', LIGHTNING_ADDRESS]);
|
|
464
|
+
}
|
|
452
465
|
const resultEvent = signEvent({
|
|
453
466
|
kind: resultKind,
|
|
454
|
-
tags:
|
|
455
|
-
['p', event.pubkey],
|
|
456
|
-
['e', event.id],
|
|
457
|
-
['request', JSON.stringify(event)],
|
|
458
|
-
],
|
|
467
|
+
tags: resultTags,
|
|
459
468
|
content: result,
|
|
460
469
|
}, state.sovereignKeys.privkey);
|
|
461
470
|
await state.relayPool.publish(resultEvent);
|
|
462
471
|
console.log(`[${label}] Published DVM result (Kind ${resultKind}) via relay`);
|
|
472
|
+
// Publish reputation endorsement (Kind 30311) for customer
|
|
473
|
+
try {
|
|
474
|
+
const endorsementEvent = signEvent({
|
|
475
|
+
kind: 30311,
|
|
476
|
+
tags: [
|
|
477
|
+
['d', event.pubkey],
|
|
478
|
+
['p', event.pubkey],
|
|
479
|
+
['rating', '5'],
|
|
480
|
+
['k', String(KIND)],
|
|
481
|
+
],
|
|
482
|
+
content: JSON.stringify({
|
|
483
|
+
rating: 5,
|
|
484
|
+
context: { jobs_together: 1, kinds: [KIND], last_job_at: Math.floor(Date.now() / 1000) },
|
|
485
|
+
}),
|
|
486
|
+
}, state.sovereignKeys.privkey);
|
|
487
|
+
await state.relayPool.publish(endorsementEvent);
|
|
488
|
+
console.log(`[${label}] Published endorsement (Kind 30311) for ${event.pubkey.slice(0, 8)}`);
|
|
489
|
+
}
|
|
490
|
+
catch (e) {
|
|
491
|
+
console.warn(`[${label}] Failed to publish endorsement: ${e.message}`);
|
|
492
|
+
}
|
|
463
493
|
}
|
|
464
494
|
finally {
|
|
465
495
|
releaseSlot();
|
|
@@ -676,7 +706,14 @@ async function startSwarmListener(label) {
|
|
|
676
706
|
node.on('message', async (msg, socket, peerId) => {
|
|
677
707
|
const tag = peerId.slice(0, 8);
|
|
678
708
|
if (msg.type === 'skill_request') {
|
|
679
|
-
|
|
709
|
+
const satsPerMinute = state.skill?.pricing?.sats_per_minute
|
|
710
|
+
|| Number(process.env.SATS_PER_MINUTE)
|
|
711
|
+
|| 10;
|
|
712
|
+
// Always include runtime pricing, even without a skill file
|
|
713
|
+
const skillWithPricing = state.skill
|
|
714
|
+
? { ...state.skill, pricing: { ...(state.skill.pricing || {}), sats_per_minute: satsPerMinute } }
|
|
715
|
+
: { pricing: { sats_per_minute: satsPerMinute }, payment_methods: LIGHTNING_ADDRESS ? ['cashu', 'invoice'] : ['cashu'] };
|
|
716
|
+
node.send(socket, { type: 'skill_response', id: msg.id, skill: skillWithPricing });
|
|
680
717
|
return;
|
|
681
718
|
}
|
|
682
719
|
// --- Session protocol ---
|
package/dist/session.js
CHANGED
|
@@ -64,6 +64,15 @@ const MINT_URL = process.env.CASHU_MINT_URL || 'https://8333.space:3338';
|
|
|
64
64
|
let cashuState = null;
|
|
65
65
|
const TICK_INTERVAL_MS = 60_000;
|
|
66
66
|
const HTTP_TIMEOUT_MS = 60_000;
|
|
67
|
+
function confirmPrompt(question) {
|
|
68
|
+
return new Promise(resolve => {
|
|
69
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
70
|
+
rl.question(question, answer => {
|
|
71
|
+
rl.close();
|
|
72
|
+
resolve(answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
67
76
|
const state = {
|
|
68
77
|
node: null,
|
|
69
78
|
socket: null,
|
|
@@ -72,6 +81,7 @@ const state = {
|
|
|
72
81
|
skill: null,
|
|
73
82
|
satsPerMinute: 0,
|
|
74
83
|
totalSpent: 0,
|
|
84
|
+
pendingAmount: 0, // accumulates fractional sats until >= 1
|
|
75
85
|
startedAt: 0,
|
|
76
86
|
httpServer: null,
|
|
77
87
|
shuttingDown: false,
|
|
@@ -194,19 +204,32 @@ function setupMessageHandler() {
|
|
|
194
204
|
}
|
|
195
205
|
}
|
|
196
206
|
else if (cashuState) {
|
|
197
|
-
// Cashu mode:
|
|
198
|
-
|
|
207
|
+
// Cashu mode: accumulate fractional amounts, pay in whole sats
|
|
208
|
+
state.pendingAmount += amount;
|
|
209
|
+
const payNow = Math.floor(state.pendingAmount);
|
|
210
|
+
if (payNow < 1) {
|
|
211
|
+
// Not enough to pay yet — ack without token
|
|
212
|
+
state.node.send(state.socket, {
|
|
213
|
+
type: 'session_tick_ack',
|
|
214
|
+
id: msg.id,
|
|
215
|
+
session_id: state.sessionId,
|
|
216
|
+
amount: 0,
|
|
217
|
+
});
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
log(`Paying with Cashu: ${payNow} sats (accumulated from ${state.pendingAmount.toFixed(2)})...`);
|
|
199
221
|
try {
|
|
200
|
-
const { token, change } = await sendCashuToken(cashuState.mintUrl, cashuState.proofs,
|
|
222
|
+
const { token, change } = await sendCashuToken(cashuState.mintUrl, cashuState.proofs, payNow);
|
|
201
223
|
cashuState.proofs = change;
|
|
202
|
-
state.totalSpent +=
|
|
203
|
-
|
|
224
|
+
state.totalSpent += payNow;
|
|
225
|
+
state.pendingAmount -= payNow;
|
|
226
|
+
log(`Paid ${payNow} sats (total: ${state.totalSpent}, ~${estimatedMinutesLeft()} min left)`);
|
|
204
227
|
state.node.send(state.socket, {
|
|
205
228
|
type: 'session_tick_ack',
|
|
206
229
|
id: msg.id,
|
|
207
230
|
session_id: state.sessionId,
|
|
208
231
|
cashu_token: token,
|
|
209
|
-
amount,
|
|
232
|
+
amount: payNow,
|
|
210
233
|
});
|
|
211
234
|
}
|
|
212
235
|
catch (e) {
|
|
@@ -687,8 +710,26 @@ async function main() {
|
|
|
687
710
|
const pricing = skill?.pricing;
|
|
688
711
|
const satsPerMinute = Number(pricing?.sats_per_minute) || 10;
|
|
689
712
|
state.satsPerMinute = satsPerMinute;
|
|
690
|
-
|
|
691
|
-
|
|
713
|
+
// Price confirmation
|
|
714
|
+
const minutesAvailable = Math.floor(BUDGET / satsPerMinute);
|
|
715
|
+
log('');
|
|
716
|
+
log('┌─────────────────────────────────');
|
|
717
|
+
log(`│ Rate: ${satsPerMinute} sats/min`);
|
|
718
|
+
log(`│ Budget: ${BUDGET} sats`);
|
|
719
|
+
log(`│ Duration: ~${minutesAvailable} min`);
|
|
720
|
+
if (skill?.payment_methods) {
|
|
721
|
+
log(`│ Payment: ${skill.payment_methods.join(', ')}`);
|
|
722
|
+
}
|
|
723
|
+
log('└─────────────────────────────────');
|
|
724
|
+
log('');
|
|
725
|
+
if (process.stdin.isTTY) {
|
|
726
|
+
const confirmed = await confirmPrompt('Start session? (y/n): ');
|
|
727
|
+
if (!confirmed) {
|
|
728
|
+
log('Session cancelled.');
|
|
729
|
+
await node.destroy();
|
|
730
|
+
process.exit(0);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
692
733
|
// 4. Determine payment method: Cashu (default) or invoice (fallback)
|
|
693
734
|
let paymentMethod;
|
|
694
735
|
if (CASHU_TOKEN) {
|