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/README.md +33 -9
- package/SKILL.md +69 -5
- package/bin/cli.js +539 -162
- package/docs/protocol.md +24 -14
- package/package.json +1 -1
- package/scripts/install-openclaw.js +64 -64
- package/src/dashboard/public/app.js +765 -28
- package/src/dashboard/public/index.html +57 -13
- package/src/dashboard/public/style.css +16 -0
- package/src/lib/callbook.js +358 -0
- package/src/lib/client.js +1 -2
- package/src/lib/config.js +214 -16
- package/src/lib/conversations.js +74 -0
- package/src/lib/disclosure.js +3 -43
- package/src/lib/external-ip.js +18 -7
- package/src/lib/invite-host.js +24 -21
- package/src/lib/logger.js +26 -14
- package/src/lib/tokens.js +314 -113
- package/src/routes/a2a.js +11 -2
- package/src/routes/callbook.js +142 -0
- package/src/routes/dashboard.js +605 -37
- package/src/server.js +6 -0
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
|
|
156
|
+
## Permission Tiers
|
|
157
157
|
|
|
158
|
-
|
|
|
159
|
-
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
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": "
|
|
180
|
+
"id": "tok_abc123xyz789",
|
|
181
181
|
"token_hash": "sha256...",
|
|
182
182
|
"name": "Alice's agent",
|
|
183
|
-
"
|
|
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
|
-
"
|
|
198
|
+
"contacts": [
|
|
195
199
|
{
|
|
196
|
-
"id": "
|
|
200
|
+
"id": "contact_xyz",
|
|
197
201
|
"name": "Bob's agent",
|
|
202
|
+
"owner": "Bob",
|
|
198
203
|
"host": "bob.example.com",
|
|
199
|
-
"
|
|
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
|
-
"
|
|
291
|
+
"tier": "public",
|
|
282
292
|
"disclosure": "minimal"
|
|
283
293
|
}
|
|
284
294
|
}
|
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
|
912
|
-
/a2a call <url> <msg> Call a
|
|
911
|
+
/a2a add <url> Add a contact
|
|
912
|
+
/a2a call <url> <msg> Call a contact
|
|
913
913
|
|
|
914
914
|
${bold('━━━ Done! ━━━')}
|
|
915
915
|
|