@tjamescouch/agentchat 0.22.0 → 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 (135) 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 → dist/lib}/jitter.js +16 -19
  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/lib/chat.py +0 -241
  109. package/lib/client.js +0 -821
  110. package/lib/daemon.js +0 -562
  111. package/lib/deploy/akash.js +0 -811
  112. package/lib/deploy/config.js +0 -128
  113. package/lib/deploy/docker.js +0 -132
  114. package/lib/deploy/index.js +0 -24
  115. package/lib/elo_swarm.py +0 -569
  116. package/lib/escrow-hooks.js +0 -237
  117. package/lib/identity.js +0 -376
  118. package/lib/proposals.js +0 -426
  119. package/lib/protocol.js +0 -484
  120. package/lib/receipts.js +0 -294
  121. package/lib/reputation.js +0 -664
  122. package/lib/security.js +0 -183
  123. package/lib/server/handlers/identity.js +0 -242
  124. package/lib/server/handlers/index.js +0 -42
  125. package/lib/server/handlers/message.js +0 -306
  126. package/lib/server/handlers/presence.js +0 -44
  127. package/lib/server/handlers/proposal.js +0 -358
  128. package/lib/server/handlers/skills.js +0 -141
  129. package/lib/server-directory.js +0 -181
  130. package/lib/server.js +0 -528
  131. package/lib/supervisor/USAGE.md +0 -110
  132. package/lib/supervisor/agent-supervisor.sh +0 -135
  133. package/lib/supervisor/agentctl.sh +0 -250
  134. package/lib/supervisor/killswitch.sh +0 -36
  135. package/lib/supervisor/notify.sh +0 -19
