ai2ai 0.1.0 → 1.0.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 (95) hide show
  1. package/.keys/agent.key +3 -0
  2. package/.keys/agent.pub +3 -0
  3. package/.keys/x25519.key.der +0 -0
  4. package/.keys/x25519.pub.der +0 -0
  5. package/SKILL.md +39 -0
  6. package/ai2ai-client.js +351 -0
  7. package/ai2ai-conversations.js +324 -0
  8. package/{lib/crypto.js → ai2ai-crypto.js} +19 -39
  9. package/ai2ai-discovery.js +398 -0
  10. package/ai2ai-encryption.js +292 -0
  11. package/ai2ai-handlers.js +392 -0
  12. package/ai2ai-logger.js +148 -0
  13. package/ai2ai-queue.js +281 -0
  14. package/ai2ai-server.js +433 -0
  15. package/ai2ai-trust.js +137 -0
  16. package/client.js +434 -0
  17. package/contacts.example.json +45 -0
  18. package/contacts.json +57 -0
  19. package/conversations/06dfc9fc-e0fb-47a6-80ea-6f89b805dcc9.jsonl +1 -0
  20. package/conversations/06dfc9fc-e0fb-47a6-80ea-6f89b805dcc9.meta.json +31 -0
  21. package/conversations/132889ee-3c68-4a86-a465-829c467f6782.jsonl +1 -0
  22. package/conversations/132889ee-3c68-4a86-a465-829c467f6782.meta.json +27 -0
  23. package/conversations/16c99cf3-7250-4136-8d4a-f5214bcd32ba.jsonl +1 -0
  24. package/conversations/16c99cf3-7250-4136-8d4a-f5214bcd32ba.meta.json +27 -0
  25. package/conversations/3f62daf5-49cb-4f9b-9d25-de70625f46e2.jsonl +1 -0
  26. package/conversations/3f62daf5-49cb-4f9b-9d25-de70625f46e2.meta.json +31 -0
  27. package/conversations/532b39ab-d513-4e40-98ff-2d3df2d5f256.jsonl +1 -0
  28. package/conversations/532b39ab-d513-4e40-98ff-2d3df2d5f256.meta.json +27 -0
  29. package/conversations/5549dc7a-d62e-49f6-93b6-977da7908a11.jsonl +1 -0
  30. package/conversations/5549dc7a-d62e-49f6-93b6-977da7908a11.meta.json +27 -0
  31. package/conversations/610ab34a-c7f4-4d24-98b1-75e3f98835d3.jsonl +1 -0
  32. package/conversations/610ab34a-c7f4-4d24-98b1-75e3f98835d3.meta.json +31 -0
  33. package/conversations/611610be-28f2-4b31-9afc-b5e278334d8a.jsonl +1 -0
  34. package/conversations/611610be-28f2-4b31-9afc-b5e278334d8a.meta.json +27 -0
  35. package/conversations/69ed7660-73bc-4994-9070-db4b264ccd18.jsonl +1 -0
  36. package/conversations/69ed7660-73bc-4994-9070-db4b264ccd18.meta.json +27 -0
  37. package/conversations/85eb0bf1-8a1a-4e9b-8b98-963f6b274913.jsonl +1 -0
  38. package/conversations/85eb0bf1-8a1a-4e9b-8b98-963f6b274913.meta.json +27 -0
  39. package/conversations/8b1ee339-fcc5-4587-81cd-dc32ea69cfe0.jsonl +1 -0
  40. package/conversations/8b1ee339-fcc5-4587-81cd-dc32ea69cfe0.meta.json +31 -0
  41. package/conversations/9b628dc0-0a71-456b-9ba7-8ca2b0872c63.jsonl +1 -0
  42. package/conversations/9b628dc0-0a71-456b-9ba7-8ca2b0872c63.meta.json +27 -0
  43. package/conversations/ad3ef614-306b-414c-a5c5-ae0f2bd4e3d8.jsonl +1 -0
  44. package/conversations/ad3ef614-306b-414c-a5c5-ae0f2bd4e3d8.meta.json +27 -0
  45. package/conversations/b71f8bc4-3f34-4667-aad1-5ab899339fb0.jsonl +1 -0
  46. package/conversations/b71f8bc4-3f34-4667-aad1-5ab899339fb0.meta.json +27 -0
  47. package/conversations/daf8a65b-83eb-4f7e-8052-714457a8f6b0.jsonl +1 -0
  48. package/conversations/daf8a65b-83eb-4f7e-8052-714457a8f6b0.meta.json +27 -0
  49. package/conversations/f2728631-64b9-4267-a793-2d39e3ce8f5e.jsonl +1 -0
  50. package/conversations/f2728631-64b9-4267-a793-2d39e3ce8f5e.meta.json +27 -0
  51. package/conversations/test-conv-1771128087319.meta.json +27 -0
  52. package/conversations/test-conv-1771128515164.meta.json +27 -0
  53. package/conversations/test-conv-1771128546424.meta.json +27 -0
  54. package/conversations/test-conv-1771128606354.meta.json +27 -0
  55. package/conversations/test-group-1771128087322.meta.json +27 -0
  56. package/conversations/test-group-1771128515165.meta.json +27 -0
  57. package/conversations/test-group-1771128546425.meta.json +27 -0
  58. package/conversations/test-group-1771128606355.meta.json +27 -0
  59. package/demo-two-agents.js +395 -0
  60. package/integrations/express.js +96 -0
  61. package/integrations/openclaw.js +62 -0
  62. package/integrations/webhook.js +111 -0
  63. package/logs/ai2ai-2026-02-15.log +40 -0
  64. package/openclaw-integration.js +540 -0
  65. package/package.json +17 -24
  66. package/package.json.bak +24 -0
  67. package/pending/139dcb76-7778-4130-b448-c7828184a53f.json +28 -0
  68. package/pending/187a69f5-9391-41d0-87d6-34d479a6cc50.json +28 -0
  69. package/pending/2d07e1bb-51f8-4e13-b08b-f1b5b1dc3d1e.json +34 -0
  70. package/pending/2d13bdf4-a818-4629-bfdf-ac29b1a64ba5.json +28 -0
  71. package/pending/3029f00d-97a4-4928-9ff8-3500541c381d.json +31 -0
  72. package/pending/37a3fddb-73e1-4b85-8de5-2def875216bf.json +34 -0
  73. package/pending/4babfd35-aba7-479f-bc0f-f0c83e31d3db.json +34 -0
  74. package/pending/602c0022-993a-4b8a-9ba9-04e56ec59bb5.json +34 -0
  75. package/pending/af925c5f-bed5-4a46-83c3-d16c97d47627.json +28 -0
  76. package/pending/ba1474fe-41b7-412e-b702-0b74307510b9.json +31 -0
  77. package/pending/bcf800f6-c5bb-44a9-8e39-195bd624ff92.json +31 -0
  78. package/pending/c6683665-1321-49ed-8d21-5ae4250848e8.json +31 -0
  79. package/registry.js +406 -0
  80. package/reliability.js +467 -0
  81. package/security.js +386 -0
  82. package/test-v1.js +540 -0
  83. package/test.js +705 -0
  84. package/README.md +0 -184
  85. package/bin/ai2ai.js +0 -87
  86. package/lib/approve.js +0 -137
  87. package/lib/config.js +0 -120
  88. package/lib/connect.js +0 -89
  89. package/lib/contacts.js +0 -62
  90. package/lib/init.js +0 -148
  91. package/lib/pending.js +0 -114
  92. package/lib/protocol.js +0 -161
  93. package/lib/send.js +0 -135
  94. package/lib/start.js +0 -318
  95. package/lib/status.js +0 -70
