a2acalling 0.6.1 → 0.6.3

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/docs/protocol.md CHANGED
@@ -153,13 +153,13 @@ Dashboard API endpoints:
153
153
 
154
154
  Example filters for `/logs`: `trace_id`, `conversation_id`, `token_id`, `error_code`, `status_code`, `component`, `event`, `level`, `search`, `from`, `to`.
155
155
 
156
- ## Permission Scopes
156
+ ## Permission Tiers
157
157
 
158
- | Scope | Tools | Files | Memory | Actions |
159
- |-------|-------|-------|--------|---------|
160
- | `chat-only` | | ❌ | ❌ | ❌ |
161
- | `tools-read` | Read | Read | | ❌ |
162
- | `tools-write` | All | All | | Notify owner |
158
+ | Tier | Default capabilities |
159
+ |------|----------------------|
160
+ | `public` | `context-read` |
161
+ | `friends` | `context-read`, `calendar.read`, `email.read`, `search` |
162
+ | `family` | `context-read`, `calendar`, `email`, `search`, `tools`, `memory` |
163
163
 
164
164
  ## Disclosure Levels
165
165
 
@@ -177,10 +177,14 @@ Stored in `~/.config/openclaw/a2a.json`:
177
177
  {
178
178
  "tokens": [
179
179
  {
180
- "id": "fed_abc123xyz789",
180
+ "id": "tok_abc123xyz789",
181
181
  "token_hash": "sha256...",
182
182
  "name": "Alice's agent",
183
- "permissions": "chat-only",
183
+ "tier": "public",
184
+ "capabilities": ["context-read"],
185
+ "allowed_topics": ["chat"],
186
+ "allowed_goals": [],
187
+ "tier_settings": {},
184
188
  "disclosure": "minimal",
185
189
  "notify": "all",
186
190
  "max_calls": null,
@@ -191,16 +195,22 @@ Stored in `~/.config/openclaw/a2a.json`:
191
195
  "revoked": false
192
196
  }
193
197
  ],
194
- "remotes": [
198
+ "contacts": [
195
199
  {
196
- "id": "remote_xyz",
200
+ "id": "contact_xyz",
197
201
  "name": "Bob's agent",
202
+ "owner": "Bob",
198
203
  "host": "bob.example.com",
199
- "token": "fed_bobtoken123",
204
+ "token_hash": "sha256...",
205
+ "token_enc": "base64...",
206
+ "server_name": "Bob's server",
207
+ "notes": "Met via A2A",
208
+ "tags": ["collaborator"],
209
+ "fields": { "email": "bob@example.com" },
210
+ "linked_token_id": "tok_abc123xyz789",
200
211
  "added_at": "2026-02-11T18:00:00Z"
201
212
  }
202
- ],
203
- "calls": []
213
+ ]
204
214
  }
