@tjamescouch/agentchat 0.22.1 → 0.23.1

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 (153) hide show
  1. package/Dockerfile +1 -1
  2. package/dist/bin/agentchat.d.ts +7 -0
  3. package/dist/bin/agentchat.d.ts.map +1 -0
  4. package/dist/bin/agentchat.js +1511 -0
  5. package/dist/bin/agentchat.js.map +1 -0
  6. package/dist/lib/allowlist.d.ts +77 -0
  7. package/dist/lib/allowlist.d.ts.map +1 -0
  8. package/dist/lib/allowlist.js +151 -0
  9. package/dist/lib/allowlist.js.map +1 -0
  10. package/dist/lib/client.d.ts +147 -0
  11. package/dist/lib/client.d.ts.map +1 -0
  12. package/dist/lib/client.js +704 -0
  13. package/dist/lib/client.js.map +1 -0
  14. package/dist/lib/daemon.d.ts +122 -0
  15. package/dist/lib/daemon.d.ts.map +1 -0
  16. package/dist/lib/daemon.js +523 -0
  17. package/dist/lib/daemon.js.map +1 -0
  18. package/dist/lib/deploy/akash.d.ts +271 -0
  19. package/dist/lib/deploy/akash.d.ts.map +1 -0
  20. package/dist/lib/deploy/akash.js +671 -0
  21. package/dist/lib/deploy/akash.js.map +1 -0
  22. package/dist/lib/deploy/config.d.ts +62 -0
  23. package/dist/lib/deploy/config.d.ts.map +1 -0
  24. package/dist/lib/deploy/config.js +116 -0
  25. package/dist/lib/deploy/config.js.map +1 -0
  26. package/dist/lib/deploy/docker.d.ts +37 -0
  27. package/dist/lib/deploy/docker.d.ts.map +1 -0
  28. package/dist/lib/deploy/docker.js +122 -0
  29. package/dist/lib/deploy/docker.js.map +1 -0
  30. package/dist/lib/deploy/index.d.ts +11 -0
  31. package/dist/lib/deploy/index.d.ts.map +1 -0
  32. package/dist/lib/deploy/index.js +11 -0
  33. package/dist/lib/deploy/index.js.map +1 -0
  34. package/dist/lib/escrow-hooks.d.ts +199 -0
  35. package/dist/lib/escrow-hooks.d.ts.map +1 -0
  36. package/dist/lib/escrow-hooks.js +221 -0
  37. package/dist/lib/escrow-hooks.js.map +1 -0
  38. package/dist/lib/identity.d.ts +134 -0
  39. package/dist/lib/identity.d.ts.map +1 -0
  40. package/dist/lib/identity.js +334 -0
  41. package/dist/lib/identity.js.map +1 -0
  42. package/dist/lib/jitter.d.ts +42 -0
  43. package/dist/lib/jitter.d.ts.map +1 -0
  44. package/{lib/jitter.ts → dist/lib/jitter.js} +10 -18
  45. package/dist/lib/jitter.js.map +1 -0
  46. package/dist/lib/proposals.d.ts +223 -0
  47. package/dist/lib/proposals.d.ts.map +1 -0
  48. package/dist/lib/proposals.js +379 -0
  49. package/dist/lib/proposals.js.map +1 -0
  50. package/dist/lib/protocol.d.ts +220 -0
  51. package/dist/lib/protocol.d.ts.map +1 -0
  52. package/dist/lib/protocol.js +507 -0
  53. package/dist/lib/protocol.js.map +1 -0
  54. package/dist/lib/receipts.d.ts +134 -0
  55. package/dist/lib/receipts.d.ts.map +1 -0
  56. package/dist/lib/receipts.js +270 -0
  57. package/dist/lib/receipts.js.map +1 -0
  58. package/dist/lib/reputation.d.ts +250 -0
  59. package/dist/lib/reputation.d.ts.map +1 -0
  60. package/dist/lib/reputation.js +586 -0
  61. package/dist/lib/reputation.js.map +1 -0
  62. package/dist/lib/security.d.ts +27 -0
  63. package/dist/lib/security.d.ts.map +1 -0
  64. package/dist/lib/security.js +150 -0
  65. package/dist/lib/security.js.map +1 -0
  66. package/dist/lib/server/handlers/admin.d.ts +26 -0
  67. package/dist/lib/server/handlers/admin.d.ts.map +1 -0
  68. package/dist/lib/server/handlers/admin.js +76 -0
  69. package/dist/lib/server/handlers/admin.js.map +1 -0
  70. package/dist/lib/server/handlers/identity.d.ts +36 -0
  71. package/dist/lib/server/handlers/identity.d.ts.map +1 -0
  72. package/dist/lib/server/handlers/identity.js +330 -0
  73. package/dist/lib/server/handlers/identity.js.map +1 -0
  74. package/dist/lib/server/handlers/index.d.ts +10 -0
  75. package/dist/lib/server/handlers/index.d.ts.map +1 -0
  76. package/dist/lib/server/handlers/index.js +15 -0
  77. package/dist/lib/server/handlers/index.js.map +1 -0
  78. package/dist/lib/server/handlers/message.d.ts +47 -0
  79. package/dist/lib/server/handlers/message.d.ts.map +1 -0
  80. package/dist/lib/server/handlers/message.js +265 -0
  81. package/dist/lib/server/handlers/message.js.map +1 -0
  82. package/dist/lib/server/handlers/presence.d.ts +18 -0
  83. package/dist/lib/server/handlers/presence.d.ts.map +1 -0
  84. package/dist/lib/server/handlers/presence.js +35 -0
  85. package/dist/lib/server/handlers/presence.js.map +1 -0
  86. package/dist/lib/server/handlers/proposal.d.ts +38 -0
  87. package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
  88. package/dist/lib/server/handlers/proposal.js +273 -0
  89. package/dist/lib/server/handlers/proposal.js.map +1 -0
  90. package/dist/lib/server/handlers/skills.d.ts +22 -0
  91. package/dist/lib/server/handlers/skills.d.ts.map +1 -0
  92. package/dist/lib/server/handlers/skills.js +119 -0
  93. package/dist/lib/server/handlers/skills.js.map +1 -0
  94. package/dist/lib/server-directory.d.ts +85 -0
  95. package/dist/lib/server-directory.d.ts.map +1 -0
  96. package/dist/lib/server-directory.js +177 -0
  97. package/dist/lib/server-directory.js.map +1 -0
  98. package/dist/lib/server.d.ts +162 -0
  99. package/dist/lib/server.d.ts.map +1 -0
  100. package/dist/lib/server.js +602 -0
  101. package/dist/lib/server.js.map +1 -0
  102. package/dist/lib/types.d.ts +461 -0
  103. package/dist/lib/types.d.ts.map +1 -0
  104. package/dist/lib/types.js +98 -0
  105. package/dist/lib/types.js.map +1 -0
  106. package/package.json +22 -13
  107. package/bin/agentchat.js +0 -1617
  108. package/bin/agentchat.ts +0 -1812
  109. package/lib/allowlist.js +0 -162
  110. package/lib/chat.py +0 -241
  111. package/lib/client.js +0 -821
  112. package/lib/client.ts +0 -877
  113. package/lib/daemon.js +0 -562
  114. package/lib/daemon.ts +0 -662
  115. package/lib/deploy/akash.js +0 -811
  116. package/lib/deploy/config.js +0 -128
  117. package/lib/deploy/docker.js +0 -132
  118. package/lib/deploy/index.js +0 -24
  119. package/lib/elo_swarm.py +0 -569
  120. package/lib/escrow-hooks.js +0 -237
  121. package/lib/escrow-hooks.ts +0 -391
  122. package/lib/identity.js +0 -376
  123. package/lib/identity.ts +0 -412
  124. package/lib/jitter.js +0 -54
  125. package/lib/proposals.js +0 -426
  126. package/lib/proposals.ts +0 -612
  127. package/lib/protocol.js +0 -516
  128. package/lib/receipts.js +0 -294
  129. package/lib/receipts.ts +0 -359
  130. package/lib/reputation.js +0 -664
  131. package/lib/reputation.ts +0 -790
  132. package/lib/security.js +0 -183
  133. package/lib/server/handlers/admin.js +0 -94
  134. package/lib/server/handlers/identity.js +0 -258
  135. package/lib/server/handlers/index.js +0 -42
  136. package/lib/server/handlers/message.js +0 -319
  137. package/lib/server/handlers/presence.js +0 -45
  138. package/lib/server/handlers/proposal.js +0 -358
  139. package/lib/server/handlers/skills.js +0 -141
  140. package/lib/server-directory.js +0 -190
  141. package/lib/server-directory.ts +0 -232
  142. package/lib/server.js +0 -633
  143. package/lib/server.ts +0 -698
  144. package/lib/supervisor/USAGE.md +0 -110
  145. package/lib/supervisor/agent-health.sh +0 -107
  146. package/lib/supervisor/agent-monitor.sh +0 -123
  147. package/lib/supervisor/agent-supervisor.sh +0 -135
  148. package/lib/supervisor/agentctl.sh +0 -266
  149. package/lib/supervisor/god-backup.sh +0 -126
  150. package/lib/supervisor/god-watchdog.sh +0 -107
  151. package/lib/supervisor/killswitch.sh +0 -43
  152. package/lib/supervisor/notify.sh +0 -19
  153. package/lib/types.ts +0 -433
