a2acalling 0.1.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.
@@ -0,0 +1,241 @@
1
+ # Federation Protocol v0 Reference
2
+
3
+ ## Overview
4
+
5
+ Federation enables OpenClaw agents to call each other across instances with scoped permissions and owner notification.
6
+
7
+ ## Token Format
8
+
9
+ ```
10
+ a2a://<hostname>/<token>
11
+ ```
12
+
13
+ Example: `a2a://my-server.example.com/fed_abc123xyz`
14
+
15
+ Token structure: `fed_<base64url(24 random bytes)>`
16
+
17
+ ## API Endpoints
18
+
19
+ All endpoints are prefixed with `/api/federation/`
20
+
21
+ ### GET /status
22
+
23
+ Check if federation is enabled.
24
+
25
+ Response:
26
+ ```json
27
+ {
28
+ "federation": true,
29
+ "version": "0.1.0",
30
+ "capabilities": ["invoke", "multi-turn"],
31
+ "rate_limits": {
32
+ "per_minute": 10,
33
+ "per_hour": 100,
34
+ "per_day": 1000
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### GET /ping
40
+
41
+ Health check endpoint.
42
+
43
+ Response:
44
+ ```json
45
+ {
46
+ "pong": true,
47
+ "timestamp": "2026-02-11T17:54:00Z"
48
+ }
49
+ ```
50
+
51
+ ### POST /invoke
52
+
53
+ Call the agent.
54
+
55
+ Headers:
56
+ ```
57
+ Authorization: Bearer fed_abc123xyz
58
+ Content-Type: application/json
59
+ ```
60
+
61
+ Request body:
62
+ ```json
63
+ {
64
+ "message": "Your question or request",
65
+ "conversation_id": "optional-for-multi-turn",
66
+ "caller": {
67
+ "name": "Alice's Agent",
68
+ "instance": "alice.example.com",
69
+ "context": "Why I'm calling"
70
+ },
71
+ "timeout_seconds": 60
72
+ }
73
+ ```
74
+
75
+ Success response:
76
+ ```json
77
+ {
78
+ "success": true,
79
+ "conversation_id": "conv_123456",
80
+ "response": "The agent's response text",
81
+ "can_continue": true,
82
+ "tokens_remaining": 47
83
+ }
84
+ ```
85
+
86
+ Error responses:
87
+ ```json
88
+ {"success": false, "error": "token_expired", "message": "..."}
89
+ {"success": false, "error": "token_revoked", "message": "..."}
90
+ {"success": false, "error": "permission_denied", "message": "..."}
91
+ {"success": false, "error": "rate_limited", "message": "..."}
92
+ {"success": false, "error": "missing_token", "message": "..."}
93
+ {"success": false, "error": "missing_message", "message": "..."}
94
+ ```
95
+
96
+ ## Permission Scopes
97
+
98
+ | Scope | Tools | Files | Memory | Actions |
99
+ |-------|-------|-------|--------|---------|
100
+ | `chat-only` | ❌ | ❌ | ❌ | ❌ |
101
+ | `tools-read` | Read | Read | ❌ | ❌ |
102
+ | `tools-write` | All | All | ❌ | Notify owner |
103
+
104
+ ## Disclosure Levels
105
+
106
+ | Level | Behavior |
107
+ |-------|----------|
108
+ | `public` | Agent may share any non-private information |
109
+ | `minimal` | Agent gives direct answers only, no context about owner |
110
+ | `none` | Agent confirms capability only, provides no actual information |
111
+
112
+ ## Token Storage Schema
113
+
114
+ Stored in `~/.config/openclaw/federation.json`:
115
+
116
+ ```json
117
+ {
118
+ "tokens": [
119
+ {
120
+ "id": "fed_abc123xyz789",
121
+ "token_hash": "sha256...",
122
+ "name": "Alice's agent",
123
+ "permissions": "chat-only",
124
+ "disclosure": "minimal",
125
+ "notify": "all",
126
+ "max_calls": null,
127
+ "calls_made": 5,
128
+ "created_at": "2026-02-11T17:54:00Z",
129
+ "expires_at": "2026-02-18T17:54:00Z",
130
+ "last_used": "2026-02-12T10:30:00Z",
131
+ "revoked": false
132
+ }
133
+ ],
134
+ "remotes": [
135
+ {
136
+ "id": "remote_xyz",
137
+ "name": "Bob's agent",
138
+ "host": "bob.example.com",
139
+ "token": "fed_bobtoken123",
140
+ "added_at": "2026-02-11T18:00:00Z"
141
+ }
142
+ ],
143
+ "calls": []
144
+ }
145
+ ```
146
+
147
+ ## Rate Limits
148
+
149
+ Default per-token limits:
150
+ - 10 requests per minute
151
+ - 100 requests per hour
152
+ - 1000 requests per day
153
+
154
+ Limits reset on natural boundaries (minute, hour, day UTC).
155
+
156
+ ## Security Considerations
157
+
158
+ 1. **Token hashing**: Tokens stored as SHA-256 hashes server-side
159
+ 2. **TLS required**: All federation calls should use HTTPS
160
+ 3. **No credential forwarding**: Tokens are never forwarded to other agents
161
+ 4. **Audit logging**: All invocations are logged with caller info
162
+ 5. **Auto-revocation**: Tokens may auto-revoke after repeated errors
163
+
164
+ ## Multi-turn Conversations
165
+
166
+ To continue a conversation, include `conversation_id` from the previous response:
167
+
168
+ ```json
169
+ {
170
+ "message": "Follow-up question",
171
+ "conversation_id": "conv_123456"
172
+ }
173
+ ```
174
+
175
+ Conversations expire after 1 hour of inactivity.
176
+
177
+ ## Owner Notifications
178
+
179
+ When `notify: all`:
180
+ ```
181
+ 🤝 Federation call received
182
+
183
+ From: Alice's Agent (alice.example.com)
184
+ Token: "Work collab" (expires 2026-02-18)
185
+
186
+ ---
187
+ Alice's Agent: Does Ben have time this week?
188
+ You: Ben is available Thursday 2-4pm.
189
+ ---
190
+
191
+ 📊 5 of unlimited calls | Token expires in 6d
192
+ ```
193
+
194
+ Owner can reply to inject into the conversation.
195
+
196
+ ## OpenClaw Integration
197
+
198
+ ### Gateway Route Registration
199
+
200
+ Add to gateway routes:
201
+ ```javascript
202
+ const federation = require('./skills/federation/scripts/server');
203
+ app.use('/api/federation', federation);
204
+ ```
205
+
206
+ ### Agent Context
207
+
208
+ When handling a federation call, inject context:
209
+ ```json
210
+ {
211
+ "federation": {
212
+ "active": true,
213
+ "caller": "Alice's Agent",
214
+ "permissions": "chat-only",
215
+ "disclosure": "minimal"
216
+ }
217
+ }
218
+ ```
219
+
220
+ ### New Tool: federation_call
221
+
222
+ ```typescript
223
+ federation_call({
224
+ endpoint: string, // a2a:// URL
225
+ message: string, // Message to send
226
+ conversation_id?: string // For multi-turn
227
+ }): {
228
+ success: boolean,
229
+ response?: string,
230
+ conversation_id?: string,
231
+ error?: string
232
+ }
233
+ ```
234
+
235
+ ## Future Protocol Extensions (v1+)
236
+
237
+ - **Capability advertisement**: Agents declare what they can help with
238
+ - **Cryptographic identity**: Ed25519 signatures for caller verification
239
+ - **Streaming responses**: SSE for long-running operations
240
+ - **Webhooks**: Push notifications instead of polling
241
+ - **Payments**: Token-gated access with usage billing
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "a2acalling",
3
+ "version": "0.1.0",
4
+ "description": "Agent-to-agent calling for OpenClaw - federated agent communication",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "a2a": "bin/cli.js",
8
+ "a2acalling": "scripts/install-openclaw.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/server.js",
12
+ "test": "node test/run.js"
13
+ },
14
+ "keywords": [
15
+ "openclaw",
16
+ "claudebot",
17
+ "agent",
18
+ "federation",
19
+ "a2a",
20
+ "ai"
21
+ ],
22
+ "author": "OpenClaw Contributors",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/onthegonow/A2A_for_OpenClaw.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/onthegonow/A2A_for_OpenClaw/issues"
30
+ },
31
+ "homepage": "https://github.com/onthegonow/A2A_for_OpenClaw#readme",
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "peerDependencies": {
36
+ "express": "^4.18.0"
37
+ },
38
+ "optionalDependencies": {
39
+ "better-sqlite3": "^11.10.0"
40
+ },
41
+ "devDependencies": {
42
+ "express": "^5.2.1"
43
+ }
44
+ }
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * A2A Calling - OpenClaw Integration Installer
4
+ *
5
+ * This script:
6
+ * 1. Installs the a2a skill to the user's OpenClaw skills directory
7
+ * 2. Adds /a2a as a custom command in OpenClaw config
8
+ * 3. Sets up the federation server as a systemd service (optional)
9
+ *
10
+ * Usage:
11
+ * npx a2acalling install
12
+ * npx a2acalling install --hostname myserver.com
13
+ * npx a2acalling install --port 3001
14
+ * npx a2acalling uninstall
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { execSync } = require('child_process');
20
+
21
+ // Paths
22
+ const OPENCLAW_CONFIG = process.env.OPENCLAW_CONFIG || path.join(process.env.HOME, '.openclaw', 'openclaw.json');
23
+ const OPENCLAW_SKILLS = process.env.OPENCLAW_SKILLS || path.join(process.env.HOME, '.openclaw', 'skills');
24
+ const SKILL_NAME = 'a2a';
25
+
26
+ // Parse args
27
+ const args = process.argv.slice(2);
28
+ const command = args[0];
29
+ const flags = {};
30
+ for (let i = 1; i < args.length; i++) {
31
+ if (args[i].startsWith('--')) {
32
+ const key = args[i].slice(2);
33
+ flags[key] = args[i + 1] && !args[i + 1].startsWith('--') ? args[++i] : true;
34
+ }
35
+ }
36
+
37
+ // Colors
38
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
39
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
40
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
41
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
42
+
43
+ function log(msg) { console.log(`${green('[a2a]')} ${msg}`); }
44
+ function warn(msg) { console.log(`${yellow('[a2a]')} ${msg}`); }
45
+ function error(msg) { console.error(`${red('[a2a]')} ${msg}`); }
46
+
47
+ // Skill content
48
+ const SKILL_MD = `---
49
+ name: a2a
50
+ description: "Agent-to-Agent federation. Handle /a2a commands to create tokens, manage connections, and call remote agents. Triggers on: /a2a, federation, agent token, a2a invite."
51
+ ---
52
+
53
+ # A2A Federation
54
+
55
+ Handle agent-to-agent communication with Telegram inline buttons + \`a2a\` CLI.
56
+
57
+ ## CRITICAL: Forum Topic Threading
58
+
59
+ When sending messages with buttons in Telegram forum groups, **ALWAYS include threadId**:
60
+
61
+ 1. Extract topic ID from message header (e.g., \`topic:567\`)
62
+ 2. Include \`threadId: "TOPIC_ID"\` in ALL message tool calls
63
+
64
+ ## Onboarding (First Run)
65
+
66
+ **BEFORE showing tiers, ALWAYS read and analyze user context:**
67
+ - HEARTBEAT.md - current tasks/interests
68
+ - USER.md - professional context, shareable bio
69
+ - SOUL.md - agent personality
70
+ - memory/*.md - stored context
71
+
72
+ **Extract:** Topics of interest, goals, professional context (job seeking?), sensitive areas.
73
+
74
+ **Personalize tiers based on findings** - not generic examples!
75
+
76
+ **Step 1:** Show analyzed topics grouped into Public/Friends/Private tiers
77
+ **Step 2:** Confirm default settings (expiration, rate limits)
78
+ **Step 3:** Confirm agent identity
79
+ **Step 4:** Complete - save config, show next steps
80
+
81
+ Settings saved to ~/.config/openclaw/a2a-config.json
82
+
83
+ ## Main Menu (Post-Onboarding)
84
+
85
+ \`\`\`javascript
86
+ message({
87
+ action: "send",
88
+ channel: "telegram",
89
+ target: "CHAT_ID",
90
+ threadId: "TOPIC_ID", // REQUIRED for forum topics!
91
+ message: "🤝 **A2A Federation**\\n\\nWhat would you like to do?",
92
+ buttons: [
93
+ [{ text: "📝 Create Invite", callback_data: "/a2a invite" }, { text: "📋 List Tokens", callback_data: "/a2a list" }],
94
+ [{ text: "🗑 Revoke Token", callback_data: "/a2a revoke" }, { text: "📡 Add Remote", callback_data: "/a2a add" }]
95
+ ]
96
+ })
97
+ \`\`\`
98
+
99
+ ## Commands
100
+
101
+ ### /a2a invite
102
+ \`\`\`bash
103
+ a2a create --name "NAME" --expires "DURATION"
104
+ \`\`\`
105
+ Reply with full shareable invite block.
106
+
107
+ ### /a2a list
108
+ \`\`\`bash
109
+ a2a list
110
+ \`\`\`
111
+
112
+ ### /a2a revoke <id>
113
+ \`\`\`bash
114
+ a2a revoke TOKEN_ID
115
+ \`\`\`
116
+
117
+ ### /a2a add <url> [name]
118
+ \`\`\`bash
119
+ a2a add "URL" "NAME"
120
+ \`\`\`
121
+
122
+ ### /a2a call <url> <msg>
123
+ \`\`\`bash
124
+ a2a call "URL" "MESSAGE"
125
+ \`\`\`
126
+
127
+ ## Server
128
+
129
+ \`\`\`bash
130
+ a2a server --port 3001
131
+ \`\`\`
132
+
133
+ ## Defaults
134
+ - Expiration: 1 day
135
+ - Max calls: 100
136
+ - Rate limit: 10/min
137
+ `;
138
+
139
+ function install() {
140
+ log('Installing A2A Calling for OpenClaw...\n');
141
+
142
+ // 1. Create skills directory if needed
143
+ if (!fs.existsSync(OPENCLAW_SKILLS)) {
144
+ fs.mkdirSync(OPENCLAW_SKILLS, { recursive: true });
145
+ log(`Created skills directory: ${OPENCLAW_SKILLS}`);
146
+ }
147
+
148
+ // 2. Install skill
149
+ const skillDir = path.join(OPENCLAW_SKILLS, SKILL_NAME);
150
+ if (!fs.existsSync(skillDir)) {
151
+ fs.mkdirSync(skillDir, { recursive: true });
152
+ }
153
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), SKILL_MD);
154
+ log(`Installed skill to: ${skillDir}`);
155
+
156
+ // 3. Update OpenClaw config
157
+ if (fs.existsSync(OPENCLAW_CONFIG)) {
158
+ try {
159
+ const config = JSON.parse(fs.readFileSync(OPENCLAW_CONFIG, 'utf8'));
160
+
161
+ // Add custom command for each channel that supports it
162
+ const channels = ['telegram', 'discord', 'slack'];
163
+ let updated = false;
164
+
165
+ for (const channel of channels) {
166
+ if (config.channels?.[channel]?.enabled) {
167
+ if (!config.channels[channel].customCommands) {
168
+ config.channels[channel].customCommands = [];
169
+ }
170
+
171
+ const existing = config.channels[channel].customCommands.find(c => c.command === 'a2a');
172
+ if (!existing) {
173
+ config.channels[channel].customCommands.push({
174
+ command: 'a2a',
175
+ description: 'Agent-to-Agent: create invitations, manage connections'
176
+ });
177
+ updated = true;
178
+ log(`Added /a2a command to ${channel} config`);
179
+ } else {
180
+ log(`/a2a command already exists in ${channel} config`);
181
+ }
182
+ }
183
+ }
184
+
185
+ if (updated) {
186
+ // Backup original
187
+ const backupPath = `${OPENCLAW_CONFIG}.backup.${Date.now()}`;
188
+ fs.copyFileSync(OPENCLAW_CONFIG, backupPath);
189
+ log(`Backed up config to: ${backupPath}`);
190
+
191
+ // Write updated config
192
+ fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(config, null, 2));
193
+ log('Updated OpenClaw config');
194
+ warn('Restart OpenClaw gateway to apply changes: openclaw gateway restart');
195
+ }
196
+ } catch (e) {
197
+ warn(`Could not update OpenClaw config: ${e.message}`);
198
+ warn('You may need to manually add the /a2a custom command');
199
+ }
200
+ } else {
201
+ warn(`OpenClaw config not found at: ${OPENCLAW_CONFIG}`);
202
+ warn('You may need to manually add the /a2a custom command');
203
+ }
204
+
205
+ // 4. Show server setup instructions
206
+ const hostname = flags.hostname || process.env.HOSTNAME || 'localhost';
207
+ const port = flags.port || '3001';
208
+
209
+ console.log(`
210
+ ${bold('━━━ Server Setup ━━━')}
211
+
212
+ To receive incoming calls, run the a2a server:
213
+
214
+ ${green(`A2A_HOSTNAME="${hostname}:${port}" a2a server`)}
215
+
216
+ Or create a systemd service:
217
+
218
+ ${green('sudo a2a service install')}
219
+
220
+ ${bold('━━━ Usage ━━━')}
221
+
222
+ In your chat app, use:
223
+
224
+ /a2a invite Create an invitation token
225
+ /a2a list List active tokens
226
+ /a2a revoke <id> Revoke a token
227
+ /a2a add <url> Add a remote agent
228
+ /a2a call <url> <msg> Call a remote agent
229
+
230
+ ${bold('━━━ Done! ━━━')}
231
+
232
+ ${green('✅ A2A Calling installed successfully!')}
233
+ `);
234
+ }
235
+
236
+ function uninstall() {
237
+ log('Uninstalling A2A Calling...\n');
238
+
239
+ // Remove skill
240
+ const skillDir = path.join(OPENCLAW_SKILLS, SKILL_NAME);
241
+ if (fs.existsSync(skillDir)) {
242
+ fs.rmSync(skillDir, { recursive: true });
243
+ log(`Removed skill from: ${skillDir}`);
244
+ }
245
+
246
+ // Note about config
247
+ warn('Custom command in OpenClaw config was not removed.');
248
+ warn('You can manually remove it if desired.');
249
+
250
+ log('✅ Uninstall complete');
251
+ }
252
+
253
+ function showHelp() {
254
+ console.log(`
255
+ ${bold('A2A Calling - OpenClaw Integration')}
256
+
257
+ Usage:
258
+ npx a2acalling install [options] Install A2A for OpenClaw
259
+ npx a2acalling uninstall Remove A2A skill
260
+ npx a2acalling server Start federation server
261
+
262
+ Install Options:
263
+ --hostname <host> Hostname for invite URLs (default: system hostname)
264
+ --port <port> Server port (default: 3001)
265
+
266
+ Examples:
267
+ npx a2acalling install --hostname myserver.com --port 443
268
+ npx a2acalling server --port 3001
269
+ `);
270
+ }
271
+
272
+ // Main
273
+ switch (command) {
274
+ case 'install':
275
+ install();
276
+ break;
277
+ case 'uninstall':
278
+ uninstall();
279
+ break;
280
+ case 'help':
281
+ case '--help':
282
+ case '-h':
283
+ showHelp();
284
+ break;
285
+ default:
286
+ if (command) {
287
+ error(`Unknown command: ${command}`);
288
+ }
289
+ showHelp();
290
+ process.exit(command ? 1 : 0);
291
+ }
package/src/index.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * A2A Calling - Agent-to-Agent Communication for OpenClaw
3
+ *
4
+ * @example
5
+ * // Server side - mount routes
6
+ * const { createRoutes, TokenStore } = require('a2acalling');
7
+ * const tokenStore = new TokenStore();
8
+ * app.use('/api/federation', createRoutes({ tokenStore, handleMessage }));
9
+ *
10
+ * @example
11
+ * // Client side - call remote agent
12
+ * const { A2AClient } = require('a2acalling');
13
+ * const client = new A2AClient({ caller: { name: 'My Agent' } });
14
+ * const response = await client.call('a2a://host/token', 'Hello!');
15
+ */
16
+
17
+ const { TokenStore } = require('./lib/tokens');
18
+ const { A2AClient, A2AError } = require('./lib/client');
19
+ const { createRoutes } = require('./routes/federation');
20
+
21
+ // Lazy load optional dependencies
22
+ let ConversationStore = null;
23
+ let summarizers = null;
24
+ let CallMonitor = null;
25
+ let openclawIntegration = null;
26
+
27
+ try {
28
+ ConversationStore = require('./lib/conversations').ConversationStore;
29
+ summarizers = require('./lib/summarizer');
30
+ CallMonitor = require('./lib/call-monitor').CallMonitor;
31
+ openclawIntegration = require('./lib/openclaw-integration');
32
+ } catch (err) {
33
+ // Optional dependencies not installed
34
+ }
35
+
36
+ module.exports = {
37
+ // Token management
38
+ TokenStore,
39
+
40
+ // Client for outbound calls
41
+ A2AClient,
42
+ A2AError,
43
+
44
+ // Express routes for inbound calls
45
+ createRoutes,
46
+
47
+ // Conversation storage (requires better-sqlite3)
48
+ ConversationStore,
49
+
50
+ // Call monitoring for auto-conclude
51
+ CallMonitor,
52
+
53
+ // Summarizers for conversation conclusion
54
+ ...(summarizers || {}),
55
+
56
+ // OpenClaw integration helpers
57
+ ...(openclawIntegration || {}),
58
+
59
+ // Version
60
+ version: require('../package.json').version
61
+ };