2020117-agent 0.1.5 → 0.1.7

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 CHANGED
@@ -470,13 +470,34 @@ async function startSwarmListener(label) {
470
470
  const resBody = await res.text();
471
471
  const resHeaders = {};
472
472
  res.headers.forEach((v, k) => { resHeaders[k] = v; });
473
- node.send(socket, {
474
- type: 'http_response',
475
- id: msg.id,
476
- status: res.status,
477
- headers: resHeaders,
478
- body: resBody,
479
- });
473
+ // Chunk large responses to avoid swarm transport truncation
474
+ const CHUNK_SIZE = 48_000; // ~48KB per chunk (safe margin under 64KB NOISE frame)
475
+ if (resBody.length > CHUNK_SIZE) {
476
+ const chunks = [];
477
+ for (let i = 0; i < resBody.length; i += CHUNK_SIZE) {
478
+ chunks.push(resBody.slice(i, i + CHUNK_SIZE));
479
+ }
480
+ for (let i = 0; i < chunks.length; i++) {
481
+ node.send(socket, {
482
+ type: 'http_response',
483
+ id: msg.id,
484
+ status: i === 0 ? res.status : undefined,
485
+ headers: i === 0 ? resHeaders : undefined,
486
+ body: chunks[i],
487
+ chunk_index: i,
488
+ chunk_total: chunks.length,
489
+ });
490
+ }
491
+ }
492
+ else {
493
+ node.send(socket, {
494
+ type: 'http_response',
495
+ id: msg.id,
496
+ status: res.status,
497
+ headers: resHeaders,
498
+ body: resBody,
499
+ });
500
+ }
480
501
  }
