2020117-agent 0.1.13 → 0.1.15

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
@@ -121,6 +121,8 @@ const state = {
121
121
  swarmNode: null,
122
122
  processor: null,
123
123
  skill: loadSkill(),
124
+ p2pSessionsCompleted: 0,
125
+ p2pTotalEarnedSats: 0,
124
126
  };
125
127
  // --- Capacity management ---
126
128
  function acquireSlot() {
@@ -182,7 +184,11 @@ async function setupPlatform(label) {
182
184
  models,
183
185
  skill: state.skill,
184
186
  });
185
- state.stopHeartbeat = startHeartbeatLoop(() => getAvailableCapacity());
187
+ state.stopHeartbeat = startHeartbeatLoop(() => getAvailableCapacity(), () => ({
188
+ sessions: state.p2pSessionsCompleted,
189
+ earned_sats: state.p2pTotalEarnedSats,
190
+ active: activeSessions.size > 0,
191
+ }));
186
192
  }
187
193
  // --- 3. Async Inbox Poller ---
188
194
  function startInboxPoller(label) {
@@ -685,6 +691,37 @@ async function startSwarmListener(label) {
685
691
  handleStop(job, msg.id, label);
686
692
  }
687
693
  });
694
+ // Handle customer disconnect — claim any earned tokens immediately
695
+ node.on('peer-leave', (peerId) => {
696
+ const tag = peerId.slice(0, 8);
697
+ // Find and end all sessions for this peer
698
+ for (const [sessionId, session] of activeSessions) {
699
+ if (session.peerId === peerId) {
700
+ console.log(`[${label}] Peer ${tag} disconnected — ending session ${sessionId} (${session.totalEarned} sats earned)`);
701
+ endSession(node, session, label);
702
+ }
703
+ }
704
+ // Clean up any P2P streaming jobs for this peer
705
+ for (const [jobId, job] of p2pJobs) {
706
+ if (job.socket?.remotePublicKey?.toString('hex') === peerId) {
707
+ console.log(`[${label}] Peer ${tag} disconnected — claiming P2P job ${jobId} tokens`);
708
+ job.stopped = true;
709
+ batchClaim(job.tokens, jobId, label);
710
+ p2pJobs.delete(jobId);
711
+ releaseSlot();
712
+ }
713
+ }
714
+ // Clean up backend WebSockets for this peer
715
+ for (const [wsId, entry] of backendWebSockets) {
716
+ if (entry.peerId === peerId) {
717
+ try {
718
+ entry.ws.close(1001, 'Peer disconnected');
719
+ }
720
+ catch { }
721
+ backendWebSockets.delete(wsId);
722
+ }
723
+ }
724
+ });
688
725
  }
689
726
  async function runP2PGeneration(node, job, msg, label) {
690
727
  const source = SUB_KIND
@@ -728,14 +765,22 @@ function endSession(node, session, label) {
728
765
  backendWebSockets.delete(wsId);
729
766
  }
730
767
  }
731
- node.send(session.socket, {
732
- type: 'session_end',
733
- id: session.sessionId,
734
- session_id: session.sessionId,
735
- total_sats: session.totalEarned,
736
- duration_s: durationS,
737
- });
768
+ try {
769
+ node.send(session.socket, {
770
+ type: 'session_end',
771
+ id: session.sessionId,
772
+ session_id: session.sessionId,
773
+ total_sats: session.totalEarned,
774
+ duration_s: durationS,
775
+ });
776
+ }
777
+ catch {
778
+ // Socket may already be closed (peer disconnect)
779
+ }
738
780
  console.log(`[${label}] Session ${session.sessionId} ended: ${session.totalEarned} sats, ${durationS}s`);
781
+ // Update P2P lifetime counters
782
+ state.p2pSessionsCompleted++;
783
+ state.p2pTotalEarnedSats += session.totalEarned;
739
784
  // Batch claim tokens
740
785
  batchClaim(session.tokens, session.sessionId, label);
741
786
  activeSessions.delete(session.sessionId);
package/dist/api.d.ts CHANGED
@@ -25,9 +25,14 @@ export declare function registerService(opts: {
25
25
  models?: string[];
26
26
  skill?: Record<string, unknown> | null;
27
27
  }): Promise<unknown | null>;
28
- export declare function sendHeartbeat(capacity?: number): Promise<boolean>;
28
+ export interface P2PStats {
29
+ sessions: number;
30
+ earned_sats: number;
31
+ active: boolean;
32
+ }
33
+ export declare function sendHeartbeat(capacity?: number, p2pStats?: P2PStats): Promise<boolean>;
29
34
  export declare function getOnlineProviders(kind: number): Promise<OnlineAgent[]>;
30
- export declare function startHeartbeatLoop(capacityOrFn?: number | (() => number)): () => void;
35
+ export declare function startHeartbeatLoop(capacityOrFn?: number | (() => number), p2pStatsFn?: () => P2PStats): () => void;
31
36
  export interface InboxJob {
32
37
  id: string;
33
38
  kind: number;
package/dist/api.js CHANGED
@@ -161,13 +161,15 @@ export async function registerService(opts) {
161
161
  return null;
162
162
  }
163
163
  }
164
- export async function sendHeartbeat(capacity) {
164
+ export async function sendHeartbeat(capacity, p2pStats) {
165
165
  if (!API_KEY)
166
166
  return false;
167
167
  try {
168
168
  const body = {};
169
169
  if (capacity !== undefined)
170
170
  body.capacity = capacity;
171
+ if (p2pStats)
172
+ body.p2p_stats = p2pStats;
171
173
  const resp = await fetch(`${BASE_URL}/api/heartbeat`, {
172
174
  method: 'POST',
173
175
  headers: {
@@ -206,17 +208,17 @@ export async function getOnlineProviders(kind) {
206
208
  }
207
209
  }
208
210
  const HEARTBEAT_INTERVAL = 5 * 60 * 1000; // 5 minutes
209
- export function startHeartbeatLoop(capacityOrFn) {
211
+ export function startHeartbeatLoop(capacityOrFn, p2pStatsFn) {
210
212
  const getCapacity = typeof capacityOrFn === 'function'
211
213
  ? capacityOrFn
212
214
  : () => capacityOrFn;
213
215
  // Send first heartbeat immediately
214
- sendHeartbeat(getCapacity()).then((ok) => {
216
+ sendHeartbeat(getCapacity(), p2pStatsFn?.()).then((ok) => {
215
217
  if (ok)
216
218
  console.log('[api] Heartbeat sent');
217
219
  });
218
220
  const timer = setInterval(() => {
219
- sendHeartbeat(getCapacity()).then((ok) => {
221
+ sendHeartbeat(getCapacity(), p2pStatsFn?.()).then((ok) => {
220
222
  if (ok)
221
223
  console.log('[api] Heartbeat sent');
222
224
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "2020117-agent",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "2020117 agent runtime — API polling + Hyperswarm P2P + Cashu streaming payments",
5
5
  "type": "module",
6
6
  "bin": {