205
215
  ```
206
216
 
@@ -278,7 +288,7 @@ When handling an A2A call, inject context:
278
288
  "a2a": {
279
289
  "active": true,
280
290
  "caller": "Alice's Agent",
281
- "permissions": "chat-only",
291
+ "tier": "public",
282
292
  "disclosure": "minimal"
283
293
  }
284
294
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -79,53 +79,6 @@ function loadOpenClawConfig() {
79
79
  }
80
80
  }
81
81
 
82
- function normalizeDashboardPluginEntry(rawEntry, backendUrl) {
83
- const issues = [];
84
- const normalized = {
85
- enabled: true,
86
- config: {}
87
- };
88
- const entry = (rawEntry && typeof rawEntry === 'object') ? rawEntry : {};
89
-
90
- const legacyBackendUrl = (typeof entry.backendUrl === 'string' && entry.backendUrl.trim())
91
- ? entry.backendUrl.trim()
92
- : null;
93
- const rawConfig = (entry.config && typeof entry.config === 'object') ? entry.config : null;
94
-
95
- if (!entry || typeof entry !== 'object') {
96
- issues.push('plugin entry is missing or invalid');
97
- }
98
-
99
- if (entry && typeof entry.enabled === 'boolean') {
100
- normalized.enabled = entry.enabled;
101
- }
102
-
103
- if (rawConfig) {
104
- normalized.config = { ...rawConfig };
105
- } else if (entry.config !== undefined) {
106
- issues.push('plugin entry has non-object config; replacing with empty object');
107
- }
108
-
109
- if (legacyBackendUrl) {
110
- issues.push(`legacy key detected: plugins.entries.${DASHBOARD_PLUGIN_ID}.backendUrl (using backendUrl migration)`);
111
- normalized.config.backendUrl = backendUrl || legacyBackendUrl;
112
- } else if (typeof normalized.config.backendUrl === 'string' && normalized.config.backendUrl.trim()) {
113
- normalized.config.backendUrl = normalized.config.backendUrl.trim();
114
- } else if (typeof backendUrl === 'string' && backendUrl.trim()) {
115
- normalized.config.backendUrl = backendUrl.trim();
116
- } else {
117
- issues.push('backendUrl could not be determined; plugin may fail to route dashboard traffic');
118
- }
119
-
120
- return {
121
- normalized,
122
- issues,
123
- changed: issues.length > 0,
124
- legacyBackendUrl,
125
- summary: `a2a-dashboard-proxy config => ${normalized.enabled ? 'enabled' : 'disabled'}, backendUrl=${normalized.config.backendUrl || 'missing'}`
126
- };
127
- }
128
-
129
82
  function writeOpenClawConfig(config) {
130
83
  const backupPath = `${OPENCLAW_CONFIG}.backup.${Date.now()}`;
131
84
  fs.copyFileSync(OPENCLAW_CONFIG, backupPath);
@@ -135,9 +88,12 @@ function writeOpenClawConfig(config) {
135
88
  }
136
89
 
137
90
  function detectGateway(config) {
138
- const hasBinary = commandExists('openclaw');
139
91
  const hasGatewayBlock = Boolean(config?.gateway);
140
- return hasBinary && hasGatewayBlock;
92
+ // Treat the presence of a gateway block in config as "gateway mode" even if
93
+ // the `openclaw` binary is not on PATH (e.g., CI, remote provisioning, or
94
+ // minimal environments). We still want to migrate config + install the proxy
95
+ // plugin in those cases.
96
+ return hasGatewayBlock;
141
97
  }
142
98
 
143
99
  function resolveGatewayBaseUrl() {
@@ -345,8 +301,6 @@ function resolveBackendUrl(api: OpenClawPluginApi): URL {
345
301
  const entryConfig = (pluginEntry.config || {}) as Record<string, unknown>;
346
302
  const candidate = typeof entryConfig.backendUrl === "string" && entryConfig.backendUrl
347
303
  ? entryConfig.backendUrl
348
- : typeof pluginEntry.backendUrl === "string" && pluginEntry.backendUrl
349
- ? pluginEntry.backendUrl
350
304
  : fallback;
351
305
  return new URL(candidate);
352
306
  } catch {
@@ -699,6 +653,27 @@ async function install() {
699
653
  warn(w);
700
654
  }
701
655
 
656
+ // Network diagnostics: fetch the current egress IP via the same mechanism used for invite host resolution.
657
+ // This is extremely useful during install to separate "networking/ingress" issues from "code" issues.
658
+ let externalIpProbe = null;
659
+ try {
660
+ const { getExternalIp } = require('../src/lib/external-ip');
661
+ // Force refresh so we can surface "blocked outbound HTTPS/DNS" problems during setup.
662
+ externalIpProbe = await getExternalIp({ timeoutMs: 2500, forceRefresh: true });
663
+ } catch (err) {
664
+ externalIpProbe = {
665
+ ip: null,
666
+ error: err && err.message ? err.message : 'external_ip_probe_failed'
667
+ };
668
+ }
669
+ const externalIpSummary = (externalIpProbe && externalIpProbe.ip)
670
+ ? `${externalIpProbe.ip}${externalIpProbe.source ? ` (source: ${externalIpProbe.source})` : ''}` +
671
+ `${externalIpProbe.fromCache ? ' [cache]' : ''}${externalIpProbe.stale ? ' [stale]' : ''}`
672
+ : `FAILED${externalIpProbe && externalIpProbe.error ? ` (${externalIpProbe.error})` : ''}`;
673
+ const externalIpAttempts = (externalIpProbe && Array.isArray(externalIpProbe.attempts))
674
+ ? externalIpProbe.attempts
675
+ : [];
676
+
702
677
  const forceStandalone = Boolean(flags.standalone) || String(process.env.A2A_FORCE_STANDALONE || '').toLowerCase() === 'true';
703
678
  const hasOpenClawBinary = commandExists('openclaw');
704
679
  const hasOpenClawConfig = fs.existsSync(OPENCLAW_CONFIG);
@@ -771,17 +746,15 @@ async function install() {
771
746
  config.plugins = config.plugins || {};
772
747
  config.plugins.entries = config.plugins.entries || {};
773
748
  const rawEntry = config.plugins.entries[DASHBOARD_PLUGIN_ID];
774
- const audit = normalizeDashboardPluginEntry(rawEntry, backendUrl);
775
- for (const issue of audit.issues) {
776
- warn(`a2a-dashboard-proxy config issue: ${issue}`);
777
- }
778
- if (audit.legacyBackendUrl) {
779
- warn(`Auto-fixing legacy key: plugins.entries.${DASHBOARD_PLUGIN_ID}.backendUrl`);
780
- }
781
- if (audit.changed) {
782
- log(`Migrated dashboard plugin config: ${audit.summary}`);
783
- }
784
- config.plugins.entries[DASHBOARD_PLUGIN_ID] = audit.normalized;
749
+ const existingEnabled = (rawEntry && typeof rawEntry.enabled === 'boolean') ? rawEntry.enabled : true;
750
+ const existingConfig = (rawEntry && rawEntry.config && typeof rawEntry.config === 'object') ? rawEntry.config : {};
751
+ config.plugins.entries[DASHBOARD_PLUGIN_ID] = {
752
+ enabled: existingEnabled,
753
+ config: {
754
+ ...existingConfig,
755
+ backendUrl
756
+ }
757
+ };
785
758
  configUpdated = true;
786
759
  log(`Configured gateway plugin entry: ${DASHBOARD_PLUGIN_ID}`);
787
760
  }
@@ -829,6 +802,21 @@ ${bold('━━━ Ingress Setup ━━━')}
829
802
  Invite host: ${green(inviteHost)}
830
803
  Expected ping URL: ${green(invitePingUrl)}
831
804
 
805
+ External IP probe (egress):
806
+ ${externalIpProbe && externalIpProbe.source && String(externalIpProbe.source).includes('ifconfig.me')
807
+ ? green('ifconfig.me')
808
+ : 'external-ip resolver'} → ${externalIpProbe && externalIpProbe.ip ? green(externalIpSummary) : yellow(externalIpSummary)}
809
+ ${externalIpAttempts.length
810
+ ? ` Attempts:
811
+ ${externalIpAttempts.map(a => {
812
+ const service = a && a.service ? String(a.service) : '-';
813
+ const ok = Boolean(a && a.ok);
814
+ const status = a && a.statusCode ? ` (${a.statusCode})` : '';
815
+ const err = a && a.error ? ` (${a.error})` : '';
816
+ return ` - ${service}: ${ok ? 'ok' + status : 'failed' + err}`;
817
+ }).join('\n')}`
818
+ : ''}
819
+
832
820
  Port 80 scan:
833
821
  ${port80.a2aPingOk
834
822
  ? green('Port 80 responds to /api/a2a/ping (A2A ready on :80)')
@@ -872,6 +860,18 @@ ${inviteLooksLocal
872
860
  ? green(`External ping OK via ${externalPing.provider}`)
873
861
  : yellow(`External ping FAILED (expected if the server is not running yet, or ingress is not publicly reachable).`)}
874
862
 
863
+ ${bold('━━━ Debug Quick Checks ━━━')}
864
+
865
+ On the server:
866
+ ${green(`curl -s http://127.0.0.1:${backendPort}/api/a2a/status`)}
867
+ ${green('curl -s https://ifconfig.me/ip')}
868
+
869
+ From a remote machine (tests ingress):
870
+ ${green(`curl -s ${inviteScheme}://${inviteHost}/api/a2a/status`)}
871
+
872
+ If calls fail, check the dashboard logs:
873
+ Open ${green(dashboardUrl)} -> Logs
874
+
875
875
  ${bold('━━━ Dashboard Setup ━━━')}
876
876
 
877
877
  Mode: ${dashboardMode === 'gateway' ? green('gateway') : yellow('standalone')}
@@ -908,8 +908,8 @@ In your chat app, use:
908
908
  /a2a invite Create an invitation token
909
909
  /a2a list List active tokens
910
910
  /a2a revoke <id> Revoke a token
911
- /a2a add <url> Add a remote agent
912
- /a2a call <url> <msg> Call a remote agent
911
+ /a2a add <url> Add a contact
912
+ /a2a call <url> <msg> Call a contact
913
913
 
914
914
  ${bold('━━━ Done! ━━━')}
915
915