@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.
- package/Dockerfile +1 -1
- package/dist/bin/agentchat.d.ts +7 -0
- package/dist/bin/agentchat.d.ts.map +1 -0
- package/dist/bin/agentchat.js +1511 -0
- package/dist/bin/agentchat.js.map +1 -0
- package/dist/lib/allowlist.d.ts +77 -0
- package/dist/lib/allowlist.d.ts.map +1 -0
- package/dist/lib/allowlist.js +151 -0
- package/dist/lib/allowlist.js.map +1 -0
- package/dist/lib/client.d.ts +147 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +704 -0
- package/dist/lib/client.js.map +1 -0
- package/dist/lib/daemon.d.ts +122 -0
- package/dist/lib/daemon.d.ts.map +1 -0
- package/dist/lib/daemon.js +523 -0
- package/dist/lib/daemon.js.map +1 -0
- package/dist/lib/deploy/akash.d.ts +271 -0
- package/dist/lib/deploy/akash.d.ts.map +1 -0
- package/dist/lib/deploy/akash.js +671 -0
- package/dist/lib/deploy/akash.js.map +1 -0
- package/dist/lib/deploy/config.d.ts +62 -0
- package/dist/lib/deploy/config.d.ts.map +1 -0
- package/dist/lib/deploy/config.js +116 -0
- package/dist/lib/deploy/config.js.map +1 -0
- package/dist/lib/deploy/docker.d.ts +37 -0
- package/dist/lib/deploy/docker.d.ts.map +1 -0
- package/dist/lib/deploy/docker.js +122 -0
- package/dist/lib/deploy/docker.js.map +1 -0
- package/dist/lib/deploy/index.d.ts +11 -0
- package/dist/lib/deploy/index.d.ts.map +1 -0
- package/dist/lib/deploy/index.js +11 -0
- package/dist/lib/deploy/index.js.map +1 -0
- package/dist/lib/escrow-hooks.d.ts +199 -0
- package/dist/lib/escrow-hooks.d.ts.map +1 -0
- package/dist/lib/escrow-hooks.js +221 -0
- package/dist/lib/escrow-hooks.js.map +1 -0
- package/dist/lib/identity.d.ts +134 -0
- package/dist/lib/identity.d.ts.map +1 -0
- package/dist/lib/identity.js +334 -0
- package/dist/lib/identity.js.map +1 -0
- package/dist/lib/jitter.d.ts +42 -0
- package/dist/lib/jitter.d.ts.map +1 -0
- package/{lib/jitter.ts → dist/lib/jitter.js} +10 -18
- package/dist/lib/jitter.js.map +1 -0
- package/dist/lib/proposals.d.ts +223 -0
- package/dist/lib/proposals.d.ts.map +1 -0
- package/dist/lib/proposals.js +379 -0
- package/dist/lib/proposals.js.map +1 -0
- package/dist/lib/protocol.d.ts +220 -0
- package/dist/lib/protocol.d.ts.map +1 -0
- package/dist/lib/protocol.js +507 -0
- package/dist/lib/protocol.js.map +1 -0
- package/dist/lib/receipts.d.ts +134 -0
- package/dist/lib/receipts.d.ts.map +1 -0
- package/dist/lib/receipts.js +270 -0
- package/dist/lib/receipts.js.map +1 -0
- package/dist/lib/reputation.d.ts +250 -0
- package/dist/lib/reputation.d.ts.map +1 -0
- package/dist/lib/reputation.js +586 -0
- package/dist/lib/reputation.js.map +1 -0
- package/dist/lib/security.d.ts +27 -0
- package/dist/lib/security.d.ts.map +1 -0
- package/dist/lib/security.js +150 -0
- package/dist/lib/security.js.map +1 -0
- package/dist/lib/server/handlers/admin.d.ts +26 -0
- package/dist/lib/server/handlers/admin.d.ts.map +1 -0
- package/dist/lib/server/handlers/admin.js +76 -0
- package/dist/lib/server/handlers/admin.js.map +1 -0
- package/dist/lib/server/handlers/identity.d.ts +36 -0
- package/dist/lib/server/handlers/identity.d.ts.map +1 -0
- package/dist/lib/server/handlers/identity.js +330 -0
- package/dist/lib/server/handlers/identity.js.map +1 -0
- package/dist/lib/server/handlers/index.d.ts +10 -0
- package/dist/lib/server/handlers/index.d.ts.map +1 -0
- package/dist/lib/server/handlers/index.js +15 -0
- package/dist/lib/server/handlers/index.js.map +1 -0
- package/dist/lib/server/handlers/message.d.ts +47 -0
- package/dist/lib/server/handlers/message.d.ts.map +1 -0
- package/dist/lib/server/handlers/message.js +265 -0
- package/dist/lib/server/handlers/message.js.map +1 -0
- package/dist/lib/server/handlers/presence.d.ts +18 -0
- package/dist/lib/server/handlers/presence.d.ts.map +1 -0
- package/dist/lib/server/handlers/presence.js +35 -0
- package/dist/lib/server/handlers/presence.js.map +1 -0
- package/dist/lib/server/handlers/proposal.d.ts +38 -0
- package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
- package/dist/lib/server/handlers/proposal.js +273 -0
- package/dist/lib/server/handlers/proposal.js.map +1 -0
- package/dist/lib/server/handlers/skills.d.ts +22 -0
- package/dist/lib/server/handlers/skills.d.ts.map +1 -0
- package/dist/lib/server/handlers/skills.js +119 -0
- package/dist/lib/server/handlers/skills.js.map +1 -0
- package/dist/lib/server-directory.d.ts +85 -0
- package/dist/lib/server-directory.d.ts.map +1 -0
- package/dist/lib/server-directory.js +177 -0
- package/dist/lib/server-directory.js.map +1 -0
- package/dist/lib/server.d.ts +162 -0
- package/dist/lib/server.d.ts.map +1 -0
- package/dist/lib/server.js +602 -0
- package/dist/lib/server.js.map +1 -0
- package/dist/lib/types.d.ts +461 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +98 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +22 -13
- package/bin/agentchat.js +0 -1617
- package/bin/agentchat.ts +0 -1812
- package/lib/allowlist.js +0 -162
- package/lib/chat.py +0 -241
- package/lib/client.js +0 -821
- package/lib/client.ts +0 -877
- package/lib/daemon.js +0 -562
- package/lib/daemon.ts +0 -662
- package/lib/deploy/akash.js +0 -811
- package/lib/deploy/config.js +0 -128
- package/lib/deploy/docker.js +0 -132
- package/lib/deploy/index.js +0 -24
- package/lib/elo_swarm.py +0 -569
- package/lib/escrow-hooks.js +0 -237
- package/lib/escrow-hooks.ts +0 -391
- package/lib/identity.js +0 -376
- package/lib/identity.ts +0 -412
- package/lib/jitter.js +0 -54
- package/lib/proposals.js +0 -426
- package/lib/proposals.ts +0 -612
- package/lib/protocol.js +0 -516
- package/lib/receipts.js +0 -294
- package/lib/receipts.ts +0 -359
- package/lib/reputation.js +0 -664
- package/lib/reputation.ts +0 -790
- package/lib/security.js +0 -183
- package/lib/server/handlers/admin.js +0 -94
- package/lib/server/handlers/identity.js +0 -258
- package/lib/server/handlers/index.js +0 -42
- package/lib/server/handlers/message.js +0 -319
- package/lib/server/handlers/presence.js +0 -45
- package/lib/server/handlers/proposal.js +0 -358
- package/lib/server/handlers/skills.js +0 -141
- package/lib/server-directory.js +0 -190
- package/lib/server-directory.ts +0 -232
- package/lib/server.js +0 -633
- package/lib/server.ts +0 -698
- package/lib/supervisor/USAGE.md +0 -110
- package/lib/supervisor/agent-health.sh +0 -107
- package/lib/supervisor/agent-monitor.sh +0 -123
- package/lib/supervisor/agent-supervisor.sh +0 -135
- package/lib/supervisor/agentctl.sh +0 -266
- package/lib/supervisor/god-backup.sh +0 -126
- package/lib/supervisor/god-watchdog.sh +0 -107
- package/lib/supervisor/killswitch.sh +0 -43
- package/lib/supervisor/notify.sh +0 -19
- 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
|
-
}
|