package/lib/reputation.js DELETED
@@ -1,664 +0,0 @@
1
- /**
2
- * AgentChat Reputation Module
3
- * ELO-based rating system for agent reputation
4
- *
5
- * Adapts chess ELO for cooperative agent coordination:
6
- * - Each agent starts at 1200
7
- * - On COMPLETE: both agents gain points, scaled by counterparty rating
8
- * - On DISPUTE: at-fault party loses points
9
- * - K-factor varies by experience (new agents move faster)
10
- */
11
-
12
- import fs from 'fs';
13
- import fsp from 'fs/promises';
14
- import path from 'path';
15
- import os from 'os';
16
-
17
- // Default ratings file location
18
- const AGENTCHAT_DIR = path.join(process.cwd(), '.agentchat');
19
- export const DEFAULT_RATINGS_PATH = path.join(AGENTCHAT_DIR, 'ratings.json');
20
-
21
- // ELO constants
22
- export const DEFAULT_RATING = 1200;
23
- export const ELO_DIVISOR = 400; // Standard ELO divisor
24
- export const MINIMUM_RATING = 100; // Floor - can't drop below this
25
-
26
- // K-factor thresholds
27
- const K_FACTOR_NEW = 32; // < 30 transactions
28
- const K_FACTOR_INTERMEDIATE = 24; // < 100 transactions
29
- const K_FACTOR_ESTABLISHED = 16; // >= 100 transactions
30
-
31
- const TRANSACTIONS_NEW = 30;
32
- const TRANSACTIONS_INTERMEDIATE = 100;
33
-
34
- /**
35
- * Calculate expected outcome (standard ELO formula)
36
- * E = 1 / (1 + 10^((R_opponent - R_self) / 400))
37
- *
38
- * @param {number} selfRating - Your rating
39
- * @param {number} opponentRating - Counterparty rating
40
- * @returns {number} Expected outcome (0-1)
41
- */
42
- export function calculateExpected(selfRating, opponentRating) {
43
- const exponent = (opponentRating - selfRating) / ELO_DIVISOR;
44
- return 1 / (1 + Math.pow(10, exponent));
45
- }
46
-
47
- /**
48
- * Get K-factor based on transaction count
49
- * New agents have higher K (volatile), established agents lower K (stable)
50
- *
51
- * @param {number} transactions - Number of completed transactions
52
- * @returns {number} K-factor
53
- */
54
- export function getKFactor(transactions) {
55
- if (transactions < TRANSACTIONS_NEW) {
56
- return K_FACTOR_NEW;
57
- } else if (transactions < TRANSACTIONS_INTERMEDIATE) {
58
- return K_FACTOR_INTERMEDIATE;
59
- }
60
- return K_FACTOR_ESTABLISHED;
61
- }
62
-
63
- /**
64
- * Calculate effective K-factor with optional task value weighting
65
- * effective_K = K * (1 + log10(amount + 1))
66
- *
67
- * @param {number} baseK - Base K-factor
68
- * @param {number} amount - Task value/amount (optional)
69
- * @returns {number} Effective K-factor
70
- */
71
- export function getEffectiveK(baseK, amount = 0) {
72
- if (!amount || amount <= 0) {
73
- return baseK;
74
- }
75
- // Weight by task value: higher value = more rating movement
76
- // Cap the multiplier to prevent extreme swings
77
- const multiplier = Math.min(1 + Math.log10(amount + 1), 3);
78
- return baseK * multiplier;
79
- }
80
-
81
- /**
82
- * Calculate rating change for a completion (cooperative outcome)
83
- * Both parties gain, but you gain more when completing with higher-rated counterparty
84
- *
85
- * @param {number} selfRating - Your current rating
86
- * @param {number} counterpartyRating - Counterparty's rating
87
- * @param {number} kFactor - Your K-factor
88
- * @param {number} amount - Optional task value
89
- * @returns {number} Rating change (positive)
90
- */
91
- export function calculateCompletionGain(selfRating, counterpartyRating, kFactor, amount = 0) {
92
- const expected = calculateExpected(selfRating, counterpartyRating);
93
- const effectiveK = getEffectiveK(kFactor, amount);
94
-
95
- // Gain = K * (1 - E)
96
- // You gain more when completing with higher-rated counterparty (lower E)
97
- const gain = effectiveK * (1 - expected);
98
-
99
- // Minimum gain of 1 point for any completion
100
- return Math.max(1, Math.round(gain));
101
- }
102
-
103
- /**
104
- * Calculate rating change for a dispute (loss for at-fault party)
105
- *
106
- * @param {number} selfRating - Your current rating
107
- * @param {number} counterpartyRating - Counterparty's rating
108
- * @param {number} kFactor - Your K-factor
109
- * @param {number} amount - Optional task value
110
- * @returns {number} Rating change (negative)
111
- */
112
- export function calculateDisputeLoss(selfRating, counterpartyRating, kFactor, amount = 0) {
113
- const expected = calculateExpected(selfRating, counterpartyRating);
114
- const effectiveK = getEffectiveK(kFactor, amount);
115
-
116
- // Loss = K * E
117
- // You lose more when you were expected to succeed (higher E)
118
- const loss = effectiveK * expected;
119
-
120
- // Minimum loss of 1 point
121
- return -Math.max(1, Math.round(loss));
122
- }
123
-
124
- /**
125
- * Reputation Store - manages agent ratings
126
- */
127
- export class ReputationStore {
128
- constructor(ratingsPath = DEFAULT_RATINGS_PATH) {
129
- this.ratingsPath = ratingsPath;
130
- this._ratings = null; // Lazy load
131
- this._escrows = new Map(); // proposalId → escrow record
132
- }
133
-
134
- /**
135
- * Load ratings from file
136
- */
137
- async load() {
138
- try {
139
- const content = await fsp.readFile(this.ratingsPath, 'utf-8');
140
- this._ratings = JSON.parse(content);
141
- } catch (err) {
142
- if (err.code === 'ENOENT') {
143
- this._ratings = {}; // No ratings file yet
144
- } else {
145
- throw err;
146
- }
147
- }
148
- return this._ratings;
149
- }
150
-
151
- /**
152
- * Save ratings to file
153
- */
154
- async save() {
155
- await fsp.mkdir(path.dirname(this.ratingsPath), { recursive: true });
156
- await fsp.writeFile(
157
- this.ratingsPath,
158
- JSON.stringify(this._ratings, null, 2),
159
- { mode: 0o600 }
160
- );
161
- }
162
-
163
- /**
164
- * Ensure ratings are loaded
165
- */
166
- async _ensureLoaded() {
167
- if (this._ratings === null) {
168
- await this.load();
169
- }
170
- }
171
-
172
- /**
173
- * Normalize agent ID (ensure @ prefix)
174
- */
175
- _normalizeId(agentId) {
176
- return agentId.startsWith('@') ? agentId : `@${agentId}`;
177
- }
178
-
179
- /**
180
- * Get rating for an agent
181
- * Returns default rating if agent not found
182
- */
183
- async getRating(agentId) {
184
- await this._ensureLoaded();
185
- const id = this._normalizeId(agentId);
186
- const record = this._ratings[id];
187
-
188
- if (!record) {
189
- return {
190
- agentId: id,
191
- rating: DEFAULT_RATING,
192
- transactions: 0,
193
- updated: null,
194
- isNew: true
195
- };
196
- }
197
-
198
- return {
199
- agentId: id,
200
- rating: record.rating,
201
- transactions: record.transactions,
202
- updated: record.updated,
203
- isNew: false
204
- };
205
- }
206
-
207
- /**
208
- * Get K-factor for an agent
209
- */
210
- async getAgentKFactor(agentId) {
211
- const record = await this.getRating(agentId);
212
- return getKFactor(record.transactions);
213
- }
214
-
215
- /**
216
- * Get total escrowed ELO for an agent
217
- */
218
- getEscrowedAmount(agentId) {
219
- const id = this._normalizeId(agentId);
220
- let total = 0;
221
- for (const escrow of this._escrows.values()) {
222
- if (escrow.status === 'active') {
223
- if (escrow.from.agent_id === id) {
224
- total += escrow.from.stake;
225
- }
226
- if (escrow.to.agent_id === id) {
227
- total += escrow.to.stake;
228
- }
229
- }
230
- }
231
- return total;
232
- }
233
-
234
- /**
235
- * Get available rating for staking (rating - escrowed - minimum floor)
236
- */
237
- async getAvailableRating(agentId) {
238
- const record = await this.getRating(agentId);
239
- const escrowed = this.getEscrowedAmount(agentId);
240
- const available = record.rating - escrowed - MINIMUM_RATING;
241
- return Math.max(0, available);
242
- }
243
-
244
- /**
245
- * Check if agent can stake the requested amount
246
- */
247
- async canStake(agentId, amount) {
248
- if (!amount || amount <= 0) {
249
- return { canStake: true, available: await this.getAvailableRating(agentId) };
250
- }
251
-
252
- const available = await this.getAvailableRating(agentId);
253
-
254
- if (amount > available) {
255
- return {
256
- canStake: false,
257
- available,
258
- reason: `Insufficient ELO. Available: ${available}, Requested: ${amount}`
259
- };
260
- }
261
-
262
- return { canStake: true, available };
263
- }
264
-
265
- /**
266
- * Create escrow for a proposal
267
- * Called when proposal is accepted with stakes
268
- */
269
- async createEscrow(proposalId, fromStake, toStake, expiresAt = null) {
270
- // Validate both parties can stake
271
- if (fromStake.stake > 0) {
272
- const canFrom = await this.canStake(fromStake.agent_id, fromStake.stake);
273
- if (!canFrom.canStake) {
274
- return { success: false, error: `Proposer: ${canFrom.reason}` };
275
- }
276
- }
277
-
278
- if (toStake.stake > 0) {
279
- const canTo = await this.canStake(toStake.agent_id, toStake.stake);
280
- if (!canTo.canStake) {
281
- return { success: false, error: `Acceptor: ${canTo.reason}` };
282
- }
283
- }
284
-
285
- const escrow = {
286
- proposal_id: proposalId,
287
- created_at: Date.now(),
288
- from: {
289
- agent_id: this._normalizeId(fromStake.agent_id),
290
- stake: fromStake.stake || 0
291
- },
292
- to: {
293
- agent_id: this._normalizeId(toStake.agent_id),
294
- stake: toStake.stake || 0
295
- },
296
- status: 'active',
297
- expires_at: expiresAt,
298
- settled_at: null,
299
- settlement_reason: null
300
- };
301
-
302
- this._escrows.set(proposalId, escrow);
303
- return { success: true, escrow };
304
- }
305
-
306
- /**
307
- * Get escrow for a proposal
308
- */
309
- getEscrow(proposalId) {
310
- return this._escrows.get(proposalId) || null;
311
- }
312
-
313
- /**
314
- * Release escrow (return stakes to both parties, no rating change)
315
- * Used for proposal expiration
316
- */
317
- releaseEscrow(proposalId) {
318
- const escrow = this._escrows.get(proposalId);
319
- if (!escrow) {
320
- return { released: false, error: 'Escrow not found' };
321
- }
322
-
323
- if (escrow.status !== 'active') {
324
- return { released: false, error: `Escrow already ${escrow.status}` };
325
- }
326
-
327
- escrow.status = 'released';
328
- escrow.settled_at = Date.now();
329
- escrow.settlement_reason = 'expired';
330
-
331
- return { released: true, escrow };
332
- }
333
-
334
- /**
335
- * Update rating for an agent
336
- */
337
- async _updateAgent(agentId, ratingChange) {
338
- await this._ensureLoaded();
339
- const id = this._normalizeId(agentId);
340
-
341
- if (!this._ratings[id]) {
342
- this._ratings[id] = {
343
- rating: DEFAULT_RATING,
344
- transactions: 0,
345
- updated: null
346
- };
347
- }
348
-
349
- this._ratings[id].rating = Math.max(100, this._ratings[id].rating + ratingChange);
350
- this._ratings[id].transactions += 1;
351
- this._ratings[id].updated = new Date().toISOString();
352
-
353
- return this._ratings[id];
354
- }
355
-
356
- /**
357
- * Process a COMPLETE receipt - both parties gain (halved gains with staking)
358
- *
359
- * @param {object} receipt - The COMPLETE receipt
360
- * @returns {object} Rating changes for both parties
361
- */
362
- async processCompletion(receipt) {
363
- // Extract parties from receipt
364
- const party1 = receipt.proposal?.from || receipt.from;
365
- const party2 = receipt.proposal?.to || receipt.to;
366
- const amount = receipt.proposal?.amount || receipt.amount || 0;
367
- const proposalId = receipt.proposal_id;
368
-
369
- if (!party1 || !party2) {
370
- throw new Error('Receipt missing party information');
371
- }
372
-
373
- // Get current ratings
374
- const rating1 = await this.getRating(party1);
375
- const rating2 = await this.getRating(party2);
376
-
377
- // Calculate gains (halved for staking model)
378
- const k1 = getKFactor(rating1.transactions);
379
- const k2 = getKFactor(rating2.transactions);
380
-
381
- const fullGain1 = calculateCompletionGain(rating1.rating, rating2.rating, k1, amount);
382
- const fullGain2 = calculateCompletionGain(rating2.rating, rating1.rating, k2, amount);
383
-
384
- // Half the gains (staking model: split gains to balance inflation)
385
- const gain1 = Math.max(1, Math.round(fullGain1 / 2));
386
- const gain2 = Math.max(1, Math.round(fullGain2 / 2));
387
-
388
- // Settle escrow if exists (return stakes)
389
- let escrowSettlement = null;
390
- if (proposalId) {
391
- const escrow = this._escrows.get(proposalId);
392
- if (escrow && escrow.status === 'active') {
393
- escrow.status = 'settled';
394
- escrow.settled_at = Date.now();
395
- escrow.settlement_reason = 'completed';
396
- escrowSettlement = {
397
- proposer_stake: escrow.from.stake,
398
- acceptor_stake: escrow.to.stake,
399
- settlement: 'returned'
400
- };
401
- }
402
- }
403
-
404
- // Apply updates
405
- const updated1 = await this._updateAgent(party1, gain1);
406
- const updated2 = await this._updateAgent(party2, gain2);
407
-
408
- // Save
409
- await this.save();
410
-
411
- const result = {
412
- [party1]: {
413
- oldRating: rating1.rating,
414
- newRating: updated1.rating,
415
- change: gain1,
416
- transactions: updated1.transactions
417
- },
418
- [party2]: {
419
- oldRating: rating2.rating,
420
- newRating: updated2.rating,
421
- change: gain2,
422
- transactions: updated2.transactions
423
- }
424
- };
425
-
426
- if (escrowSettlement) {
427
- result._escrow = escrowSettlement;
428
- }
429
-
430
- return result;
431
- }
432
-
433
- /**
434
- * Process a DISPUTE receipt
435
- * If disputed_by is set, they are the "winner" (counterparty is at fault)
436
- * Otherwise, both parties lose (mutual fault)
437
- * Stakes are transferred to winner or burned on mutual fault
438
- *
439
- * @param {object} receipt - The DISPUTE receipt
440
- * @returns {object} Rating changes for both parties
441
- */
442
- async processDispute(receipt) {
443
- const party1 = receipt.proposal?.from || receipt.from;
444
- const party2 = receipt.proposal?.to || receipt.to;
445
- const disputedBy = receipt.disputed_by;
446
- const amount = receipt.proposal?.amount || receipt.amount || 0;
447
- const proposalId = receipt.proposal_id;
448
-
449
- if (!party1 || !party2) {
450
- throw new Error('Receipt missing party information');
451
- }
452
-
453
- const rating1 = await this.getRating(party1);
454
- const rating2 = await this.getRating(party2);
455
-
456
- const k1 = getKFactor(rating1.transactions);
457
- const k2 = getKFactor(rating2.transactions);
458
-
459
- // Get escrow info for stake calculations
460
- let escrow = null;
461
- let stake1 = 0, stake2 = 0;
462
- if (proposalId) {
463
- escrow = this._escrows.get(proposalId);
464
- if (escrow && escrow.status === 'active') {
465
- stake1 = escrow.from.agent_id === this._normalizeId(party1)
466
- ? escrow.from.stake
467
- : escrow.to.stake;
468
- stake2 = escrow.from.agent_id === this._normalizeId(party2)
469
- ? escrow.from.stake
470
- : escrow.to.stake;
471
- }
472
- }
473
-
474
- let change1, change2;
475
- let escrowSettlement = null;
476
-
477
- if (disputedBy) {
478
- // The disputer is the "winner", counterparty at fault
479
- const atFault = disputedBy === party1 ? party2 : party1;
480
-
481
- if (atFault === party1) {
482
- // Party1 at fault: loses ELO + loses stake to party2
483
- change1 = calculateDisputeLoss(rating1.rating, rating2.rating, k1, amount) - stake1;
484
- change2 = Math.round(Math.abs(calculateDisputeLoss(rating1.rating, rating2.rating, k1, amount)) * 0.5) + stake1;
485
- escrowSettlement = {
486
- proposer_stake: escrow?.from.stake || 0,
487
- acceptor_stake: escrow?.to.stake || 0,
488
- settlement: 'transferred',
489
- transferred_to: party2,
490
- transferred_amount: stake1
491
- };
492
- } else {
493
- // Party2 at fault: loses ELO + loses stake to party1
494
- change2 = calculateDisputeLoss(rating2.rating, rating1.rating, k2, amount) - stake2;
495
- change1 = Math.round(Math.abs(calculateDisputeLoss(rating2.rating, rating1.rating, k2, amount)) * 0.5) + stake2;
496
- escrowSettlement = {
497
- proposer_stake: escrow?.from.stake || 0,
498
- acceptor_stake: escrow?.to.stake || 0,
499
- settlement: 'transferred',
500
- transferred_to: party1,
501
- transferred_amount: stake2
502
- };
503
- }
504
- } else {
505
- // Mutual fault - both lose ELO + both stakes burned
506
- change1 = calculateDisputeLoss(rating1.rating, rating2.rating, k1, amount) - stake1;
507
- change2 = calculateDisputeLoss(rating2.rating, rating1.rating, k2, amount) - stake2;
508
- escrowSettlement = {
509
- proposer_stake: escrow?.from.stake || 0,
510
- acceptor_stake: escrow?.to.stake || 0,
511
- settlement: 'burned',
512
- burned_amount: stake1 + stake2
513
- };
514
- }
515
-
516
- // Settle escrow
517
- if (escrow && escrow.status === 'active') {
518
- escrow.status = 'settled';
519
- escrow.settled_at = Date.now();
520
- escrow.settlement_reason = 'disputed';
521
- }
522
-
523
- const updated1 = await this._updateAgent(party1, change1);
524
- const updated2 = await this._updateAgent(party2, change2);
525
-
526
- await this.save();
527
-
528
- const result = {
529
- [party1]: {
530
- oldRating: rating1.rating,
531
- newRating: updated1.rating,
532
- change: change1,
533
- transactions: updated1.transactions
534
- },
535
- [party2]: {
536
- oldRating: rating2.rating,
537
- newRating: updated2.rating,
538
- change: change2,
539
- transactions: updated2.transactions
540
- }
541
- };
542
-
543
- if (escrowSettlement) {
544
- result._escrow = escrowSettlement;
545
- }
546
-
547
- return result;
548
- }
549
-
550
- /**
551
- * Process a receipt (routes to completion or dispute)
552
- */
553
- async updateRatings(receipt) {
554
- const type = receipt.type || receipt.status;
555
-
556
- if (type === 'COMPLETE' || type === 'completed') {
557
- return this.processCompletion(receipt);
558
- } else if (type === 'DISPUTE' || type === 'disputed') {
559
- return this.processDispute(receipt);
560
- }
561
-
562
- // Not a rating-relevant receipt type
563
- return null;
564
- }
565
-
566
- /**
567
- * Export all ratings
568
- */
569
- async exportRatings() {
570
- await this._ensureLoaded();
571
- return { ...this._ratings };
572
- }
573
-
574
- /**
575
- * Get all ratings sorted by rating (descending)
576
- */
577
- async getLeaderboard(limit = 50) {
578
- await this._ensureLoaded();
579
-
580
- const entries = Object.entries(this._ratings)
581
- .map(([id, data]) => ({
582
- agentId: id,
583
- rating: data.rating,
584
- transactions: data.transactions,
585
- updated: data.updated
586
- }))
587
- .sort((a, b) => b.rating - a.rating)
588
- .slice(0, limit);
589
-
590
- return entries;
591
- }
592
-
593
- /**
594
- * Recalculate all ratings from receipt history
595
- *
596
- * @param {Array} receipts - Array of receipts to process
597
- */
598
- async recalculateFromReceipts(receipts) {
599
- // Reset ratings
600
- this._ratings = {};
601
-
602
- // Sort receipts by timestamp
603
- const sorted = [...receipts].sort((a, b) => {
604
- const tsA = a.completed_at || a.disputed_at || a.stored_at || 0;
605
- const tsB = b.completed_at || b.disputed_at || b.stored_at || 0;
606
- return tsA - tsB;
607
- });
608
-
609
- // Process each receipt
610
- for (const receipt of sorted) {
611
- try {
612
- await this.updateRatings(receipt);
613
- } catch (err) {
614
- // Skip invalid receipts
615
- console.error(`Skipping invalid receipt: ${err.message}`);
616
- }
617
- }
618
-
619
- // Save is called by updateRatings, but save final state
620
- await this.save();
621
-
622
- return this._ratings;
623
- }
624
-
625
- /**
626
- * Get statistics about the rating system
627
- */
628
- async getStats() {
629
- await this._ensureLoaded();
630
-
631
- const ratings = Object.values(this._ratings).map(r => r.rating);
632
-
633
- if (ratings.length === 0) {
634
- return {
635
- totalAgents: 0,
636
- averageRating: DEFAULT_RATING,
637
- highestRating: DEFAULT_RATING,
638
- lowestRating: DEFAULT_RATING,
639
- totalTransactions: 0
640
- };
641
- }
642
-
643
- const totalTransactions = Object.values(this._ratings)
644
- .reduce((sum, r) => sum + r.transactions, 0);
645
-
646
- return {
647
- totalAgents: ratings.length,
648
- averageRating: Math.round(ratings.reduce((a, b) => a + b, 0) / ratings.length),
649
- highestRating: Math.max(...ratings),
650
- lowestRating: Math.min(...ratings),
651
- totalTransactions
652
- };
653
- }
654
- }
655
-
656
- // Default instance for convenience
657
- let defaultStore = null;
658
-
659
- export function getDefaultStore() {
660
- if (!defaultStore) {
661
- defaultStore = new ReputationStore();
662
- }
663
- return defaultStore;
664
- }