@tjamescouch/agentchat 0.22.1 → 0.23.0

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
@@ -1,319 +0,0 @@
1
- /**
2
- * Message Handlers
3
- * Handles message routing, join, leave, and channel operations
4
- */
5
-
6
- import {
7
- ServerMessageType,
8
- ErrorCode,
9
- createMessage,
10
- createError,
11
- isChannel,
12
- isAgent,
13
- } from '../../protocol.js';
14
-
15
- /**
16
- * Handle MSG command - route messages to channels or agents
17
- */
18
- export function handleMsg(server, ws, msg) {
19
- const agent = server.agents.get(ws);
20
- if (!agent) {
21
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
22
- return;
23
- }
24
-
25
- // Rate limiting: 1 message per second per agent
26
- const now = Date.now();
27
- const lastTime = server.lastMessageTime.get(ws) || 0;
28
- if (now - lastTime < server.rateLimitMs) {
29
- server._send(ws, createError(ErrorCode.RATE_LIMITED, 'Rate limit exceeded (max 1 message per second)'));
30
- return;
31
- }
32
- server.lastMessageTime.set(ws, now);
33
-
34
- const outMsg = createMessage(ServerMessageType.MSG, {
35
- from: `@${agent.id}`,
36
- from_name: agent.name,
37
- to: msg.to,
38
- content: msg.content,
39
- ...(msg.sig && { sig: msg.sig })
40
- });
41
-
42
- if (isChannel(msg.to)) {
43
- // Channel message
44
- const channel = server.channels.get(msg.to);
45
- if (!channel) {
46
- server._send(ws, createError(ErrorCode.CHANNEL_NOT_FOUND, `Channel ${msg.to} not found`));
47
- return;
48
- }
49
-
50
- if (!agent.channels.has(msg.to)) {
51
- server._send(ws, createError(ErrorCode.NOT_INVITED, `Not a member of ${msg.to}`));
52
- return;
53
- }
54
-
55
- // Broadcast to channel including sender
56
- server._broadcast(msg.to, outMsg);
57
-
58
- // Buffer the message for replay to future joiners
59
- server._bufferMessage(msg.to, outMsg);
60
-
61
- // Update channel activity timestamp (for idle detection)
62
- server.channelLastActivity.set(msg.to, Date.now());
63
-
64
- } else if (isAgent(msg.to)) {
65
- // Direct message
66
- const targetId = msg.to.slice(1);
67
- const targetWs = server.agentById.get(targetId);
68
-
69
- if (!targetWs) {
70
- server._send(ws, createError(ErrorCode.AGENT_NOT_FOUND, `Agent ${msg.to} not found`));
71
- return;
72
- }
73
-
74
- // Send to target
75
- server._send(targetWs, outMsg);
76
- // Echo back to sender
77
- server._send(ws, outMsg);
78
- }
79
- }
80
-
81
- /**
82
- * Handle JOIN command - add agent to channel
83
- */
84
- export function handleJoin(server, ws, msg) {
85
- const agent = server.agents.get(ws);
86
- if (!agent) {
87
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
88
- return;
89
- }
90
-
91
- const channel = server.channels.get(msg.channel);
92
- if (!channel) {
93
- server._send(ws, createError(ErrorCode.CHANNEL_NOT_FOUND, `Channel ${msg.channel} not found`));
94
- return;
95
- }
96
-
97
- // Check invite-only
98
- if (channel.inviteOnly && !channel.invited.has(agent.id)) {
99
- server._send(ws, createError(ErrorCode.NOT_INVITED, `Channel ${msg.channel} is invite-only`));
100
- return;
101
- }
102
-
103
- // Add to channel
104
- channel.agents.add(ws);
105
- agent.channels.add(msg.channel);
106
-
107
- server._log('join', { agent: agent.id, channel: msg.channel });
108
-
109
- // Notify others
110
- server._broadcast(msg.channel, createMessage(ServerMessageType.AGENT_JOINED, {
111
- channel: msg.channel,
112
- agent: `@${agent.id}`,
113
- name: agent.name
114
- }), ws);
115
-
116
- // Send confirmation with agent list
117
- const agentList = [];
118
- for (const memberWs of channel.agents) {
119
- const member = server.agents.get(memberWs);
120
- if (member) agentList.push({ id: `@${member.id}`, name: member.name });
121
- }
122
-
123
- server._send(ws, createMessage(ServerMessageType.JOINED, {
124
- channel: msg.channel,
125
- agents: agentList
126
- }));
127
-
128
- // Replay recent messages to the joining agent
129
- server._replayMessages(ws, msg.channel);
130
-
131
- // Send welcome prompt to the new joiner
132
- server._send(ws, createMessage(ServerMessageType.MSG, {
133
- from: '@server',
134
- to: msg.channel,
135
- content: `Welcome to ${msg.channel}, ${agent.name} (@${agent.id})! Say hello to introduce yourself and start collaborating with other agents.`
136
- }));
137
-
138
- // Prompt existing agents to engage with the new joiner (if there are others)
139
- const otherAgents = [];
140
- for (const memberWs of channel.agents) {
141
- if (memberWs !== ws) {
142
- const member = server.agents.get(memberWs);
143
- if (member) otherAgents.push({ ws: memberWs, id: member.id, name: member.name });
144
- }
145
- }
146
-
147
- if (otherAgents.length > 0) {
148
- const welcomePrompt = createMessage(ServerMessageType.MSG, {
149
- from: '@server',
150
- to: msg.channel,
151
- content: `Hey ${otherAgents.map(a => `${a.name} (@${a.id})`).join(', ')} - new agent ${agent.name} (@${agent.id}) just joined! Say hi and share what you're working on.`
152
- });
153
-
154
- for (const other of otherAgents) {
155
- server._send(other.ws, welcomePrompt);
156
- }
157
- }
158
-
159
- // Update channel activity
160
- server.channelLastActivity.set(msg.channel, Date.now());
161
- }
162
-
163
- /**
164
- * Handle LEAVE command - remove agent from channel
165
- */
166
- export function handleLeave(server, ws, msg) {
167
- const agent = server.agents.get(ws);
168
- if (!agent) {
169
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
170
- return;
171
- }
172
-
173
- const channel = server.channels.get(msg.channel);
174
- if (!channel) return;
175
-
176
- channel.agents.delete(ws);
177
- agent.channels.delete(msg.channel);
178
-
179
- server._log('leave', { agent: agent.id, channel: msg.channel });
180
-
181
- // Notify others
182
- server._broadcast(msg.channel, createMessage(ServerMessageType.AGENT_LEFT, {
183
- channel: msg.channel,
184
- agent: `@${agent.id}`,
185
- name: agent.name
186
- }));
187
-
188
- server._send(ws, createMessage(ServerMessageType.LEFT, {
189
- channel: msg.channel
190
- }));
191
- }
192
-
193
- /**
194
- * Handle LIST_CHANNELS command
195
- * Unauthenticated: returns channel names and agent count only
196
- * Authenticated: returns full details
197
- */
198
- export function handleListChannels(server, ws) {
199
- const agent = server.agents.get(ws);
200
- const list = [];
201
- for (const [name, channel] of server.channels) {
202
- if (!channel.inviteOnly) {
203
- list.push({
204
- name,
205
- agents: channel.agents.size
206
- });
207
- }
208
- }
209
-
210
- server._send(ws, createMessage(ServerMessageType.CHANNELS, { list }));
211
- }
212
-
213
- /**
214
- * Handle LIST_AGENTS command
215
- * Requires authentication to see agent details
216
- */
217
- export function handleListAgents(server, ws, msg) {
218
- const agent = server.agents.get(ws);
219
- if (!agent) {
220
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
221
- return;
222
- }
223
-
224
- const channel = server.channels.get(msg.channel);
225
- if (!channel) {
226
- server._send(ws, createError(ErrorCode.CHANNEL_NOT_FOUND, `Channel ${msg.channel} not found`));
227
- return;
228
- }
229
-
230
- const list = [];
231
- for (const memberWs of channel.agents) {
232
- const member = server.agents.get(memberWs);
233
- if (member) {
234
- list.push({
235
- id: `@${member.id}`,
236
- name: member.name,
237
- presence: member.presence || 'online',
238
- status_text: member.statusText || null
239
- });
240
- }
241
- }
242
-
243
- server._send(ws, createMessage(ServerMessageType.AGENTS, {
244
- channel: msg.channel,
245
- list
246
- }));
247
- }
248
-
249
- /**
250
- * Handle CREATE_CHANNEL command
251
- */
252
- export function handleCreateChannel(server, ws, msg) {
253
- const agent = server.agents.get(ws);
254
- if (!agent) {
255
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
256
- return;
257
- }
258
-
259
- if (server.channels.has(msg.channel)) {
260
- server._send(ws, createError(ErrorCode.CHANNEL_EXISTS, `Channel ${msg.channel} already exists`));
261
- return;
262
- }
263
-
264
- const channel = server._createChannel(msg.channel, msg.invite_only || false);
265
-
266
- // Creator is automatically invited and joined
267
- if (channel.inviteOnly) {
268
- channel.invited.add(agent.id);
269
- }
270
-
271
- server._log('create_channel', { agent: agent.id, channel: msg.channel, inviteOnly: channel.inviteOnly });
272
-
273
- // Auto-join creator
274
- channel.agents.add(ws);
275
- agent.channels.add(msg.channel);
276
-
277
- server._send(ws, createMessage(ServerMessageType.JOINED, {
278
- channel: msg.channel,
279
- agents: [`@${agent.id}`]
280
- }));
281
- }
282
-
283
- /**
284
- * Handle INVITE command
285
- */
286
- export function handleInvite(server, ws, msg) {
287
- const agent = server.agents.get(ws);
288
- if (!agent) {
289
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
290
- return;
291
- }
292
-
293
- const channel = server.channels.get(msg.channel);
294
- if (!channel) {
295
- server._send(ws, createError(ErrorCode.CHANNEL_NOT_FOUND, `Channel ${msg.channel} not found`));
296
- return;
297
- }
298
-
299
- // Must be a member to invite
300
- if (!agent.channels.has(msg.channel)) {
301
- server._send(ws, createError(ErrorCode.NOT_INVITED, `Not a member of ${msg.channel}`));
302
- return;
303
- }
304
-
305
- const targetId = msg.agent.slice(1);
306
- channel.invited.add(targetId);
307
-
308
- server._log('invite', { agent: agent.id, target: targetId, channel: msg.channel });
309
-
310
- // Notify target if connected
311
- const targetWs = server.agentById.get(targetId);
312
- if (targetWs) {
313
- server._send(targetWs, createMessage(ServerMessageType.MSG, {
314
- from: `@${agent.id}`,
315
- to: msg.agent,
316
- content: `You have been invited to ${msg.channel}`
317
- }));
318
- }
319
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * Presence Handlers
3
- * Handles presence status updates
4
- */
5
-
6
- import {
7
- ServerMessageType,
8
- ErrorCode,
9
- createMessage,
10
- createError,
11
- } from '../../protocol.js';
12
-
13
- /**
14
- * Handle SET_PRESENCE command
15
- */
16
- export function handleSetPresence(server, ws, msg) {
17
- const agent = server.agents.get(ws);
18
- if (!agent) {
19
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
20
- return;
21
- }
22
-
23
- const oldPresence = agent.presence;
24
- agent.presence = msg.status;
25
- agent.statusText = msg.status_text || null;
26
-
27
- server._log('presence_changed', {
28
- agent: agent.id,
29
- from: oldPresence,
30
- to: msg.status,
31
- statusText: agent.statusText
32
- });
33
-
34
- // Broadcast presence change to all channels the agent is in
35
- const presenceMsg = createMessage(ServerMessageType.PRESENCE_CHANGED, {
36
- agent_id: `@${agent.id}`,
37
- name: agent.name,
38
- presence: agent.presence,
39
- status_text: agent.statusText
40
- });
41
-
42
- for (const channelName of agent.channels) {
43
- server._broadcast(channelName, presenceMsg);
44
- }
45
- }
@@ -1,358 +0,0 @@
1
- /**
2
- * Proposal Handlers
3
- * Handles proposal, accept, reject, complete, dispute operations
4
- */
5
-
6
- import {
7
- ServerMessageType,
8
- ErrorCode,
9
- createMessage,
10
- createError,
11
- } from '../../protocol.js';
12
- import { formatProposal, formatProposalResponse } from '../../proposals.js';
13
- import {
14
- EscrowEvent,
15
- createEscrowCreatedPayload,
16
- createCompletionPayload,
17
- createDisputePayload,
18
- } from '../../escrow-hooks.js';
19
-
20
- /**
21
- * Handle PROPOSAL command
22
- */
23
- export function handleProposal(server, ws, msg) {
24
- const agent = server.agents.get(ws);
25
- if (!agent) {
26
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
27
- return;
28
- }
29
-
30
- // Proposals require a persistent identity (signature verification)
31
- if (!agent.pubkey) {
32
- server._send(ws, createError(ErrorCode.SIGNATURE_REQUIRED, 'Proposals require persistent identity'));
33
- return;
34
- }
35
-
36
- const targetId = msg.to.slice(1);
37
- const targetWs = server.agentById.get(targetId);
38
-
39
- if (!targetWs) {
40
- server._send(ws, createError(ErrorCode.AGENT_NOT_FOUND, `Agent ${msg.to} not found`));
41
- return;
42
- }
43
-
44
- // Create proposal in store
45
- const proposal = server.proposals.create({
46
- from: `@${agent.id}`,
47
- to: msg.to,
48
- task: msg.task,
49
- amount: msg.amount,
50
- currency: msg.currency,
51
- payment_code: msg.payment_code,
52
- terms: msg.terms,
53
- expires: msg.expires,
54
- sig: msg.sig,
55
- elo_stake: msg.elo_stake || null
56
- });
57
-
58
- server._log('proposal', { id: proposal.id, from: agent.id, to: targetId });
59
-
60
- // Send to target
61
- const outMsg = createMessage(ServerMessageType.PROPOSAL, {
62
- ...formatProposal(proposal)
63
- });
64
-
65
- server._send(targetWs, outMsg);
66
- // Echo back to sender with the assigned ID
67
- server._send(ws, outMsg);
68
- }
69
-
70
- /**
71
- * Handle ACCEPT command
72
- */
73
- export async function handleAccept(server, ws, msg) {
74
- const agent = server.agents.get(ws);
75
- if (!agent) {
76
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
77
- return;
78
- }
79
-
80
- if (!agent.pubkey) {
81
- server._send(ws, createError(ErrorCode.SIGNATURE_REQUIRED, 'Accepting proposals requires persistent identity'));
82
- return;
83
- }
84
-
85
- // Get proposal first to check stakes
86
- const existingProposal = server.proposals.get(msg.proposal_id);
87
- if (!existingProposal) {
88
- server._send(ws, createError(ErrorCode.PROPOSAL_NOT_FOUND, 'Proposal not found'));
89
- return;
90
- }
91
-
92
- const proposerStake = existingProposal.proposer_stake || 0;
93
- const acceptorStake = msg.elo_stake || 0;
94
-
95
- // Validate proposer can stake (if they declared a stake)
96
- if (proposerStake > 0) {
97
- const canProposerStake = await server.reputationStore.canStake(existingProposal.from, proposerStake);
98
- if (!canProposerStake.canStake) {
99
- server._send(ws, createError(ErrorCode.INSUFFICIENT_REPUTATION, `Proposer: ${canProposerStake.reason}`));
100
- return;
101
- }
102
- }
103
-
104
- // Validate acceptor can stake (if they declared a stake)
105
- if (acceptorStake > 0) {
106
- const canAcceptorStake = await server.reputationStore.canStake(`@${agent.id}`, acceptorStake);
107
- if (!canAcceptorStake.canStake) {
108
- server._send(ws, createError(ErrorCode.INSUFFICIENT_REPUTATION, canAcceptorStake.reason));
109
- return;
110
- }
111
- }
112
-
113
- const result = server.proposals.accept(
114
- msg.proposal_id,
115
- `@${agent.id}`,
116
- msg.sig,
117
- msg.payment_code,
118
- acceptorStake
119
- );
120
-
121
- if (result.error) {
122
- server._send(ws, createError(ErrorCode.INVALID_PROPOSAL, result.error));
123
- return;
124
- }
125
-
126
- const proposal = result.proposal;
127
-
128
- // Create escrow if either party has a stake
129
- if (proposerStake > 0 || acceptorStake > 0) {
130
- const escrowResult = await server.reputationStore.createEscrow(
131
- proposal.id,
132
- { agent_id: proposal.from, stake: proposerStake },
133
- { agent_id: proposal.to, stake: acceptorStake },
134
- proposal.expires
135
- );
136
-
137
- if (escrowResult.success) {
138
- proposal.stakes_escrowed = true;
139
- server._log('escrow_created', {
140
- proposal_id: proposal.id,
141
- proposer_stake: proposerStake,
142
- acceptor_stake: acceptorStake
143
- });
144
-
145
- // Emit escrow:created hook for external integrations
146
- server.escrowHooks.emit(EscrowEvent.CREATED, createEscrowCreatedPayload(proposal, escrowResult))
147
- .catch(err => server._log('escrow_hook_error', { event: 'created', error: err.message }));
148
- } else {
149
- server._log('escrow_error', { proposal_id: proposal.id, error: escrowResult.error });
150
- }
151
- }
152
-
153
- server._log('accept', { id: proposal.id, by: agent.id, proposer_stake: proposerStake, acceptor_stake: acceptorStake });
154
-
155
- // Notify the proposal creator
156
- const creatorId = proposal.from.slice(1);
157
- const creatorWs = server.agentById.get(creatorId);
158
-
159
- const outMsg = createMessage(ServerMessageType.ACCEPT, {
160
- ...formatProposalResponse(proposal, 'accept')
161
- });
162
-
163
- if (creatorWs) {
164
- server._send(creatorWs, outMsg);
165
- }
166
- // Echo to acceptor
167
- server._send(ws, outMsg);
168
- }
169
-
170
- /**
171
- * Handle REJECT command
172
- */
173
- export function handleReject(server, ws, msg) {
174
- const agent = server.agents.get(ws);
175
- if (!agent) {
176
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
177
- return;
178
- }
179
-
180
- if (!agent.pubkey) {
181
- server._send(ws, createError(ErrorCode.SIGNATURE_REQUIRED, 'Rejecting proposals requires persistent identity'));
182
- return;
183
- }
184
-
185
- const result = server.proposals.reject(
186
- msg.proposal_id,
187
- `@${agent.id}`,
188
- msg.sig,
189
- msg.reason
190
- );
191
-
192
- if (result.error) {
193
- server._send(ws, createError(ErrorCode.INVALID_PROPOSAL, result.error));
194
- return;
195
- }
196
-
197
- const proposal = result.proposal;
198
- server._log('reject', { id: proposal.id, by: agent.id });
199
-
200
- // Notify the proposal creator
201
- const creatorId = proposal.from.slice(1);
202
- const creatorWs = server.agentById.get(creatorId);
203
-
204
- const outMsg = createMessage(ServerMessageType.REJECT, {
205
- ...formatProposalResponse(proposal, 'reject')
206
- });
207
-
208
- if (creatorWs) {
209
- server._send(creatorWs, outMsg);
210
- }
211
- // Echo to rejector
212
- server._send(ws, outMsg);
213
- }
214
-
215
- /**
216
- * Handle COMPLETE command
217
- */
218
- export async function handleComplete(server, ws, msg) {
219
- const agent = server.agents.get(ws);
220
- if (!agent) {
221
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
222
- return;
223
- }
224
-
225
- if (!agent.pubkey) {
226
- server._send(ws, createError(ErrorCode.SIGNATURE_REQUIRED, 'Completing proposals requires persistent identity'));
227
- return;
228
- }
229
-
230
- const result = server.proposals.complete(
231
- msg.proposal_id,
232
- `@${agent.id}`,
233
- msg.sig,
234
- msg.proof
235
- );
236
-
237
- if (result.error) {
238
- server._send(ws, createError(ErrorCode.INVALID_PROPOSAL, result.error));
239
- return;
240
- }
241
-
242
- const proposal = result.proposal;
243
- server._log('complete', { id: proposal.id, by: agent.id });
244
-
245
- // Update reputation ratings (includes escrow settlement)
246
- let ratingChanges = null;
247
- try {
248
- ratingChanges = await server.reputationStore.processCompletion({
249
- type: 'COMPLETE',
250
- proposal_id: proposal.id,
251
- from: proposal.from,
252
- to: proposal.to,
253
- amount: proposal.amount
254
- });
255
- server._log('reputation_updated', {
256
- proposal_id: proposal.id,
257
- changes: ratingChanges,
258
- escrow: ratingChanges?._escrow
259
- });
260
-
261
- // Emit settlement:completion hook for external integrations
262
- if (ratingChanges?._escrow) {
263
- server.escrowHooks.emit(EscrowEvent.COMPLETION_SETTLED, createCompletionPayload(proposal, ratingChanges))
264
- .catch(err => server._log('escrow_hook_error', { event: 'completion', error: err.message }));
265
- }
266
- } catch (err) {
267
- server._log('reputation_error', { error: err.message });
268
- }
269
-
270
- // Notify both parties
271
- const outMsg = createMessage(ServerMessageType.COMPLETE, {
272
- ...formatProposalResponse(proposal, 'complete'),
273
- rating_changes: ratingChanges
274
- });
275
-
276
- // Notify the other party
277
- const otherId = proposal.from === `@${agent.id}` ? proposal.to.slice(1) : proposal.from.slice(1);
278
- const otherWs = server.agentById.get(otherId);
279
-
280
- if (otherWs) {
281
- server._send(otherWs, outMsg);
282
- }
283
- // Echo to completer
284
- server._send(ws, outMsg);
285
- }
286
-
287
- /**
288
- * Handle DISPUTE command
289
- */
290
- export async function handleDispute(server, ws, msg) {
291
- const agent = server.agents.get(ws);
292
- if (!agent) {
293
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
294
- return;
295
- }
296
-
297
- if (!agent.pubkey) {
298
- server._send(ws, createError(ErrorCode.SIGNATURE_REQUIRED, 'Disputing proposals requires persistent identity'));
299
- return;
300
- }
301
-
302
- const result = server.proposals.dispute(
303
- msg.proposal_id,
304
- `@${agent.id}`,
305
- msg.sig,
306
- msg.reason
307
- );
308
-
309
- if (result.error) {
310
- server._send(ws, createError(ErrorCode.INVALID_PROPOSAL, result.error));
311
- return;
312
- }
313
-
314
- const proposal = result.proposal;
315
- server._log('dispute', { id: proposal.id, by: agent.id, reason: msg.reason });
316
-
317
- // Update reputation ratings (includes escrow settlement)
318
- let ratingChanges = null;
319
- try {
320
- ratingChanges = await server.reputationStore.processDispute({
321
- type: 'DISPUTE',
322
- proposal_id: proposal.id,
323
- from: proposal.from,
324
- to: proposal.to,
325
- amount: proposal.amount,
326
- disputed_by: `@${agent.id}`
327
- });
328
- server._log('reputation_updated', {
329
- proposal_id: proposal.id,
330
- changes: ratingChanges,
331
- escrow: ratingChanges?._escrow
332
- });
333
-
334
- // Emit settlement:dispute hook for external integrations
335
- if (ratingChanges?._escrow) {
336
- server.escrowHooks.emit(EscrowEvent.DISPUTE_SETTLED, createDisputePayload(proposal, ratingChanges))
337
- .catch(err => server._log('escrow_hook_error', { event: 'dispute', error: err.message }));
338
- }
339
- } catch (err) {
340
- server._log('reputation_error', { error: err.message });
341
- }
342
-
343
- // Notify both parties
344
- const outMsg = createMessage(ServerMessageType.DISPUTE, {
345
- ...formatProposalResponse(proposal, 'dispute'),
346
- rating_changes: ratingChanges
347
- });
348
-
349
- // Notify the other party
350
- const otherId = proposal.from === `@${agent.id}` ? proposal.to.slice(1) : proposal.from.slice(1);
351
- const otherWs = server.agentById.get(otherId);
352
-
353
- if (otherWs) {
354
- server._send(otherWs, outMsg);
355
- }
356
- // Echo to disputer
357
- server._send(ws, outMsg);
358
- }