481
502
  catch (e) {
482
503
  node.send(socket, {
package/dist/cashu.js CHANGED
@@ -81,10 +81,27 @@ export async function splitTokens(tokenStr, perAmount) {
81
81
  const total = remaining.reduce((sum, p) => sum + p.amount, 0);
82
82
  if (total < perAmount)
83
83
  break;
84
- const { send, keep } = await wallet.send(perAmount, remaining);
85
- microTokens.push(getEncodedToken({ mint: mintUrl, proofs: send }));
86
- remaining = keep;
87
- if (keep.length === 0)
84
+ let retries = 5;
85
+ while (retries > 0) {
86
+ try {
87
+ const { send, keep: kept } = await wallet.send(perAmount, remaining);
88
+ microTokens.push(getEncodedToken({ mint: mintUrl, proofs: send }));
89
+ remaining = kept;
90
+ break;
91
+ }
92
+ catch (e) {
93
+ if (retries > 1 && /rate.limit/i.test(e.message || '')) {
94
+ const delay = (6 - retries) * 2000; // 2s, 4s, 6s, 8s
95
+ console.log(`[cashu] Rate limited, waiting ${delay / 1000}s... (${retries - 1} retries left)`);
96
+ await sleep(delay);
97
+ retries--;
98
+ }
99
+ else {
100
+ throw e;
101
+ }
102
+ }
103
+ }
104
+ if (remaining.length === 0)
88
105
  break;
89
106
  }
90
107
  console.log(`[cashu] Split into ${microTokens.length} micro-tokens of ${perAmount} sats each`);
package/dist/session.js CHANGED
@@ -59,6 +59,8 @@ const state = {
59
59
  sessionId: '',
60
60
  skill: null,
61
61
  satsPerMinute: 0,
62
+ satsPerToken: 1,
63
+ tickIntervalMs: 60_000,
62
64
  microTokens: [],
63
65
  tokenIndex: 0,
64
66
  totalSpent: 0,
@@ -67,6 +69,7 @@ const state = {
67
69
  httpServer: null,
68
70
  shuttingDown: false,
69
71
  pendingRequests: new Map(),
72
+ chunkBuffers: new Map(),
70
73
  outputCounter: 0,
71
74
  };
72
75
  // --- Helpers ---
@@ -109,6 +112,34 @@ function sendAndWait(msg, timeoutMs) {
109
112
  // --- 6. Message handler ---
110
113
  function setupMessageHandler() {
111
114
  state.node.on('message', (msg) => {
115
+ // Handle chunked HTTP responses — reassemble before resolving
116
+ if (msg.type === 'http_response' && msg.chunk_total && msg.chunk_total > 1) {
117
+ const id = msg.id;
118
+ let buf = state.chunkBuffers.get(id);
119
+ if (!buf) {
120
+ buf = { chunks: new Array(msg.chunk_total), total: msg.chunk_total, firstMsg: msg };
121
+ state.chunkBuffers.set(id, buf);
122
+ }
123
+ buf.chunks[msg.chunk_index ?? 0] = msg.body ?? '';
124
+ const received = buf.chunks.filter(c => c !== undefined).length;
125
+ if (received < buf.total)
126
+ return; // wait for more chunks
127
+ // All chunks received — reassemble and resolve
128
+ const assembled = {
129
+ ...buf.firstMsg,
130
+ body: buf.chunks.join(''),
131
+ chunk_index: undefined,
132
+ chunk_total: undefined,
133
+ };
134
+ state.chunkBuffers.delete(id);
135
+ const pending = state.pendingRequests.get(id);
136
+ if (pending) {
137
+ clearTimeout(pending.timer);
138
+ state.pendingRequests.delete(id);
139
+ pending.resolve(assembled);
140
+ }
141
+ return;
142
+ }
112
143
  // Check pending requests first — match by id
113
144
  const pending = state.pendingRequests.get(msg.id);
114
145
  if (pending) {
@@ -155,38 +186,42 @@ function setupMessageHandler() {
155
186
  });
156
187
  }
157
188
  // --- 3. Tick timer ---
158
- function startTickTimer() {
159
- state.tickTimer = setInterval(async () => {
160
- if (state.shuttingDown)
161
- return;
162
- if (remainingTokens() <= 0) {
163
- log('Budget exhausted — ending session');
164
- await endSession();
165
- return;
166
- }
167
- // Low balance warning
168
- if (remainingTokens() <= 2) {
169
- warn(`Low balance! Only ${remainingTokens()} tick(s) remaining (${remainingSats()} sats)`);
170
- }
171
- const token = state.microTokens[state.tokenIndex++];
172
- state.totalSpent += state.satsPerMinute;
173
- const tickId = randomBytes(4).toString('hex');
174
- try {
175
- const resp = await sendAndWait({
176
- type: 'session_tick',
177
- id: tickId,
178
- session_id: state.sessionId,
179
- token,
180
- budget: BUDGET,
181
- }, 15_000);
182
- if (resp.balance !== undefined) {
183
- log(`Tick OK — balance: ${resp.balance} sats`);
184
- }
185
- }
186
- catch (e) {
187
- warn(`Tick failed: ${e.message}`);
189
+ async function sendTick() {
190
+ if (state.shuttingDown)
191
+ return false;
192
+ if (remainingTokens() <= 0) {
193
+ log('Budget exhausted — ending session');
194
+ await endSession();
195
+ return false;
196
+ }
197
+ if (remainingTokens() <= 2) {
198
+ warn(`Low balance! Only ${remainingTokens()} tick(s) remaining (${remainingSats()} sats)`);
199
+ }
200
+ const token = state.microTokens[state.tokenIndex++];
201
+ state.totalSpent += state.satsPerToken;
202
+ const tickId = randomBytes(4).toString('hex');
203
+ try {
204
+ const resp = await sendAndWait({
205
+ type: 'session_tick',
206
+ id: tickId,
207
+ session_id: state.sessionId,
208
+ token,
209
+ budget: BUDGET,
210
+ }, 15_000);
211
+ if (resp.balance !== undefined) {
212
+ log(`Tick OK — balance: ${resp.balance} sats`);
188
213
  }
189
- }, TICK_INTERVAL_MS);
214
+ return true;
215
+ }
216
+ catch (e) {
217
+ warn(`Tick failed: ${e.message}`);
218
+ return false;
219
+ }
220
+ }
221
+ function startTickTimer() {
222
+ // Send first tick immediately (prepay) to prevent provider timeout
223
+ sendTick();
224
+ state.tickTimer = setInterval(() => sendTick(), state.tickIntervalMs);
190
225
  }
191
226
  // --- 4. HTTP proxy ---
192
227
  function startHttpProxy() {
@@ -251,6 +286,11 @@ function startHttpProxy() {
251
286
  }
252
287
  // --- 5. CLI REPL ---
253
288
  function startRepl() {
289
+ // Skip REPL when stdin is not a TTY (e.g. background process, piped input)
290
+ if (!process.stdin.isTTY) {
291
+ log('Non-interactive mode (no TTY) — session will run until budget exhausted or provider ends it');
292
+ return;
293
+ }
254
294
  const rl = createInterface({
255
295
  input: process.stdin,
256
296
  output: process.stdout,
@@ -453,6 +493,7 @@ async function endSession() {
453
493
  pending.reject(new Error('Session ending'));
454
494
  state.pendingRequests.delete(id);
455
495
  }
496
+ state.chunkBuffers.clear();
456
497
  // Send session_end
457
498
  if (state.node && state.socket && state.sessionId) {
458
499
  const duration = elapsedSeconds();
@@ -519,10 +560,15 @@ async function main() {
519
560
  log(`Pricing: ${satsPerMinute} sats/min`);
520
561
  log(`Budget: ${BUDGET} sats (~${Math.floor(BUDGET / satsPerMinute)} min)`);
521
562
  // 4. Mint and split tokens
563
+ // Cashu tokens must be whole sats. If rate < 1, use 1 sat tokens with longer tick intervals.
564
+ const satsPerToken = Math.max(1, Math.ceil(satsPerMinute));
565
+ const tickMinutes = satsPerToken / satsPerMinute; // e.g. 0.1 sats/min → 1 sat every 10 min
566
+ state.tickIntervalMs = Math.round(tickMinutes * 60_000);
567
+ state.satsPerToken = satsPerToken;
522
568
  log(`Minting ${BUDGET} sats...`);
523
569
  const { token: bigToken } = await mintTokens(BUDGET);
524
- log(`Splitting into ${satsPerMinute}-sat micro-tokens...`);
525
- state.microTokens = await splitTokens(bigToken, satsPerMinute);
570
+ log(`Splitting into ${satsPerToken}-sat micro-tokens (tick every ${tickMinutes} min)...`);
571
+ state.microTokens = await splitTokens(bigToken, satsPerToken);
526
572
  log(`Ready: ${state.microTokens.length} micro-tokens`);
527
573
  if (state.microTokens.length === 0) {
528
574
  warn('Budget too small for even one tick payment');
package/dist/swarm.d.ts CHANGED
@@ -50,6 +50,8 @@ export interface SwarmMessage {
50
50
  headers?: Record<string, string>;
51
51
  body?: string;
52
52
  status?: number;
53
+ chunk_index?: number;
54
+ chunk_total?: number;
53
55
  }
54
56
  /**
55
57
  * Create a deterministic topic hash from a service kind number.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "2020117-agent",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "2020117 agent runtime — API polling + Hyperswarm P2P + Cashu streaming payments",
5
5
  "type": "module",
6
6
  "bin": {