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.
- package/AGENTS.md +66 -0
- package/CLAUDE.md +52 -0
- package/README.md +307 -0
- package/SKILL.md +122 -0
- package/bin/cli.js +908 -0
- package/docs/protocol.md +241 -0
- package/package.json +44 -0
- package/scripts/install-openclaw.js +291 -0
- package/src/index.js +61 -0
- package/src/lib/call-monitor.js +143 -0
- package/src/lib/client.js +208 -0
- package/src/lib/config.js +173 -0
- package/src/lib/conversations.js +470 -0
- package/src/lib/openclaw-integration.js +329 -0
- package/src/lib/summarizer.js +137 -0
- package/src/lib/tokens.js +448 -0
- package/src/routes/federation.js +463 -0
- package/src/server.js +56 -0
package/docs/protocol.md
ADDED
|
@@ -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
|
+
};
|