gopherhole_openclaw_a2a 0.4.8 → 0.5.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/README.md +44 -1
- package/SKILL.md +1 -1
- package/dist/src/channel.js +38 -24
- package/dist/src/types.d.ts +1 -1
- package/openclaw.plugin.json +8 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Then add to your OpenClaw config (`~/.openclaw/openclaw.json`):
|
|
|
13
13
|
```json
|
|
14
14
|
{
|
|
15
15
|
"channels": {
|
|
16
|
-
"
|
|
16
|
+
"gopherhole": {
|
|
17
17
|
"enabled": true,
|
|
18
18
|
"bridgeUrl": "wss://hub.gopherhole.ai/ws",
|
|
19
19
|
"apiKey": "gph_your_api_key_here"
|
|
@@ -28,6 +28,10 @@ Then restart the gateway:
|
|
|
28
28
|
openclaw gateway restart
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
> **Upgrading from 0.4.x?** The channel was renamed from `a2a` to `gopherhole`
|
|
32
|
+
> in 0.5.0. Rename the `channels.a2a` block in your config to
|
|
33
|
+
> `channels.gopherhole`. See [Migration](#migration-from-04x) below.
|
|
34
|
+
|
|
31
35
|
## Getting an API Key
|
|
32
36
|
|
|
33
37
|
1. Go to [gopherhole.ai](https://gopherhole.ai)
|
|
@@ -54,6 +58,45 @@ a2a_agents action=list
|
|
|
54
58
|
a2a_agents action=send agentId=@memory message="store: remember this"
|
|
55
59
|
```
|
|
56
60
|
|
|
61
|
+
## Migration from 0.4.x
|
|
62
|
+
|
|
63
|
+
OpenClaw 2026.9.0 ships its own stock `a2a` channel for the A2A v1.0 protocol,
|
|
64
|
+
which claimed the same channel ID as this plugin. Because the two have
|
|
65
|
+
different config schemas, OpenClaw refuses to start:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Gateway aborted: config is invalid.
|
|
69
|
+
channels.a2a: invalid config: must not have additional properties:
|
|
70
|
+
"agentId", "agentName", "apiKey", "bridgeUrl", "requestTimeoutMs"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Do not run `openclaw doctor --fix` here** — it repairs the config by deleting
|
|
74
|
+
the unrecognised keys, taking your API key with them.
|
|
75
|
+
|
|
76
|
+
Instead, rename the block in `~/.openclaw/openclaw.json`:
|
|
77
|
+
|
|
78
|
+
```diff
|
|
79
|
+
"channels": {
|
|
80
|
+
- "a2a": {
|
|
81
|
+
+ "gopherhole": {
|
|
82
|
+
"enabled": true,
|
|
83
|
+
"bridgeUrl": "wss://hub.gopherhole.ai/ws",
|
|
84
|
+
"apiKey": "gph_your_api_key_here"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then restart:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
openclaw gateway restart
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The old `channels.a2a` path is still read as a fallback (with a deprecation
|
|
96
|
+
warning) and will be removed in a future release. Note the fallback only
|
|
97
|
+
applies when OpenClaw's stock `a2a` plugin is disabled — while it is enabled,
|
|
98
|
+
its schema rejects `channels.a2a` before this plugin ever loads.
|
|
99
|
+
|
|
57
100
|
## Links
|
|
58
101
|
|
|
59
102
|
- [GopherHole Hub](https://gopherhole.ai)
|
package/SKILL.md
CHANGED
package/dist/src/channel.js
CHANGED
|
@@ -40,7 +40,21 @@ function loadAgentSecrets(agentId) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
function resolveA2AConfig(cfg) {
|
|
43
|
-
|
|
43
|
+
const channels = cfg?.channels;
|
|
44
|
+
const current = channels?.gopherhole;
|
|
45
|
+
if (current)
|
|
46
|
+
return current;
|
|
47
|
+
// Back-compat: this channel was `a2a` before v0.5.0. OpenClaw now ships its
|
|
48
|
+
// own stock `a2a` channel, so we moved to `gopherhole` to end the clash.
|
|
49
|
+
// Legacy config is still honoured, but only when the stock plugin is
|
|
50
|
+
// disabled -- otherwise its schema rejects channels.a2a before we load.
|
|
51
|
+
const legacy = channels?.a2a;
|
|
52
|
+
if (legacy) {
|
|
53
|
+
a2aLog.error('config_legacy_path', 'channels.a2a is deprecated -- rename it to channels.gopherhole. ' +
|
|
54
|
+
'Support for the old path will be removed in a future release.', {});
|
|
55
|
+
return legacy;
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
44
58
|
}
|
|
45
59
|
function resolveA2AAccount(opts) {
|
|
46
60
|
const config = resolveA2AConfig(opts.cfg);
|
|
@@ -56,19 +70,19 @@ function resolveA2AAccount(opts) {
|
|
|
56
70
|
};
|
|
57
71
|
}
|
|
58
72
|
const meta = {
|
|
59
|
-
id: '
|
|
60
|
-
label: '
|
|
61
|
-
selectionLabel: '
|
|
62
|
-
detailLabel: 'A2A
|
|
63
|
-
docsPath: '/channels/
|
|
64
|
-
docsLabel: '
|
|
73
|
+
id: 'gopherhole',
|
|
74
|
+
label: 'GopherHole',
|
|
75
|
+
selectionLabel: 'GopherHole (Agent-to-Agent)',
|
|
76
|
+
detailLabel: 'GopherHole A2A',
|
|
77
|
+
docsPath: '/channels/gopherhole',
|
|
78
|
+
docsLabel: 'gopherhole',
|
|
65
79
|
blurb: 'Communicate with other AI agents via GopherHole A2A protocol.',
|
|
66
80
|
systemImage: 'bubble.left.and.bubble.right',
|
|
67
|
-
aliases: ['agent2agent'
|
|
81
|
+
aliases: ['agent2agent'],
|
|
68
82
|
order: 200,
|
|
69
83
|
};
|
|
70
84
|
export const a2aPlugin = {
|
|
71
|
-
id: '
|
|
85
|
+
id: 'gopherhole',
|
|
72
86
|
meta,
|
|
73
87
|
capabilities: {
|
|
74
88
|
chatTypes: ['direct'],
|
|
@@ -78,7 +92,7 @@ export const a2aPlugin = {
|
|
|
78
92
|
unsend: false,
|
|
79
93
|
reply: false,
|
|
80
94
|
},
|
|
81
|
-
reload: { configPrefixes: ['channels.
|
|
95
|
+
reload: { configPrefixes: ['channels.gopherhole'] },
|
|
82
96
|
config: {
|
|
83
97
|
listAccountIds: () => [DEFAULT_ACCOUNT_ID],
|
|
84
98
|
resolveAccount: (cfg, accountId) => resolveA2AAccount({ cfg: cfg, accountId }),
|
|
@@ -89,8 +103,8 @@ export const a2aPlugin = {
|
|
|
89
103
|
...next,
|
|
90
104
|
channels: {
|
|
91
105
|
...next.channels,
|
|
92
|
-
|
|
93
|
-
...next.channels?.
|
|
106
|
+
gopherhole: {
|
|
107
|
+
...next.channels?.gopherhole,
|
|
94
108
|
enabled,
|
|
95
109
|
},
|
|
96
110
|
},
|
|
@@ -111,8 +125,8 @@ export const a2aPlugin = {
|
|
|
111
125
|
resolveDmPolicy: ({ account }) => ({
|
|
112
126
|
policy: 'open', // A2A connections are pre-configured, no pairing needed
|
|
113
127
|
allowFrom: [],
|
|
114
|
-
policyPath: 'channels.
|
|
115
|
-
allowFromPath: 'channels.
|
|
128
|
+
policyPath: 'channels.gopherhole.dmPolicy',
|
|
129
|
+
allowFromPath: 'channels.gopherhole.',
|
|
116
130
|
approveHint: '',
|
|
117
131
|
normalizeEntry: (raw) => raw,
|
|
118
132
|
}),
|
|
@@ -141,8 +155,8 @@ export const a2aPlugin = {
|
|
|
141
155
|
...next,
|
|
142
156
|
channels: {
|
|
143
157
|
...next.channels,
|
|
144
|
-
|
|
145
|
-
...next.channels?.
|
|
158
|
+
gopherhole: {
|
|
159
|
+
...next.channels?.gopherhole,
|
|
146
160
|
enabled: true,
|
|
147
161
|
...(input.httpUrl ? { bridgeUrl: input.httpUrl } : {}),
|
|
148
162
|
},
|
|
@@ -165,13 +179,13 @@ export const a2aPlugin = {
|
|
|
165
179
|
},
|
|
166
180
|
sendText: async ({ to, text }) => {
|
|
167
181
|
if (!connectionManager) {
|
|
168
|
-
return { channel: '
|
|
182
|
+
return { channel: 'gopherhole', success: false, error: 'A2A not connected' };
|
|
169
183
|
}
|
|
170
184
|
try {
|
|
171
185
|
const secrets = loadAgentSecrets(to) ?? undefined;
|
|
172
186
|
const response = await connectionManager.sendMessage(to, text, secrets ? { secrets } : undefined);
|
|
173
187
|
return {
|
|
174
|
-
channel: '
|
|
188
|
+
channel: 'gopherhole',
|
|
175
189
|
success: true,
|
|
176
190
|
messageId: response.status,
|
|
177
191
|
response: response.text,
|
|
@@ -179,7 +193,7 @@ export const a2aPlugin = {
|
|
|
179
193
|
}
|
|
180
194
|
catch (err) {
|
|
181
195
|
return {
|
|
182
|
-
channel: '
|
|
196
|
+
channel: 'gopherhole',
|
|
183
197
|
success: false,
|
|
184
198
|
error: err.message,
|
|
185
199
|
};
|
|
@@ -225,7 +239,7 @@ export const a2aPlugin = {
|
|
|
225
239
|
startAccount: async (ctx) => {
|
|
226
240
|
const account = ctx.account;
|
|
227
241
|
const config = account.config;
|
|
228
|
-
ctx.log?.info(`[
|
|
242
|
+
ctx.log?.info(`[gopherhole] Starting A2A channel`);
|
|
229
243
|
ctx.setStatus({ accountId: account.accountId });
|
|
230
244
|
const localConnection = new A2AConnectionManager(config);
|
|
231
245
|
// Defensive: prior plugin versions leaked a connection on every
|
|
@@ -257,7 +271,7 @@ export const a2aPlugin = {
|
|
|
257
271
|
try {
|
|
258
272
|
// Use chat.send to route the message through the agent
|
|
259
273
|
// Session key format: agent:<agentId>:<channel>:<chatId>
|
|
260
|
-
const sessionKey = `agent:main:
|
|
274
|
+
const sessionKey = `agent:main:gopherhole:${message.from}`;
|
|
261
275
|
a2aLog.messageProcessing(message.taskId, sessionKey);
|
|
262
276
|
// Add A2A context so the agent knows to relay its full response
|
|
263
277
|
const a2aContext = `[A2A Request from agent "${message.from}"]\n\n${text}\n\n[Note: Your complete response will be sent back to the requesting agent. Include all relevant information in your reply.]`;
|
|
@@ -305,7 +319,7 @@ export const a2aPlugin = {
|
|
|
305
319
|
running: true,
|
|
306
320
|
lastStartAt: Date.now(),
|
|
307
321
|
});
|
|
308
|
-
ctx.log?.info(`[
|
|
322
|
+
ctx.log?.info(`[gopherhole] A2A channel started`);
|
|
309
323
|
// OpenClaw's channel supervisor treats resolution of this promise as
|
|
310
324
|
// "channel exited" and immediately schedules an auto-restart. Hold the
|
|
311
325
|
// channel open for its lifetime by blocking on the abort signal — the
|
|
@@ -323,12 +337,12 @@ export const a2aPlugin = {
|
|
|
323
337
|
});
|
|
324
338
|
}
|
|
325
339
|
finally {
|
|
326
|
-
ctx.log?.info(`[
|
|
340
|
+
ctx.log?.info(`[gopherhole] Stopping A2A channel`);
|
|
327
341
|
try {
|
|
328
342
|
await localConnection.stop();
|
|
329
343
|
}
|
|
330
344
|
catch (err) {
|
|
331
|
-
ctx.log?.error?.(`[
|
|
345
|
+
ctx.log?.error?.(`[gopherhole] Error during stop: ${err.message}`);
|
|
332
346
|
}
|
|
333
347
|
if (connectionManager === localConnection) {
|
|
334
348
|
connectionManager = null;
|
package/dist/src/types.d.ts
CHANGED
package/openclaw.plugin.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "gopherhole_openclaw_a2a",
|
|
3
|
-
"channels": ["
|
|
3
|
+
"channels": ["gopherhole"],
|
|
4
|
+
"contracts": {
|
|
5
|
+
"tools": ["a2a_agents", "a2a_discover_nearby"]
|
|
6
|
+
},
|
|
4
7
|
"configSchema": {
|
|
5
8
|
"type": "object",
|
|
6
9
|
"additionalProperties": false,
|
|
@@ -12,8 +15,8 @@
|
|
|
12
15
|
}
|
|
13
16
|
},
|
|
14
17
|
"channelConfigs": {
|
|
15
|
-
"
|
|
16
|
-
"label": "
|
|
18
|
+
"gopherhole": {
|
|
19
|
+
"label": "GopherHole",
|
|
17
20
|
"description": "Agent-to-agent communication over the GopherHole hub. Lets other AI agents discover and message this OpenClaw instance.",
|
|
18
21
|
"schema": {
|
|
19
22
|
"type": "object",
|
|
@@ -22,7 +25,7 @@
|
|
|
22
25
|
"enabled": {
|
|
23
26
|
"type": "boolean",
|
|
24
27
|
"default": false,
|
|
25
|
-
"description": "Enable the
|
|
28
|
+
"description": "Enable the GopherHole channel"
|
|
26
29
|
},
|
|
27
30
|
"apiKey": {
|
|
28
31
|
"type": "string",
|
|
@@ -84,7 +87,7 @@
|
|
|
84
87
|
},
|
|
85
88
|
"uiHints": {
|
|
86
89
|
"": {
|
|
87
|
-
"label": "
|
|
90
|
+
"label": "GopherHole",
|
|
88
91
|
"help": "Connect this OpenClaw instance to the GopherHole hub so other AI agents can discover and message it. Get an API key at https://gopherhole.ai."
|
|
89
92
|
},
|
|
90
93
|
"apiKey": {
|