@@ -0,0 +1,3 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MC4CAQAwBQYDK2VwBCIEIM4w1HJb8pGX9saKzOWe2+F/gUPodQFlLynR7WBEIFih
3
+ -----END PRIVATE KEY-----
@@ -0,0 +1,3 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MCowBQYDK2VwAyEAEZLXvE1B1UM8koD+6lAO7tRlQjgag5lmbXghK+uy1es=
3
+ -----END PUBLIC KEY-----
Binary file
Binary file
package/SKILL.md ADDED
@@ -0,0 +1,39 @@
1
+ # AI2AI — Agent-to-Agent Communication Skill
2
+
3
+ ## Description
4
+ Enables your OpenClaw agent to communicate with other AI agents on behalf of your human. Implements the AI2AI protocol for decentralized agent negotiation.
5
+
6
+ ## Commands
7
+
8
+ The human can say:
9
+ - **"Talk to [name]'s AI at [address]"** — Initiate handshake with another agent
10
+ - **"Schedule [event] with [name]"** — Start a schedule negotiation
11
+ - **"Send [name] a message via their AI"** — Relay a message through agents
12
+ - **"Show AI2AI contacts"** — List known agents
13
+ - **"AI2AI status"** — Show connection status and recent conversations
14
+
15
+ ## How It Works
16
+
17
+ 1. Human tells their agent what they want
18
+ 2. Agent formats an AI2AI message and sends it to the other agent's endpoint
19
+ 3. Other agent receives it, interprets it, and asks their human
20
+ 4. Response comes back
21
+ 5. Your agent tells you the result
22
+
23
+ All communication is structured JSON. Humans approve everything (unless trust is elevated).
24
+
25
+ ## Files
26
+ - `ai2ai-server.js` — HTTP endpoint that receives incoming AI2AI messages
27
+ - `ai2ai-client.js` — Sends outgoing AI2AI messages
28
+ - `ai2ai-handlers.js` — Intent handlers (schedule, message, etc.)
29
+ - `ai2ai-trust.js` — Trust management and approval flow
30
+ - `ai2ai-crypto.js` — Ed25519 signing and verification
31
+ - `contacts.json` — Known agents and trust levels
32
+ - `conversations/` — Conversation history
33
+
34
+ ## Configuration
35
+ Set in your OpenClaw config or via environment:
36
+ - `AI2AI_PORT` — Port for incoming connections (default: 18800)
37
+ - `AI2AI_AGENT_NAME` — Your agent's display name
38
+ - `AI2AI_HUMAN_NAME` — Your human's display name
39
+ - `AI2AI_TIMEZONE` — Your timezone
@@ -0,0 +1,351 @@
1
+ /**
2
+ * AI2AI Client — Send outgoing messages to other agents
3
+ * Supports encryption, queuing, multi-recipient, and logging.
4
+ */
5
+
6
+ const crypto = require('crypto');
7
+ const { loadOrCreateKeys, signMessage } = require('./ai2ai-crypto');
8
+ const { upsertContact, isBlocked, getContact } = require('./ai2ai-trust');
9
+ const { encryptPayloadX25519, loadOrCreateX25519Keys, isEncrypted } = require('./ai2ai-encryption');
10
+ const { queueAndSend } = require('./ai2ai-queue');
11
+ const { createConversation, transitionState, STATES } = require('./ai2ai-conversations');
12
+ const logger = require('./ai2ai-logger');
13
+
14
+ // Default config (override via environment or config file)
15
+ const CONFIG = {
16
+ agentName: process.env.AI2AI_AGENT_NAME || 'my-assistant',
17
+ humanName: process.env.AI2AI_HUMAN_NAME || 'Human',
18
+ timezone: process.env.AI2AI_TIMEZONE || 'UTC',
19
+ enableEncryption: process.env.AI2AI_ENCRYPTION !== 'false', // default on
20
+ enableQueue: process.env.AI2AI_QUEUE !== 'false', // default on
21
+ };
22
+
23
+ /**
24
+ * Create a new AI2AI message envelope
25
+ * Supports multiple recipients via `to` array.
26
+ */
27
+ function createEnvelope({ to, type, intent, payload, conversationId, requiresHumanApproval, participants }) {
28
+ const keys = loadOrCreateKeys();
29
+
30
+ // Normalize `to` — support single object or array
31
+ const toField = Array.isArray(to) ? to : to;
32
+
33
+ const envelope = {
34
+ ai2ai: '0.1',
35
+ id: crypto.randomUUID(),
36
+ timestamp: new Date().toISOString(),
37
+ from: {
38
+ agent: CONFIG.agentName,
39
+ node: `${CONFIG.agentName}-node`,
40
+ human: CONFIG.humanName,
41
+ },
42
+ to: toField,
43
+ conversation: conversationId || crypto.randomUUID(),
44
+ type,
45
+ intent: intent || null,
46
+ payload: payload || {},
47
+ requires_human_approval: requiresHumanApproval !== false,
48
+ };
49
+
50
+ // Add participants for group conversations
51
+ if (participants) {
52
+ envelope.participants = participants;
53
+ }
54
+
55
+ // Sign the message
56
+ envelope.signature = signMessage(envelope, keys.privateKey);
57
+
58
+ return envelope;
59
+ }
60
+
61
+ /**
62
+ * Optionally encrypt the payload field of an envelope
63
+ * Returns the envelope (mutated with encrypted payload, or unchanged)
64
+ */
65
+ function maybeEncryptEnvelope(envelope) {
66
+ if (!CONFIG.enableEncryption) return envelope;
67
+
68
+ // Get recipient's X25519 public key from contacts
69
+ const recipientAgent = Array.isArray(envelope.to) ? envelope.to[0]?.agent : envelope.to?.agent;
70
+ if (!recipientAgent) return envelope;
71
+
72
+ const contact = getContact(recipientAgent);
73
+ if (!contact?.x25519PublicKey) return envelope; // No encryption key → send signed-only
74
+
75
+ const encrypted = encryptPayloadX25519(envelope.payload, contact.x25519PublicKey);
76
+ if (encrypted) {
77
+ envelope.payload = encrypted;
78
+ envelope._payloadEncrypted = true;
79
+ }
80
+
81
+ return envelope;
82
+ }
83
+
84
+ /**
85
+ * Raw send function (used by queue system too)
86
+ */
87
+ async function rawSend(endpoint, envelope) {
88
+ const response = await fetch(endpoint, {
89
+ method: 'POST',
90
+ headers: {
91
+ 'Content-Type': 'application/json',
92
+ 'X-AI2AI-Version': '0.1',
93
+ },
94
+ body: JSON.stringify(envelope),
95
+ signal: AbortSignal.timeout(30000),
96
+ });
97
+
98
+ if (!response.ok) {
99
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
100
+ }
101
+
102
+ return response.json();
103
+ }
104
+
105
+ /**
106
+ * Send a message to another agent's endpoint
107
+ * With optional queuing for offline agents.
108
+ */
109
+ async function sendMessage(endpoint, envelope, options = {}) {
110
+ const recipientAgent = Array.isArray(envelope.to) ? envelope.to[0]?.agent : envelope.to?.agent;
111
+
112
+ // Check if agent is blocked
113
+ if (recipientAgent && isBlocked(recipientAgent)) {
114
+ throw new Error(`Agent ${recipientAgent} is blocked`);
115
+ }
116
+
117
+ // Optionally encrypt
118
+ maybeEncryptEnvelope(envelope);
119
+
120
+ // Log outgoing
121
+ logger.logOutgoing(envelope);
122
+
123
+ try {
124
+ const data = await rawSend(endpoint, envelope);
125
+
126
+ // Update contact with last interaction
127
+ if (recipientAgent) {
128
+ upsertContact(recipientAgent, {
129
+ endpoint,
130
+ humanName: Array.isArray(envelope.to) ? envelope.to[0]?.human : envelope.to?.human,
131
+ lastInteraction: new Date().toISOString(),
132
+ });
133
+ }
134
+
135
+ return data;
136
+ } catch (err) {
137
+ // If queue is enabled, queue the message for retry
138
+ if (CONFIG.enableQueue && options.queue !== false) {
139
+ logger.warn('CLIENT', `Direct send failed, queuing: ${err.message}`, { endpoint });
140
+
141
+ const result = await queueAndSend(endpoint, envelope, rawSend, {
142
+ onFailure: options.onDeliveryFailure || ((entry) => {
143
+ logger.error('CLIENT', `All retries failed for message to ${recipientAgent}`, {
144
+ queueId: entry.id,
145
+ });
146
+ }),
147
+ });
148
+
149
+ return { status: 'queued', queueId: result.queueId, message: 'Agent offline, message queued for retry' };
150
+ }
151
+
152
+ throw new Error(`Failed to reach ${endpoint}: ${err.message}`);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Send to multiple recipients (group conversations)
158
+ */
159
+ async function sendToMultiple(endpoints, envelope, options = {}) {
160
+ const results = {};
161
+ for (const { agent, endpoint } of endpoints) {
162
+ try {
163
+ // Create per-recipient envelope (same conversation, different `to`)
164
+ const recipientEnvelope = { ...envelope, to: { agent, human: agent, node: 'unknown' } };
165
+ recipientEnvelope.signature = signMessage(recipientEnvelope, loadOrCreateKeys().privateKey);
166
+ results[agent] = await sendMessage(endpoint, recipientEnvelope, options);
167
+ } catch (err) {
168
+ results[agent] = { status: 'error', error: err.message };
169
+ }
170
+ }
171
+ return results;
172
+ }
173
+
174
+ /**
175
+ * Send a ping/handshake to discover another agent
176
+ */
177
+ async function ping(endpoint) {
178
+ const keys = loadOrCreateKeys();
179
+ const { getFingerprint } = require('./ai2ai-crypto');
180
+ const { supportedIntents } = require('./ai2ai-handlers');
181
+ const x25519Keys = loadOrCreateX25519Keys();
182
+
183
+ const envelope = createEnvelope({
184
+ to: { agent: 'unknown', node: 'unknown', human: 'unknown' },
185
+ type: 'ping',
186
+ payload: {
187
+ capabilities: supportedIntents(),
188
+ languages: ['en'],
189
+ timezone: CONFIG.timezone,
190
+ availability_hours: '09:00-22:00',
191
+ model_info: 'local',
192
+ protocol_versions: ['0.1'],
193
+ public_key: keys.publicKey,
194
+ fingerprint: getFingerprint(keys.publicKey),
195
+ x25519_public_key: x25519Keys.publicKeyDer,
196
+ },
197
+ });
198
+
199
+ return sendMessage(endpoint, envelope, { queue: false }); // Don't queue pings
200
+ }
201
+
202
+ /**
203
+ * Request a meeting with another agent
204
+ */
205
+ async function requestMeeting(endpoint, { subject, proposedTimes, durationMinutes, location, notes, flexibility, to }) {
206
+ const conversationId = crypto.randomUUID();
207
+
208
+ const envelope = createEnvelope({
209
+ to: to || { agent: 'unknown' },
210
+ type: 'request',
211
+ intent: 'schedule.meeting',
212
+ conversationId,
213
+ payload: {
214
+ subject,
215
+ proposed_times: proposedTimes,
216
+ duration_minutes: durationMinutes || 60,
217
+ location_preference: location || null,
218
+ notes: notes || null,
219
+ flexibility: flexibility || 'medium',
220
+ },
221
+ });
222
+
223
+ // Track conversation
224
+ createConversation(conversationId, {
225
+ intent: 'schedule.meeting',
226
+ initiator: envelope.from,
227
+ recipient: envelope.to,
228
+ });
229
+
230
+ return sendMessage(endpoint, envelope);
231
+ }
232
+
233
+ /**
234
+ * Relay a message to another agent's human
235
+ */
236
+ async function relayMessage(endpoint, { message, urgency, replyRequested, to }) {
237
+ const envelope = createEnvelope({
238
+ to: to || { agent: 'unknown' },
239
+ type: 'request',
240
+ intent: 'message.relay',
241
+ payload: {
242
+ message,
243
+ urgency: urgency || 'low',
244
+ reply_requested: replyRequested !== false,
245
+ },
246
+ });
247
+
248
+ return sendMessage(endpoint, envelope);
249
+ }
250
+
251
+ /**
252
+ * Request information from another agent
253
+ */
254
+ async function requestInfo(endpoint, { question, context, to }) {
255
+ const envelope = createEnvelope({
256
+ to: to || { agent: 'unknown' },
257
+ type: 'request',
258
+ intent: 'info.request',
259
+ payload: {
260
+ question,
261
+ context: context || null,
262
+ },
263
+ });
264
+
265
+ return sendMessage(endpoint, envelope);
266
+ }
267
+
268
+ /**
269
+ * Request a commerce quote
270
+ */
271
+ async function requestQuote(endpoint, { item, description, quantity, budget, currency, notes, to }) {
272
+ const conversationId = crypto.randomUUID();
273
+
274
+ const envelope = createEnvelope({
275
+ to: to || { agent: 'unknown' },
276
+ type: 'request',
277
+ intent: 'commerce.request',
278
+ conversationId,
279
+ requiresHumanApproval: true,
280
+ payload: {
281
+ item,
282
+ description: description || null,
283
+ quantity: quantity || null,
284
+ budget: budget || null,
285
+ currency: currency || 'USD',
286
+ notes: notes || null,
287
+ },
288
+ });
289
+
290
+ createConversation(conversationId, {
291
+ intent: 'commerce.request',
292
+ initiator: envelope.from,
293
+ recipient: envelope.to,
294
+ });
295
+
296
+ return sendMessage(endpoint, envelope);
297
+ }
298
+
299
+ /**
300
+ * Initiate a group scheduling conversation
301
+ */
302
+ async function requestGroupMeeting(endpoints, { subject, proposedTimes, durationMinutes, location, notes, participants }) {
303
+ const conversationId = crypto.randomUUID();
304
+
305
+ const participantList = participants || endpoints.map(e => ({
306
+ agent: e.agent,
307
+ human: e.human || e.agent,
308
+ }));
309
+
310
+ // Add self
311
+ participantList.unshift({ agent: CONFIG.agentName, human: CONFIG.humanName });
312
+
313
+ const envelope = createEnvelope({
314
+ to: participantList.slice(1), // All recipients
315
+ type: 'request',
316
+ intent: 'schedule.group',
317
+ conversationId,
318
+ participants: participantList,
319
+ payload: {
320
+ subject,
321
+ proposed_times: proposedTimes,
322
+ duration_minutes: durationMinutes || 60,
323
+ location_preference: location || null,
324
+ notes: notes || null,
325
+ participants: participantList,
326
+ },
327
+ });
328
+
329
+ createConversation(conversationId, {
330
+ intent: 'schedule.group',
331
+ initiator: envelope.from,
332
+ participants: participantList,
333
+ });
334
+
335
+ return sendToMultiple(endpoints, envelope);
336
+ }
337
+
338
+ module.exports = {
339
+ CONFIG,
340
+ createEnvelope,
341
+ sendMessage,
342
+ sendToMultiple,
343
+ rawSend,
344
+ maybeEncryptEnvelope,
345
+ ping,
346
+ requestMeeting,
347
+ relayMessage,
348
+ requestInfo,
349
+ requestQuote,
350
+ requestGroupMeeting,
351
+ };