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 CHANGED
@@ -13,7 +13,7 @@ Then add to your OpenClaw config (`~/.openclaw/openclaw.json`):
13
13
  ```json
14
14
  {
15
15
  "channels": {
16
- "a2a": {
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
@@ -14,7 +14,7 @@ Add to your Clawdbot config:
14
14
 
15
15
  ```yaml
16
16
  channels:
17
- a2a:
17
+ gopherhole: # renamed from `a2a` in v0.5.0
18
18
  enabled: true
19
19
  agentId: nova # Our identifier (default: openclaw)
20
20
  agentName: Nova # Display name
@@ -40,7 +40,21 @@ function loadAgentSecrets(agentId) {
40
40
  }
41
41
  }
42
42
  function resolveA2AConfig(cfg) {
43
- return cfg?.channels?.a2a ?? {};
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: 'a2a',
60
- label: 'A2A',
61
- selectionLabel: 'A2A (Agent-to-Agent)',
62
- detailLabel: 'A2A Protocol',
63
- docsPath: '/channels/a2a',
64
- docsLabel: 'a2a',
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', 'gopherhole'],
81
+ aliases: ['agent2agent'],
68
82
  order: 200,
69
83
  };
70
84
  export const a2aPlugin = {
71
- id: 'a2a',
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.a2a'] },
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
- a2a: {
93
- ...next.channels?.a2a,
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.a2a.dmPolicy',
115
- allowFromPath: 'channels.a2a.',
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
- a2a: {
145
- ...next.channels?.a2a,
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: 'a2a', success: false, error: 'A2A not connected' };
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: 'a2a',
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: 'a2a',
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(`[a2a] Starting A2A channel`);
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:a2a:${message.from}`;
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(`[a2a] A2A channel started`);
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(`[a2a] Stopping A2A channel`);
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?.(`[a2a] Error during stop: ${err.message}`);
345
+ ctx.log?.error?.(`[gopherhole] Error during stop: ${err.message}`);
332
346
  }
333
347
  if (connectionManager === localConnection) {
334
348
  connectionManager = null;
@@ -45,7 +45,7 @@ export interface A2AResponse {
45
45
  * Example:
46
46
  * {
47
47
  * "channels": {
48
- * "a2a": {
48
+ * "gopherhole": {
49
49
  * "enabled": true,
50
50
  * "bridgeUrl": "wss://hub.gopherhole.ai/ws",
51
51
  * "apiKey": "gph_your_api_key"
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "id": "gopherhole_openclaw_a2a",
3
- "channels": ["a2a"],
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
- "a2a": {
16
- "label": "A2A (GopherHole)",
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 A2A channel"
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": "A2A (GopherHole)",
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": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gopherhole_openclaw_a2a",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "description": "GopherHole A2A plugin for OpenClaw - connect your AI agent to the GopherHole network",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",