a2acalling 0.1.8 → 0.1.9
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/README.md +19 -5
- package/bin/cli.js +8 -2
- package/package.json +4 -4
- package/scripts/install-openclaw.js +327 -74
- package/src/dashboard/public/app.js +341 -0
- package/src/dashboard/public/index.html +155 -0
- package/src/dashboard/public/style.css +166 -0
- package/src/index.js +5 -0
- package/src/lib/config.js +8 -0
- package/src/lib/openclaw-integration.js +19 -9
- package/src/lib/prompt-template.js +234 -1
- package/src/lib/tokens.js +14 -1
- package/src/routes/a2a.js +1 -0
- package/src/routes/dashboard.js +571 -0
- package/src/server.js +417 -5
- package/AGENTS.md +0 -66
- package/CLAUDE.md +0 -52
- package/SKILL.md +0 -169
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ Your AI agent can now call other AI agents — across instances, with scoped per
|
|
|
18
18
|
- ⏱️ **Flexible tokens** — expiring or permanent, call limits optional
|
|
19
19
|
- 🚦 **Rate limiting** — 10/min, 100/hr, 1000/day built-in
|
|
20
20
|
- 🔄 **Multi-turn conversations** — continue threads across calls
|
|
21
|
+
- 🧭 **Adaptive collaboration mode** — dynamic phase changes based on overlap and depth
|
|
22
|
+
- 🗂️ **Minimal dashboard** — contacts, calls, tier settings, and invite generation
|
|
21
23
|
- 💾 **Conversation history** — SQLite storage with context retrieval
|
|
22
24
|
|
|
23
25
|
## 🚀 Quick Start
|
|
@@ -59,16 +61,21 @@ npm install a2acalling
|
|
|
59
61
|
### For OpenClaw Users
|
|
60
62
|
|
|
61
63
|
```bash
|
|
62
|
-
#
|
|
63
|
-
npx a2acalling
|
|
64
|
+
# Auto setup (detects gateway and configures dashboard location)
|
|
65
|
+
npx a2acalling setup
|
|
64
66
|
|
|
65
67
|
# Or clone and install
|
|
66
|
-
git clone https://github.com/onthegonow/
|
|
67
|
-
cd
|
|
68
|
+
git clone https://github.com/onthegonow/a2a_calling.git
|
|
69
|
+
cd a2a_calling
|
|
68
70
|
npm install
|
|
69
|
-
node scripts/install-openclaw.js
|
|
71
|
+
node scripts/install-openclaw.js setup
|
|
70
72
|
```
|
|
71
73
|
|
|
74
|
+
Setup behavior:
|
|
75
|
+
- If OpenClaw gateway is detected, dashboard is exposed on gateway at `/a2a` (proxied to A2A backend).
|
|
76
|
+
- If gateway is not detected, dashboard is served directly by A2A server at `/dashboard`.
|
|
77
|
+
- Setup prints the exact dashboard URL at the end.
|
|
78
|
+
|
|
72
79
|
Before the first `a2a call`, the owner must set permissions and disclosure tiers. Run onboarding first:
|
|
73
80
|
|
|
74
81
|
```bash
|
|
@@ -179,8 +186,13 @@ a2a ping <target> # Check if agent is available
|
|
|
179
186
|
```bash
|
|
180
187
|
a2a server [options] # Start A2A server
|
|
181
188
|
--port, -p <port> # Port (default: 3001)
|
|
189
|
+
a2a setup # Auto setup via installer (gateway-aware dashboard)
|
|
182
190
|
```
|
|
183
191
|
|
|
192
|
+
Dashboard paths:
|
|
193
|
+
- Standalone A2A server: `http://<host>:<port>/dashboard`
|
|
194
|
+
- OpenClaw gateway mode: `http://<gateway>/a2a`
|
|
195
|
+
|
|
184
196
|
## 📡 Protocol
|
|
185
197
|
|
|
186
198
|
Tokens use the `a2a://` URI scheme:
|
|
@@ -313,6 +325,8 @@ app.listen(3001);
|
|
|
313
325
|
| `A2A_HOSTNAME` | Hostname for invite URLs (required for creates) |
|
|
314
326
|
| `A2A_PORT` | Server port (default: 3001) |
|
|
315
327
|
| `A2A_CONFIG_DIR` | Config directory (default: `~/.config/openclaw`) |
|
|
328
|
+
| `A2A_COLLAB_MODE` | Conversation style: `adaptive` (default) or `deep_dive` |
|
|
329
|
+
| `A2A_ADMIN_TOKEN` | Protect dashboard/conversation admin routes for non-local access |
|
|
316
330
|
|
|
317
331
|
## 🤝 Philosophy
|
|
318
332
|
|
package/bin/cli.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* a2a remotes List remote agents
|
|
11
11
|
* a2a call <url> <msg> Call a remote agent
|
|
12
12
|
* a2a ping <url> Ping a remote agent
|
|
13
|
+
* a2a setup Auto setup (gateway-aware dashboard install)
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
const { TokenStore } = require('../src/lib/tokens');
|
|
@@ -158,7 +159,7 @@ npm install -g a2acalling
|
|
|
158
159
|
a2a add "${inviteUrl}" "${agentName}"
|
|
159
160
|
a2a call "${agentName}" "Hello!"
|
|
160
161
|
|
|
161
|
-
📚 https://github.com/onthegonow/
|
|
162
|
+
📚 https://github.com/onthegonow/a2a_calling`;
|
|
162
163
|
|
|
163
164
|
console.log(invite);
|
|
164
165
|
console.log(`\n${'─'.repeat(50)}`);
|
|
@@ -804,7 +805,7 @@ ${inviteUrl}
|
|
|
804
805
|
|
|
805
806
|
3. Call: a2a call "${inviteUrl}" "Hello!"
|
|
806
807
|
|
|
807
|
-
📚 Docs: https://github.com/onthegonow/
|
|
808
|
+
📚 Docs: https://github.com/onthegonow/a2a_calling
|
|
808
809
|
`);
|
|
809
810
|
console.log('─'.repeat(50));
|
|
810
811
|
console.log(`\n✅ Done! Share the invite above with other agents.\n`);
|
|
@@ -816,6 +817,10 @@ ${inviteUrl}
|
|
|
816
817
|
require('../scripts/install-openclaw.js');
|
|
817
818
|
},
|
|
818
819
|
|
|
820
|
+
setup: () => {
|
|
821
|
+
require('../scripts/install-openclaw.js');
|
|
822
|
+
},
|
|
823
|
+
|
|
819
824
|
help: () => {
|
|
820
825
|
console.log(`A2A Calling - Agent-to-Agent Communication
|
|
821
826
|
|
|
@@ -875,6 +880,7 @@ Server:
|
|
|
875
880
|
--owner, -o Owner name (human behind the agent)
|
|
876
881
|
|
|
877
882
|
install Install A2A for OpenClaw
|
|
883
|
+
setup Auto setup (gateway-aware dashboard install)
|
|
878
884
|
|
|
879
885
|
Examples:
|
|
880
886
|
a2a create --name "bappybot" --owner "Benjamin Pollack" --expires 7d
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "a2acalling",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/onthegonow/
|
|
26
|
+
"url": "git+https://github.com/onthegonow/a2a_calling.git"
|
|
27
27
|
},
|
|
28
28
|
"bugs": {
|
|
29
|
-
"url": "https://github.com/onthegonow/
|
|
29
|
+
"url": "https://github.com/onthegonow/a2a_calling/issues"
|
|
30
30
|
},
|
|
31
|
-
"homepage": "https://github.com/onthegonow/
|
|
31
|
+
"homepage": "https://github.com/onthegonow/a2a_calling#readme",
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=18.0.0"
|
|
34
34
|
},
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* A2A Calling - OpenClaw Integration Installer
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Supports automatic setup:
|
|
6
|
+
* - If OpenClaw gateway is detected, install a gateway HTTP proxy plugin
|
|
7
|
+
* so dashboard is accessible at /a2a on gateway.
|
|
8
|
+
* - If gateway is not detected, dashboard runs on standalone A2A server.
|
|
9
|
+
*
|
|
10
10
|
* Usage:
|
|
11
11
|
* npx a2acalling install
|
|
12
|
-
* npx a2acalling
|
|
13
|
-
* npx a2acalling install --port 3001
|
|
12
|
+
* npx a2acalling setup
|
|
13
|
+
* npx a2acalling install --hostname myserver.com --port 3001
|
|
14
14
|
* npx a2acalling uninstall
|
|
15
15
|
*/
|
|
16
16
|
|
|
@@ -21,7 +21,9 @@ const { execSync } = require('child_process');
|
|
|
21
21
|
// Paths
|
|
22
22
|
const OPENCLAW_CONFIG = process.env.OPENCLAW_CONFIG || path.join(process.env.HOME, '.openclaw', 'openclaw.json');
|
|
23
23
|
const OPENCLAW_SKILLS = process.env.OPENCLAW_SKILLS || path.join(process.env.HOME, '.openclaw', 'skills');
|
|
24
|
+
const OPENCLAW_EXTENSIONS = process.env.OPENCLAW_EXTENSIONS || path.join(process.env.HOME, '.openclaw', 'extensions');
|
|
24
25
|
const SKILL_NAME = 'a2a';
|
|
26
|
+
const DASHBOARD_PLUGIN_ID = 'a2a-dashboard-proxy';
|
|
25
27
|
|
|
26
28
|
// Parse args
|
|
27
29
|
const args = process.argv.slice(2);
|
|
@@ -44,6 +46,240 @@ function log(msg) { console.log(`${green('[a2a]')} ${msg}`); }
|
|
|
44
46
|
function warn(msg) { console.log(`${yellow('[a2a]')} ${msg}`); }
|
|
45
47
|
function error(msg) { console.error(`${red('[a2a]')} ${msg}`); }
|
|
46
48
|
|
|
49
|
+
function ensureDir(dirPath) {
|
|
50
|
+
if (!fs.existsSync(dirPath)) {
|
|
51
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function commandExists(cmd) {
|
|
56
|
+
try {
|
|
57
|
+
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
58
|
+
return true;
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function loadOpenClawConfig() {
|
|
65
|
+
if (!fs.existsSync(OPENCLAW_CONFIG)) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(fs.readFileSync(OPENCLAW_CONFIG, 'utf8'));
|
|
70
|
+
} catch (err) {
|
|
71
|
+
warn(`OpenClaw config is unreadable: ${err.message}`);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeOpenClawConfig(config) {
|
|
77
|
+
const backupPath = `${OPENCLAW_CONFIG}.backup.${Date.now()}`;
|
|
78
|
+
fs.copyFileSync(OPENCLAW_CONFIG, backupPath);
|
|
79
|
+
fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(config, null, 2));
|
|
80
|
+
log(`Backed up config to: ${backupPath}`);
|
|
81
|
+
log('Updated OpenClaw config');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function detectGateway(config) {
|
|
85
|
+
const hasBinary = commandExists('openclaw');
|
|
86
|
+
const hasGatewayBlock = Boolean(config?.gateway);
|
|
87
|
+
return hasBinary && hasGatewayBlock;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolveGatewayBaseUrl() {
|
|
91
|
+
if (flags['gateway-url']) {
|
|
92
|
+
return String(flags['gateway-url']).replace(/\/+$/, '');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const output = execSync('openclaw dashboard --no-open', {
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
timeout: 8000,
|
|
99
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
100
|
+
});
|
|
101
|
+
const match = output.match(/Dashboard URL:\s*(\S+)/);
|
|
102
|
+
if (match && match[1]) {
|
|
103
|
+
const parsed = new URL(match[1]);
|
|
104
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// fall through to default
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const fallbackPort = process.env.OPENCLAW_GATEWAY_PORT || '18789';
|
|
111
|
+
return `http://127.0.0.1:${fallbackPort}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const DASHBOARD_PLUGIN_MANIFEST = {
|
|
115
|
+
id: DASHBOARD_PLUGIN_ID,
|
|
116
|
+
name: 'A2A Dashboard Proxy',
|
|
117
|
+
description: 'Proxy A2A dashboard routes through OpenClaw gateway',
|
|
118
|
+
version: '1.0.0',
|
|
119
|
+
configSchema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
additionalProperties: false,
|
|
122
|
+
properties: {
|
|
123
|
+
backendUrl: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
default: 'http://127.0.0.1:3001'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const DASHBOARD_PLUGIN_TS = `import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
132
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
133
|
+
import http from "node:http";
|
|
134
|
+
import https from "node:https";
|
|
135
|
+
|
|
136
|
+
const PLUGIN_ID = "a2a-dashboard-proxy";
|
|
137
|
+
const UI_PREFIX = "/a2a";
|
|
138
|
+
const API_PREFIX = "/api/a2a/dashboard";
|
|
139
|
+
|
|
140
|
+
function sendJson(res: ServerResponse, status: number, body: unknown) {
|
|
141
|
+
res.statusCode = status;
|
|
142
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
143
|
+
res.end(JSON.stringify(body));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function sendHtml(res: ServerResponse, status: number, html: string) {
|
|
147
|
+
res.statusCode = status;
|
|
148
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
149
|
+
res.end(html);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function resolveBackendUrl(api: OpenClawPluginApi): URL {
|
|
153
|
+
const fallback = process.env.A2A_DASHBOARD_BACKEND_URL || "http://127.0.0.1:3001";
|
|
154
|
+
try {
|
|
155
|
+
const cfg = api.runtime.config.loadConfig() as Record<string, unknown>;
|
|
156
|
+
const plugins = (cfg.plugins || {}) as Record<string, unknown>;
|
|
157
|
+
const entries = (plugins.entries || {}) as Record<string, unknown>;
|
|
158
|
+
const pluginEntry = (entries[PLUGIN_ID] || {}) as Record<string, unknown>;
|
|
159
|
+
const candidate = typeof pluginEntry.backendUrl === "string" && pluginEntry.backendUrl
|
|
160
|
+
? pluginEntry.backendUrl
|
|
161
|
+
: fallback;
|
|
162
|
+
return new URL(candidate);
|
|
163
|
+
} catch {
|
|
164
|
+
return new URL(fallback);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function rewriteUiPath(pathname: string): string {
|
|
169
|
+
if (pathname === UI_PREFIX || pathname === UI_PREFIX + "/") {
|
|
170
|
+
return "/dashboard/";
|
|
171
|
+
}
|
|
172
|
+
if (pathname.startsWith(UI_PREFIX + "/")) {
|
|
173
|
+
return "/dashboard/" + pathname.slice((UI_PREFIX + "/").length);
|
|
174
|
+
}
|
|
175
|
+
return pathname;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const plugin = {
|
|
179
|
+
id: PLUGIN_ID,
|
|
180
|
+
name: "A2A Dashboard Proxy",
|
|
181
|
+
description: "Proxy A2A dashboard routes through OpenClaw gateway",
|
|
182
|
+
configSchema: {
|
|
183
|
+
type: "object" as const,
|
|
184
|
+
additionalProperties: false,
|
|
185
|
+
properties: {
|
|
186
|
+
backendUrl: {
|
|
187
|
+
type: "string" as const,
|
|
188
|
+
default: "http://127.0.0.1:3001"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
register(api: OpenClawPluginApi) {
|
|
193
|
+
api.registerHttpHandler(async (req: IncomingMessage, res: ServerResponse) => {
|
|
194
|
+
const incoming = new URL(req.url ?? "/", \`http://\${req.headers.host ?? "localhost"}\`);
|
|
195
|
+
const isUi = incoming.pathname === UI_PREFIX || incoming.pathname.startsWith(UI_PREFIX + "/");
|
|
196
|
+
const isApi = incoming.pathname === API_PREFIX || incoming.pathname.startsWith(API_PREFIX + "/");
|
|
197
|
+
if (!isUi && !isApi) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (incoming.pathname === UI_PREFIX) {
|
|
202
|
+
res.statusCode = 302;
|
|
203
|
+
res.setHeader("Location", UI_PREFIX + "/");
|
|
204
|
+
res.end();
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const backendBase = resolveBackendUrl(api);
|
|
209
|
+
const rewrittenPath = isUi ? rewriteUiPath(incoming.pathname) : incoming.pathname;
|
|
210
|
+
const target = new URL(rewrittenPath + (incoming.search || ""), backendBase);
|
|
211
|
+
const client = target.protocol === "https:" ? https : http;
|
|
212
|
+
|
|
213
|
+
const headers: Record<string, string> = {};
|
|
214
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
215
|
+
if (!value || key.toLowerCase() === "host") continue;
|
|
216
|
+
headers[key] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
217
|
+
}
|
|
218
|
+
headers["x-forwarded-by"] = "a2a-dashboard-proxy";
|
|
219
|
+
|
|
220
|
+
const proxyReq = client.request({
|
|
221
|
+
protocol: target.protocol,
|
|
222
|
+
hostname: target.hostname,
|
|
223
|
+
port: target.port || (target.protocol === "https:" ? 443 : 80),
|
|
224
|
+
path: target.pathname + target.search,
|
|
225
|
+
method: req.method,
|
|
226
|
+
headers
|
|
227
|
+
}, (proxyRes) => {
|
|
228
|
+
res.statusCode = proxyRes.statusCode || 502;
|
|
229
|
+
Object.entries(proxyRes.headers).forEach(([key, value]) => {
|
|
230
|
+
if (value !== undefined) {
|
|
231
|
+
res.setHeader(key, value as string | string[]);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
proxyRes.pipe(res);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
proxyReq.on("error", (err) => {
|
|
238
|
+
const backend = backendBase.toString();
|
|
239
|
+
if (isApi) {
|
|
240
|
+
sendJson(res, 502, {
|
|
241
|
+
success: false,
|
|
242
|
+
error: "dashboard_backend_unreachable",
|
|
243
|
+
message: \`Could not reach A2A server at \${backend}: \${err.message}\`
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
sendHtml(res, 502, \`<!doctype html>
|
|
249
|
+
<html><head><meta charset="utf-8"><title>A2A Dashboard</title></head>
|
|
250
|
+
<body style="font-family: sans-serif; padding: 2rem;">
|
|
251
|
+
<h1>A2A Dashboard Unavailable</h1>
|
|
252
|
+
<p>The gateway proxy is active, but the A2A backend is not reachable.</p>
|
|
253
|
+
<p>Expected backend: <code>\${backend}</code></p>
|
|
254
|
+
<p>Start the backend with: <code>a2a server --port 3001</code></p>
|
|
255
|
+
</body></html>\`);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
req.pipe(proxyReq);
|
|
259
|
+
return true;
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export default plugin;
|
|
265
|
+
`;
|
|
266
|
+
|
|
267
|
+
function installDashboardProxyPlugin(backendUrl) {
|
|
268
|
+
ensureDir(OPENCLAW_EXTENSIONS);
|
|
269
|
+
const pluginDir = path.join(OPENCLAW_EXTENSIONS, DASHBOARD_PLUGIN_ID);
|
|
270
|
+
ensureDir(pluginDir);
|
|
271
|
+
|
|
272
|
+
fs.writeFileSync(
|
|
273
|
+
path.join(pluginDir, 'openclaw.plugin.json'),
|
|
274
|
+
JSON.stringify(DASHBOARD_PLUGIN_MANIFEST, null, 2)
|
|
275
|
+
);
|
|
276
|
+
fs.writeFileSync(path.join(pluginDir, 'index.ts'), DASHBOARD_PLUGIN_TS);
|
|
277
|
+
|
|
278
|
+
log(`Installed gateway dashboard plugin: ${pluginDir}`);
|
|
279
|
+
log(`Dashboard proxy backend: ${backendUrl}`);
|
|
280
|
+
return pluginDir;
|
|
281
|
+
}
|
|
282
|
+
|
|
47
283
|
// Skill content
|
|
48
284
|
const SKILL_MD = `---
|
|
49
285
|
name: a2a
|
|
@@ -144,83 +380,91 @@ a2a server --port 3001
|
|
|
144
380
|
function install() {
|
|
145
381
|
log('Installing A2A Calling for OpenClaw...\n');
|
|
146
382
|
|
|
383
|
+
const hostname = flags.hostname || process.env.HOSTNAME || 'localhost';
|
|
384
|
+
const port = String(flags.port || process.env.A2A_PORT || '3001');
|
|
385
|
+
const backendUrl = flags['dashboard-backend'] || `http://127.0.0.1:${port}`;
|
|
386
|
+
|
|
147
387
|
// 1. Create skills directory if needed
|
|
148
|
-
|
|
149
|
-
fs.mkdirSync(OPENCLAW_SKILLS, { recursive: true });
|
|
150
|
-
log(`Created skills directory: ${OPENCLAW_SKILLS}`);
|
|
151
|
-
}
|
|
388
|
+
ensureDir(OPENCLAW_SKILLS);
|
|
152
389
|
|
|
153
390
|
// 2. Install skill
|
|
154
391
|
const skillDir = path.join(OPENCLAW_SKILLS, SKILL_NAME);
|
|
155
|
-
|
|
156
|
-
fs.mkdirSync(skillDir, { recursive: true });
|
|
157
|
-
}
|
|
392
|
+
ensureDir(skillDir);
|
|
158
393
|
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), SKILL_MD);
|
|
159
394
|
log(`Installed skill to: ${skillDir}`);
|
|
160
395
|
|
|
161
|
-
// 3. Update OpenClaw config
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
396
|
+
// 3. Update OpenClaw config + gateway plugin setup (if available)
|
|
397
|
+
let config = loadOpenClawConfig();
|
|
398
|
+
let configUpdated = false;
|
|
399
|
+
let gatewayDetected = false;
|
|
400
|
+
let dashboardMode = 'standalone';
|
|
401
|
+
let dashboardUrl = `http://${hostname}:${port}/dashboard`;
|
|
402
|
+
|
|
403
|
+
if (config) {
|
|
404
|
+
// Add custom command for each enabled channel
|
|
405
|
+
const channels = ['telegram', 'discord', 'slack'];
|
|
406
|
+
for (const channel of channels) {
|
|
407
|
+
if (config.channels?.[channel]?.enabled) {
|
|
408
|
+
if (!config.channels[channel].customCommands) {
|
|
409
|
+
config.channels[channel].customCommands = [];
|
|
410
|
+
}
|
|
411
|
+
const existing = config.channels[channel].customCommands.find(c => c.command === 'a2a');
|
|
412
|
+
if (!existing) {
|
|
413
|
+
config.channels[channel].customCommands.push({
|
|
414
|
+
command: 'a2a',
|
|
415
|
+
description: 'Agent-to-Agent: create invitations, manage connections'
|
|
416
|
+
});
|
|
417
|
+
configUpdated = true;
|
|
418
|
+
log(`Added /a2a command to ${channel} config`);
|
|
419
|
+
} else {
|
|
420
|
+
log(`/a2a command already exists in ${channel} config`);
|
|
187
421
|
}
|
|
188
422
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
gatewayDetected = detectGateway(config);
|
|
426
|
+
if (gatewayDetected) {
|
|
427
|
+
dashboardMode = 'gateway';
|
|
428
|
+
installDashboardProxyPlugin(backendUrl);
|
|
429
|
+
const gatewayBaseUrl = resolveGatewayBaseUrl();
|
|
430
|
+
dashboardUrl = `${gatewayBaseUrl.replace(/\/+$/, '')}/a2a`;
|
|
431
|
+
|
|
432
|
+
config.plugins = config.plugins || {};
|
|
433
|
+
config.plugins.entries = config.plugins.entries || {};
|
|
434
|
+
const existingEntry = config.plugins.entries[DASHBOARD_PLUGIN_ID] || {};
|
|
435
|
+
config.plugins.entries[DASHBOARD_PLUGIN_ID] = {
|
|
436
|
+
...existingEntry,
|
|
437
|
+
enabled: true,
|
|
438
|
+
backendUrl
|
|
439
|
+
};
|
|
440
|
+
configUpdated = true;
|
|
441
|
+
log(`Configured gateway plugin entry: ${DASHBOARD_PLUGIN_ID}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (configUpdated) {
|
|
445
|
+
writeOpenClawConfig(config);
|
|
446
|
+
warn('Restart OpenClaw gateway to apply changes: openclaw gateway restart');
|
|
204
447
|
}
|
|
205
448
|
} else {
|
|
206
449
|
warn(`OpenClaw config not found at: ${OPENCLAW_CONFIG}`);
|
|
207
|
-
warn('
|
|
450
|
+
warn('Skipping OpenClaw command/plugin config updates');
|
|
208
451
|
}
|
|
209
452
|
|
|
210
|
-
// 4. Show server setup instructions
|
|
211
|
-
const hostname = flags.hostname || process.env.HOSTNAME || 'localhost';
|
|
212
|
-
const port = flags.port || '3001';
|
|
213
|
-
|
|
214
453
|
console.log(`
|
|
215
454
|
${bold('━━━ Server Setup ━━━')}
|
|
216
455
|
|
|
217
|
-
To receive incoming calls
|
|
456
|
+
To receive incoming calls and host A2A APIs:
|
|
457
|
+
|
|
458
|
+
${green(`A2A_HOSTNAME="${hostname}:${port}" a2a server --port ${port}`)}
|
|
218
459
|
|
|
219
|
-
|
|
460
|
+
${bold('━━━ Dashboard Setup ━━━')}
|
|
220
461
|
|
|
221
|
-
|
|
462
|
+
Mode: ${dashboardMode === 'gateway' ? green('gateway') : yellow('standalone')}
|
|
463
|
+
Dashboard URL: ${green(dashboardUrl)}
|
|
222
464
|
|
|
223
|
-
|
|
465
|
+
${dashboardMode === 'gateway'
|
|
466
|
+
? `Gateway path /a2a is now proxied to ${backendUrl}.`
|
|
467
|
+
: 'No gateway detected. Dashboard is served directly from the A2A server.'}
|
|
224
468
|
|
|
225
469
|
${bold('━━━ Usage ━━━')}
|
|
226
470
|
|
|
@@ -242,16 +486,20 @@ ${green('✅ A2A Calling installed successfully!')}
|
|
|
242
486
|
function uninstall() {
|
|
243
487
|
log('Uninstalling A2A Calling...\n');
|
|
244
488
|
|
|
245
|
-
// Remove skill
|
|
246
489
|
const skillDir = path.join(OPENCLAW_SKILLS, SKILL_NAME);
|
|
247
490
|
if (fs.existsSync(skillDir)) {
|
|
248
491
|
fs.rmSync(skillDir, { recursive: true });
|
|
249
492
|
log(`Removed skill from: ${skillDir}`);
|
|
250
493
|
}
|
|
251
494
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
495
|
+
const pluginDir = path.join(OPENCLAW_EXTENSIONS, DASHBOARD_PLUGIN_ID);
|
|
496
|
+
if (fs.existsSync(pluginDir)) {
|
|
497
|
+
fs.rmSync(pluginDir, { recursive: true });
|
|
498
|
+
log(`Removed dashboard plugin: ${pluginDir}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
warn('Custom command and plugin entries in OpenClaw config were not removed.');
|
|
502
|
+
warn('You can remove them manually if desired.');
|
|
255
503
|
|
|
256
504
|
log('✅ Uninstall complete');
|
|
257
505
|
}
|
|
@@ -262,22 +510,27 @@ ${bold('A2A Calling - OpenClaw Integration')}
|
|
|
262
510
|
|
|
263
511
|
Usage:
|
|
264
512
|
npx a2acalling install [options] Install A2A for OpenClaw
|
|
265
|
-
npx a2acalling
|
|
513
|
+
npx a2acalling setup [options] Alias for install (auto gateway/standalone)
|
|
514
|
+
npx a2acalling uninstall Remove A2A skill + dashboard plugin
|
|
266
515
|
npx a2acalling server Start A2A server
|
|
267
516
|
|
|
268
517
|
Install Options:
|
|
269
|
-
--hostname <host>
|
|
270
|
-
--port <port>
|
|
518
|
+
--hostname <host> Hostname for invite URLs (default: system hostname)
|
|
519
|
+
--port <port> A2A server port (default: 3001)
|
|
520
|
+
--gateway-url <url> Force gateway base URL for printed dashboard link
|
|
521
|
+
--dashboard-backend <url> Backend URL used by gateway dashboard proxy
|
|
271
522
|
|
|
272
523
|
Examples:
|
|
273
|
-
npx a2acalling install --hostname myserver.com --port
|
|
274
|
-
npx a2acalling
|
|
524
|
+
npx a2acalling install --hostname myserver.com --port 3001
|
|
525
|
+
npx a2acalling setup --dashboard-backend http://127.0.0.1:3001
|
|
526
|
+
npx a2acalling uninstall
|
|
275
527
|
`);
|
|
276
528
|
}
|
|
277
529
|
|
|
278
530
|
// Main
|
|
279
531
|
switch (command) {
|
|
280
532
|
case 'install':
|
|
533
|
+
case 'setup':
|
|
281
534
|
install();
|
|
282
535
|
break;
|
|
283
536
|
case 'uninstall':
|