@@ -1,306 +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
- to: msg.to,
37
- content: msg.content,
38
- ...(msg.sig && { sig: msg.sig })
39
- });
40
-
41
- if (isChannel(msg.to)) {
42
- // Channel message
43
- const channel = server.channels.get(msg.to);
44
- if (!channel) {
45
- server._send(ws, createError(ErrorCode.CHANNEL_NOT_FOUND, `Channel ${msg.to} not found`));
46
- return;
47
- }
48
-
49
- if (!agent.channels.has(msg.to)) {
50
- server._send(ws, createError(ErrorCode.NOT_INVITED, `Not a member of ${msg.to}`));
51
- return;
52
- }
53
-
54
- // Broadcast to channel including sender
55
- server._broadcast(msg.to, outMsg);
56
-
57
- // Buffer the message for replay to future joiners
58
- server._bufferMessage(msg.to, outMsg);
59
-
60
- // Update channel activity timestamp (for idle detection)
61
- server.channelLastActivity.set(msg.to, Date.now());
62
-
63
- } else if (isAgent(msg.to)) {
64
- // Direct message
65
- const targetId = msg.to.slice(1);
66
- const targetWs = server.agentById.get(targetId);
67
-
68
- if (!targetWs) {
69
- server._send(ws, createError(ErrorCode.AGENT_NOT_FOUND, `Agent ${msg.to} not found`));
70
- return;
71
- }
72
-
73
- // Send to target
74
- server._send(targetWs, outMsg);
75
- // Echo back to sender
76
- server._send(ws, outMsg);
77
- }
78
- }
79
-
80
- /**
81
- * Handle JOIN command - add agent to channel
82
- */
83
- export function handleJoin(server, ws, msg) {
84
- const agent = server.agents.get(ws);
85
- if (!agent) {
86
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
87
- return;
88
- }
89
-
90
- const channel = server.channels.get(msg.channel);
91
- if (!channel) {
92
- server._send(ws, createError(ErrorCode.CHANNEL_NOT_FOUND, `Channel ${msg.channel} not found`));
93
- return;
94
- }
95
-
96
- // Check invite-only
97
- if (channel.inviteOnly && !channel.invited.has(agent.id)) {
98
- server._send(ws, createError(ErrorCode.NOT_INVITED, `Channel ${msg.channel} is invite-only`));
99
- return;
100
- }
101
-
102
- // Add to channel
103
- channel.agents.add(ws);
104
- agent.channels.add(msg.channel);
105
-
106
- server._log('join', { agent: agent.id, channel: msg.channel });
107
-
108
- // Notify others
109
- server._broadcast(msg.channel, createMessage(ServerMessageType.AGENT_JOINED, {
110
- channel: msg.channel,
111
- agent: `@${agent.id}`
112
- }), ws);
113
-
114
- // Send confirmation with agent list
115
- const agentList = [];
116
- for (const memberWs of channel.agents) {
117
- const member = server.agents.get(memberWs);
118
- if (member) agentList.push(`@${member.id}`);
119
- }
120
-
121
- server._send(ws, createMessage(ServerMessageType.JOINED, {
122
- channel: msg.channel,
123
- agents: agentList
124
- }));
125
-
126
- // Replay recent messages to the joining agent
127
- server._replayMessages(ws, msg.channel);
128
-
129
- // Send welcome prompt to the new joiner
130
- server._send(ws, createMessage(ServerMessageType.MSG, {
131
- from: '@server',
132
- to: msg.channel,
133
- content: `Welcome to ${msg.channel}, @${agent.id}! Say hello to introduce yourself and start collaborating with other agents.`
134
- }));
135
-
136
- // Prompt existing agents to engage with the new joiner (if there are others)
137
- const otherAgents = [];
138
- for (const memberWs of channel.agents) {
139
- if (memberWs !== ws) {
140
- const member = server.agents.get(memberWs);
141
- if (member) otherAgents.push({ ws: memberWs, id: member.id });
142
- }
143
- }
144
-
145
- if (otherAgents.length > 0) {
146
- const welcomePrompt = createMessage(ServerMessageType.MSG, {
147
- from: '@server',
148
- to: msg.channel,
149
- content: `Hey ${otherAgents.map(a => `@${a.id}`).join(', ')} - new agent @${agent.id} just joined! Say hi and share what you're working on.`
150
- });
151
-
152
- for (const other of otherAgents) {
153
- server._send(other.ws, welcomePrompt);
154
- }
155
- }
156
-
157
- // Update channel activity
158
- server.channelLastActivity.set(msg.channel, Date.now());
159
- }
160
-
161
- /**
162
- * Handle LEAVE command - remove agent from channel
163
- */
164
- export function handleLeave(server, ws, msg) {
165
- const agent = server.agents.get(ws);
166
- if (!agent) {
167
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
168
- return;
169
- }
170
-
171
- const channel = server.channels.get(msg.channel);
172
- if (!channel) return;
173
-
174
- channel.agents.delete(ws);
175
- agent.channels.delete(msg.channel);
176
-
177
- server._log('leave', { agent: agent.id, channel: msg.channel });
178
-
179
- // Notify others
180
- server._broadcast(msg.channel, createMessage(ServerMessageType.AGENT_LEFT, {
181
- channel: msg.channel,
182
- agent: `@${agent.id}`
183
- }));
184
-
185
- server._send(ws, createMessage(ServerMessageType.LEFT, {
186
- channel: msg.channel
187
- }));
188
- }
189
-
190
- /**
191
- * Handle LIST_CHANNELS command
192
- */
193
- export function handleListChannels(server, ws) {
194
- const list = [];
195
- for (const [name, channel] of server.channels) {
196
- if (!channel.inviteOnly) {
197
- list.push({
198
- name,
199
- agents: channel.agents.size
200
- });
201
- }
202
- }
203
-
204
- server._send(ws, createMessage(ServerMessageType.CHANNELS, { list }));
205
- }
206
-
207
- /**
208
- * Handle LIST_AGENTS command
209
- */
210
- export function handleListAgents(server, ws, msg) {
211
- const channel = server.channels.get(msg.channel);
212
- if (!channel) {
213
- server._send(ws, createError(ErrorCode.CHANNEL_NOT_FOUND, `Channel ${msg.channel} not found`));
214
- return;
215
- }
216
-
217
- const list = [];
218
- for (const memberWs of channel.agents) {
219
- const member = server.agents.get(memberWs);
220
- if (member) {
221
- list.push({
222
- id: `@${member.id}`,
223
- name: member.name,
224
- presence: member.presence || 'online',
225
- status_text: member.statusText || null
226
- });
227
- }
228
- }
229
-
230
- server._send(ws, createMessage(ServerMessageType.AGENTS, {
231
- channel: msg.channel,
232
- list
233
- }));
234
- }
235
-
236
- /**
237
- * Handle CREATE_CHANNEL command
238
- */
239
- export function handleCreateChannel(server, ws, msg) {
240
- const agent = server.agents.get(ws);
241
- if (!agent) {
242
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
243
- return;
244
- }
245
-
246
- if (server.channels.has(msg.channel)) {
247
- server._send(ws, createError(ErrorCode.CHANNEL_EXISTS, `Channel ${msg.channel} already exists`));
248
- return;
249
- }
250
-
251
- const channel = server._createChannel(msg.channel, msg.invite_only || false);
252
-
253
- // Creator is automatically invited and joined
254
- if (channel.inviteOnly) {
255
- channel.invited.add(agent.id);
256
- }
257
-
258
- server._log('create_channel', { agent: agent.id, channel: msg.channel, inviteOnly: channel.inviteOnly });
259
-
260
- // Auto-join creator
261
- channel.agents.add(ws);
262
- agent.channels.add(msg.channel);
263
-
264
- server._send(ws, createMessage(ServerMessageType.JOINED, {
265
- channel: msg.channel,
266
- agents: [`@${agent.id}`]
267
- }));
268
- }
269
-
270
- /**
271
- * Handle INVITE command
272
- */
273
- export function handleInvite(server, ws, msg) {
274
- const agent = server.agents.get(ws);
275
- if (!agent) {
276
- server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
277
- return;
278
- }
279
-
280
- const channel = server.channels.get(msg.channel);
281
- if (!channel) {
282
- server._send(ws, createError(ErrorCode.CHANNEL_NOT_FOUND, `Channel ${msg.channel} not found`));
283
- return;
284
- }
285
-
286
- // Must be a member to invite
287
- if (!agent.channels.has(msg.channel)) {
288
- server._send(ws, createError(ErrorCode.NOT_INVITED, `Not a member of ${msg.channel}`));
289
- return;
290
- }
291
-
292
- const targetId = msg.agent.slice(1);
293
- channel.invited.add(targetId);
294
-
295
- server._log('invite', { agent: agent.id, target: targetId, channel: msg.channel });
296
-
297
- // Notify target if connected
298
- const targetWs = server.agentById.get(targetId);
299
- if (targetWs) {
300
- server._send(targetWs, createMessage(ServerMessageType.MSG, {
301
- from: `@${agent.id}`,
302
- to: msg.agent,
303
- content: `You have been invited to ${msg.channel}`
304
- }));
305
- }
306
- }
@@ -1,44 +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
- presence: agent.presence,
38
- status_text: agent.statusText
39
- });
40
-
41
- for (const channelName of agent.channels) {
42
- server._broadcast(channelName, presenceMsg);
43
- }
44
- }
@@ -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
- }