a2acalling 0.6.51 → 0.6.53
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 +1 -0
- package/bin/cli.js +83 -7
- package/docs/protocol.md +6 -5
- package/native/macos/index.html +24 -12
- package/native/macos/package-lock.json +232 -0
- package/native/macos/src-tauri/src/discovery.rs +100 -13
- package/native/macos/src-tauri/src/health.rs +2 -3
- package/native/macos/src-tauri/src/notifications.rs +1 -1
- package/native/macos/src-tauri/src/server.rs +4 -1
- package/package.json +1 -1
- package/src/dashboard/public/app.js +2 -0
- package/src/dashboard/public/index.html +1 -0
- package/src/lib/claude-subagent.js +302 -92
- package/src/lib/config.js +11 -0
- package/src/lib/conversation-driver.js +18 -2
- package/src/lib/disclosure.js +89 -13
- package/src/lib/runtime-adapter.js +76 -20
- package/src/lib/tokens.js +18 -0
- package/src/routes/a2a.js +7 -0
- package/src/routes/dashboard.js +9 -1
- package/src/server.js +44 -2
package/README.md
CHANGED
|
@@ -149,6 +149,7 @@ Customize tiers in `~/.config/openclaw/a2a-config.json`:
|
|
|
149
149
|
"tiers": {
|
|
150
150
|
"friends": {
|
|
151
151
|
"topics": ["chat", "web", "files", "calendar"],
|
|
152
|
+
"allowed_tools": ["Bash(readonly)", "Read", "Grep", "Glob", "WebSearch", "WebFetch"],
|
|
152
153
|
"disclosure": "minimal"
|
|
153
154
|
}
|
|
154
155
|
}
|
package/bin/cli.js
CHANGED
|
@@ -646,6 +646,22 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
646
646
|
return tierData.topics.map(t => String(t && t.topic || '').trim()).filter(Boolean);
|
|
647
647
|
}
|
|
648
648
|
|
|
649
|
+
// Helper to extract allowed tools per tier from the disclosure manifest.
|
|
650
|
+
function getTierTools(tierData) {
|
|
651
|
+
if (!tierData || !Array.isArray(tierData.allowed_tools)) return [];
|
|
652
|
+
const seen = new Set();
|
|
653
|
+
const out = [];
|
|
654
|
+
for (const tool of tierData.allowed_tools) {
|
|
655
|
+
const cleaned = String(tool || '').trim();
|
|
656
|
+
if (!cleaned) continue;
|
|
657
|
+
const key = cleaned.toLowerCase();
|
|
658
|
+
if (seen.has(key)) continue;
|
|
659
|
+
seen.add(key);
|
|
660
|
+
out.push(cleaned);
|
|
661
|
+
}
|
|
662
|
+
return out;
|
|
663
|
+
}
|
|
664
|
+
|
|
649
665
|
const tiersData = manifest.tiers || {};
|
|
650
666
|
|
|
651
667
|
// Derive goals from disclosure objectives (used in tier config and token creation)
|
|
@@ -659,19 +675,32 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
659
675
|
: ['grow-network', 'find-collaborators', 'build-in-public'];
|
|
660
676
|
|
|
661
677
|
try {
|
|
662
|
-
|
|
678
|
+
const publicTools = getTierTools(tiersData.public);
|
|
679
|
+
const friendsTools = [...publicTools, ...getTierTools(tiersData.friends)];
|
|
680
|
+
const familyTools = [...friendsTools, ...getTierTools(tiersData.family)];
|
|
681
|
+
|
|
682
|
+
const publicTierPatch = {
|
|
663
683
|
topics: getTierTopics(tiersData.public),
|
|
664
684
|
goals: tokenGoals,
|
|
665
685
|
disclosure: 'minimal'
|
|
666
|
-
}
|
|
667
|
-
|
|
686
|
+
};
|
|
687
|
+
if (publicTools.length > 0) publicTierPatch.allowed_tools = publicTools;
|
|
688
|
+
|
|
689
|
+
const friendsTierPatch = {
|
|
668
690
|
topics: [...getTierTopics(tiersData.public), ...getTierTopics(tiersData.friends)],
|
|
669
691
|
disclosure: 'standard'
|
|
670
|
-
}
|
|
671
|
-
|
|
692
|
+
};
|
|
693
|
+
if (friendsTools.length > 0) friendsTierPatch.allowed_tools = friendsTools;
|
|
694
|
+
|
|
695
|
+
const familyTierPatch = {
|
|
672
696
|
topics: [...getTierTopics(tiersData.public), ...getTierTopics(tiersData.friends), ...getTierTopics(tiersData.family)],
|
|
673
697
|
disclosure: 'full'
|
|
674
|
-
}
|
|
698
|
+
};
|
|
699
|
+
if (familyTools.length > 0) familyTierPatch.allowed_tools = familyTools;
|
|
700
|
+
|
|
701
|
+
config.setTier('public', publicTierPatch);
|
|
702
|
+
config.setTier('friends', friendsTierPatch);
|
|
703
|
+
config.setTier('family', familyTierPatch);
|
|
675
704
|
} catch (err) {
|
|
676
705
|
console.error(` Warning: could not sync tier config: ${err.message}`);
|
|
677
706
|
}
|
|
@@ -702,6 +731,7 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
702
731
|
const hostname = config.getAgent().hostname || process.env.A2A_HOSTNAME || 'localhost';
|
|
703
732
|
|
|
704
733
|
const publicTopics = getTierTopics(tiersData.public);
|
|
734
|
+
const publicTools = getTierTools(tiersData.public);
|
|
705
735
|
|
|
706
736
|
const { token } = store.create({
|
|
707
737
|
name: agentName,
|
|
@@ -712,6 +742,7 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
712
742
|
maxCalls: null,
|
|
713
743
|
allowedTopics: publicTopics,
|
|
714
744
|
allowedGoals: tokenGoals,
|
|
745
|
+
allowedTools: publicTools.length > 0 ? publicTools : null,
|
|
715
746
|
notify: 'all'
|
|
716
747
|
});
|
|
717
748
|
|
|
@@ -725,6 +756,33 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
725
756
|
console.log(` Disclosure: ${MANIFEST_FILE}`);
|
|
726
757
|
console.log(` Invite: ${inviteUrl}\n`);
|
|
727
758
|
|
|
759
|
+
// Native app install should be part of the onboarding tail on macOS so users
|
|
760
|
+
// don't end up launching the app before a2a setup is complete.
|
|
761
|
+
if (os.platform() === 'darwin' && !findNativeApp()) {
|
|
762
|
+
if (isInteractiveShell()) {
|
|
763
|
+
const installNow = await promptYesNo('Install the native macOS app? [Y/n] ');
|
|
764
|
+
if (installNow) {
|
|
765
|
+
const result = installNativeMacApp({ force: false, quiet: false });
|
|
766
|
+
if (result.success) {
|
|
767
|
+
if (result.reason === 'already_current') {
|
|
768
|
+
console.log(`Native app already installed at current version (${result.version}).`);
|
|
769
|
+
console.log(`Path: ${result.appPath}\n`);
|
|
770
|
+
} else {
|
|
771
|
+
console.log(`Native app installed (v${result.version}).`);
|
|
772
|
+
console.log(`Path: ${result.appPath}\n`);
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
console.warn(`Native app install failed: ${result.error || 'unknown error'}`);
|
|
776
|
+
console.warn('You can retry with: a2a app install\n');
|
|
777
|
+
}
|
|
778
|
+
} else {
|
|
779
|
+
console.log('You can install the native app later with: a2a app install\n');
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
console.log('Install the native macOS app with: a2a app install\n');
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
728
786
|
return true;
|
|
729
787
|
}
|
|
730
788
|
|
|
@@ -787,6 +845,7 @@ const commands = {
|
|
|
787
845
|
|
|
788
846
|
// Get tier from --tier or --permissions flag
|
|
789
847
|
const tier = args.flags.tier || args.flags.t || args.flags.permissions || args.flags.p || 'public';
|
|
848
|
+
const configTier = config.getTiers?.()[tier] || {};
|
|
790
849
|
|
|
791
850
|
// Get owner from flag or config
|
|
792
851
|
const configAgent = config.getAgent() || {};
|
|
@@ -807,6 +866,9 @@ const commands = {
|
|
|
807
866
|
|
|
808
867
|
// Get objectives from disclosure
|
|
809
868
|
const objectives = tierTopics.objectives || [];
|
|
869
|
+
const allowedTools = args.flags.tools
|
|
870
|
+
? String(args.flags.tools).split(',').map(t => t.trim()).filter(Boolean)
|
|
871
|
+
: (Array.isArray(configTier.allowed_tools) ? configTier.allowed_tools : null);
|
|
810
872
|
const timeoutMsRaw = args.flags['timeout-ms'] || args.flags.timeout_ms;
|
|
811
873
|
const timeoutMs = timeoutMsRaw ? Number.parseInt(String(timeoutMsRaw), 10) : null;
|
|
812
874
|
|
|
@@ -820,6 +882,7 @@ const commands = {
|
|
|
820
882
|
maxCalls,
|
|
821
883
|
allowedTopics,
|
|
822
884
|
allowedGoals: objectives.map(o => o.objective || o),
|
|
885
|
+
allowedTools,
|
|
823
886
|
timeoutMs
|
|
824
887
|
});
|
|
825
888
|
|
|
@@ -856,6 +919,9 @@ const commands = {
|
|
|
856
919
|
console.log(`Expires: ${record.expires_at || 'never'}`);
|
|
857
920
|
console.log(`Tier: ${record.tier}`);
|
|
858
921
|
console.log(`Topics: ${record.allowed_topics.join(', ')}`);
|
|
922
|
+
if (Array.isArray(record.allowed_tools) && record.allowed_tools.length > 0) {
|
|
923
|
+
console.log(`Tools: ${record.allowed_tools.join(', ')}`);
|
|
924
|
+
}
|
|
859
925
|
console.log(`Disclosure: ${record.disclosure}`);
|
|
860
926
|
console.log(`Notify: ${record.notify}`);
|
|
861
927
|
console.log(`Max calls: ${record.max_calls || 'unlimited'}`);
|
|
@@ -2117,6 +2183,9 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
|
|
|
2117
2183
|
});
|
|
2118
2184
|
} else {
|
|
2119
2185
|
console.log(' Using existing server.');
|
|
2186
|
+
// Persist detected server port even when reusing an already-running process.
|
|
2187
|
+
// Native app discovery depends on onboarding.server_port as a primary hint.
|
|
2188
|
+
config.setOnboarding({ server_port: serverPort });
|
|
2120
2189
|
}
|
|
2121
2190
|
console.log(' ✅ A2A server is running');
|
|
2122
2191
|
|
|
@@ -2282,6 +2351,11 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
|
|
|
2282
2351
|
}
|
|
2283
2352
|
|
|
2284
2353
|
if (action === 'install') {
|
|
2354
|
+
if (os.platform() === 'darwin' && !force && !isOnboarded()) {
|
|
2355
|
+
console.error('Onboarding not complete. Run `a2a quickstart` first, then install the app.');
|
|
2356
|
+
console.error('Use `a2a app install --force` to bypass this check.');
|
|
2357
|
+
process.exit(1);
|
|
2358
|
+
}
|
|
2285
2359
|
const result = installNativeMacApp({ force, quiet });
|
|
2286
2360
|
if (result.skipped === 'not_macos') {
|
|
2287
2361
|
console.error('Native app install is only available on macOS.');
|
|
@@ -2856,6 +2930,7 @@ Commands:
|
|
|
2856
2930
|
--expires, -e Expiration (1h, 1d, 7d, 30d, never)
|
|
2857
2931
|
--permissions, -p Tier (public, friends, family)
|
|
2858
2932
|
--topics Custom topics (comma-separated, overrides tier defaults)
|
|
2933
|
+
--tools Custom tool allowlist (comma-separated, overrides tier defaults)
|
|
2859
2934
|
--disclosure, -d Disclosure level (public, minimal, none)
|
|
2860
2935
|
--notify Owner notification (all, summary, none)
|
|
2861
2936
|
--max-calls Maximum invocations (default: 100)
|
|
@@ -2904,7 +2979,7 @@ Calling:
|
|
|
2904
2979
|
app Manage native macOS app
|
|
2905
2980
|
status Show native app installation status (default)
|
|
2906
2981
|
install Install/update native app from GitHub release
|
|
2907
|
-
--force, -f Reinstall
|
|
2982
|
+
--force, -f Reinstall/bypass onboarding guard
|
|
2908
2983
|
--quiet, -q Suppress download/extract output
|
|
2909
2984
|
uninstall Remove native app from ~/Applications
|
|
2910
2985
|
|
|
@@ -2939,6 +3014,7 @@ Server:
|
|
|
2939
3014
|
Examples:
|
|
2940
3015
|
a2a create --name "bappybot" --owner "Benjamin Pollack" --expires 7d
|
|
2941
3016
|
a2a create --name "custom" --topics "chat,calendar.read,email.read"
|
|
3017
|
+
a2a create --name "research" --tools "Read,Grep,Glob,WebSearch,WebFetch"
|
|
2942
3018
|
a2a contacts add a2a://host/fed_xxx --name "Alice" --owner "Alice Chen"
|
|
2943
3019
|
a2a contacts link Alice tok_abc123
|
|
2944
3020
|
a2a call Alice "Hello!"
|
package/docs/protocol.md
CHANGED
|
@@ -155,11 +155,11 @@ Example filters for `/logs`: `trace_id`, `conversation_id`, `token_id`, `error_c
|
|
|
155
155
|
|
|
156
156
|
## Permission Tiers
|
|
157
157
|
|
|
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` |
|
|
158
|
+
| Tier | Default capabilities | Default `allowed_tools` |
|
|
159
|
+
|------|----------------------|--------------------------|
|
|
160
|
+
| `public` | `context-read` | `Read`, `Grep`, `Glob` |
|
|
161
|
+
| `friends` | `context-read`, `calendar.read`, `email.read`, `search` | `Bash(readonly)`, `Read`, `Grep`, `Glob`, `WebSearch`, `WebFetch` |
|
|
162
|
+
| `family` | `context-read`, `calendar`, `email`, `search`, `tools`, `memory` | `Bash`, `Read`, `Grep`, `Glob`, `WebSearch`, `WebFetch` |
|
|
163
163
|
|
|
164
164
|
## Disclosure Levels
|
|
165
165
|
|
|
@@ -184,6 +184,7 @@ Stored in `~/.config/openclaw/a2a.json`:
|
|
|
184
184
|
"capabilities": ["context-read"],
|
|
185
185
|
"allowed_topics": ["chat"],
|
|
186
186
|
"allowed_goals": [],
|
|
187
|
+
"allowed_tools": ["Read", "Grep", "Glob"],
|
|
187
188
|
"tier_settings": {},
|
|
188
189
|
"disclosure": "minimal",
|
|
189
190
|
"notify": "all",
|
package/native/macos/index.html
CHANGED
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<span class="status-indicator searching"></span>
|
|
63
63
|
Looking for a2a server...
|
|
64
64
|
</p>
|
|
65
|
-
<p class="port-info" id="port-info">Scanning ports:
|
|
65
|
+
<p class="port-info" id="port-info">Scanning ports: 80, 3001, 8080, 8443, 9001</p>
|
|
66
66
|
</div>
|
|
67
67
|
|
|
68
68
|
<div id="status-not-found" style="display:none;">
|
|
@@ -86,10 +86,22 @@
|
|
|
86
86
|
</div>
|
|
87
87
|
|
|
88
88
|
<script>
|
|
89
|
-
const
|
|
89
|
+
const tauriCore = window.__TAURI__ && window.__TAURI__.core;
|
|
90
|
+
const tauriEvents = window.__TAURI__ && window.__TAURI__.event;
|
|
91
|
+
const legacyInvoke = window.__TAURI_INTERNALS__ && window.__TAURI_INTERNALS__.invoke;
|
|
92
|
+
const invoke = (tauriCore && tauriCore.invoke)
|
|
93
|
+
? tauriCore.invoke
|
|
94
|
+
: (legacyInvoke ? ((cmd, args) => legacyInvoke(cmd, args)) : null);
|
|
90
95
|
|
|
91
96
|
async function checkServer() {
|
|
92
97
|
show('status-searching');
|
|
98
|
+
if (!invoke) {
|
|
99
|
+
show('status-not-found');
|
|
100
|
+
const detail = document.getElementById('error-detail');
|
|
101
|
+
detail.textContent = 'Tauri bridge unavailable. Reinstall with `a2a app install --force`.';
|
|
102
|
+
detail.style.display = 'block';
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
93
105
|
try {
|
|
94
106
|
const result = await invoke('discover_server');
|
|
95
107
|
if (result.port) {
|
|
@@ -132,16 +144,16 @@
|
|
|
132
144
|
checkServer();
|
|
133
145
|
|
|
134
146
|
// Listen for server disconnect/reconnect from Tauri backend
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
}
|
|
147
|
+
if (tauriEvents && tauriEvents.listen) {
|
|
148
|
+
tauriEvents.listen('server-status', (event) => {
|
|
149
|
+
const { connected } = event.payload;
|
|
150
|
+
if (!connected) {
|
|
151
|
+
showReconnectionOverlay();
|
|
152
|
+
} else {
|
|
153
|
+
hideReconnectionOverlay();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
145
157
|
|
|
146
158
|
function showReconnectionOverlay() {
|
|
147
159
|
if (document.getElementById('reconnect-overlay')) return;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "a2a-callbook-macos",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"": {
|
|
8
|
+
"name": "a2a-callbook-macos",
|
|
9
|
+
"version": "0.1.0",
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@tauri-apps/cli": "^2.10.0"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"node_modules/@tauri-apps/cli": {
|
|
15
|
+
"version": "2.10.0",
|
|
16
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.0.tgz",
|
|
17
|
+
"integrity": "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==",
|
|
18
|
+
"dev": true,
|
|
19
|
+
"license": "Apache-2.0 OR MIT",
|
|
20
|
+
"bin": {
|
|
21
|
+
"tauri": "tauri.js"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">= 10"
|
|
25
|
+
},
|
|
26
|
+
"funding": {
|
|
27
|
+
"type": "opencollective",
|
|
28
|
+
"url": "https://opencollective.com/tauri"
|
|
29
|
+
},
|
|
30
|
+
"optionalDependencies": {
|
|
31
|
+
"@tauri-apps/cli-darwin-arm64": "2.10.0",
|
|
32
|
+
"@tauri-apps/cli-darwin-x64": "2.10.0",
|
|
33
|
+
"@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0",
|
|
34
|
+
"@tauri-apps/cli-linux-arm64-gnu": "2.10.0",
|
|
35
|
+
"@tauri-apps/cli-linux-arm64-musl": "2.10.0",
|
|
36
|
+
"@tauri-apps/cli-linux-riscv64-gnu": "2.10.0",
|
|
37
|
+
"@tauri-apps/cli-linux-x64-gnu": "2.10.0",
|
|
38
|
+
"@tauri-apps/cli-linux-x64-musl": "2.10.0",
|
|
39
|
+
"@tauri-apps/cli-win32-arm64-msvc": "2.10.0",
|
|
40
|
+
"@tauri-apps/cli-win32-ia32-msvc": "2.10.0",
|
|
41
|
+
"@tauri-apps/cli-win32-x64-msvc": "2.10.0"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
|
45
|
+
"version": "2.10.0",
|
|
46
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.0.tgz",
|
|
47
|
+
"integrity": "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==",
|
|
48
|
+
"cpu": [
|
|
49
|
+
"arm64"
|
|
50
|
+
],
|
|
51
|
+
"dev": true,
|
|
52
|
+
"license": "Apache-2.0 OR MIT",
|
|
53
|
+
"optional": true,
|
|
54
|
+
"os": [
|
|
55
|
+
"darwin"
|
|
56
|
+
],
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">= 10"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"node_modules/@tauri-apps/cli-darwin-x64": {
|
|
62
|
+
"version": "2.10.0",
|
|
63
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz",
|
|
64
|
+
"integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==",
|
|
65
|
+
"cpu": [
|
|
66
|
+
"x64"
|
|
67
|
+
],
|
|
68
|
+
"dev": true,
|
|
69
|
+
"license": "Apache-2.0 OR MIT",
|
|
70
|
+
"optional": true,
|
|
71
|
+
"os": [
|
|
72
|
+
"darwin"
|
|
73
|
+
],
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">= 10"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
|
79
|
+
"version": "2.10.0",
|
|
80
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz",
|
|
81
|
+
"integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==",
|
|
82
|
+
"cpu": [
|
|
83
|
+
"arm"
|
|
84
|
+
],
|
|
85
|
+
"dev": true,
|
|
86
|
+
"license": "Apache-2.0 OR MIT",
|
|
87
|
+
"optional": true,
|
|
88
|
+
"os": [
|
|
89
|
+
"linux"
|
|
90
|
+
],
|
|
91
|
+
"engines": {
|
|
92
|
+
"node": ">= 10"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
|
96
|
+
"version": "2.10.0",
|
|
97
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz",
|
|
98
|
+
"integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==",
|
|
99
|
+
"cpu": [
|
|
100
|
+
"arm64"
|
|
101
|
+
],
|
|
102
|
+
"dev": true,
|
|
103
|
+
"license": "Apache-2.0 OR MIT",
|
|
104
|
+
"optional": true,
|
|
105
|
+
"os": [
|
|
106
|
+
"linux"
|
|
107
|
+
],
|
|
108
|
+
"engines": {
|
|
109
|
+
"node": ">= 10"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
|
113
|
+
"version": "2.10.0",
|
|
114
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz",
|
|
115
|
+
"integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==",
|
|
116
|
+
"cpu": [
|
|
117
|
+
"arm64"
|
|
118
|
+
],
|
|
119
|
+
"dev": true,
|
|
120
|
+
"license": "Apache-2.0 OR MIT",
|
|
121
|
+
"optional": true,
|
|
122
|
+
"os": [
|
|
123
|
+
"linux"
|
|
124
|
+
],
|
|
125
|
+
"engines": {
|
|
126
|
+
"node": ">= 10"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
|
130
|
+
"version": "2.10.0",
|
|
131
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz",
|
|
132
|
+
"integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==",
|
|
133
|
+
"cpu": [
|
|
134
|
+
"riscv64"
|
|
135
|
+
],
|
|
136
|
+
"dev": true,
|
|
137
|
+
"license": "Apache-2.0 OR MIT",
|
|
138
|
+
"optional": true,
|
|
139
|
+
"os": [
|
|
140
|
+
"linux"
|
|
141
|
+
],
|
|
142
|
+
"engines": {
|
|
143
|
+
"node": ">= 10"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
|
147
|
+
"version": "2.10.0",
|
|
148
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz",
|
|
149
|
+
"integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==",
|
|
150
|
+
"cpu": [
|
|
151
|
+
"x64"
|
|
152
|
+
],
|
|
153
|
+
"dev": true,
|
|
154
|
+
"license": "Apache-2.0 OR MIT",
|
|
155
|
+
"optional": true,
|
|
156
|
+
"os": [
|
|
157
|
+
"linux"
|
|
158
|
+
],
|
|
159
|
+
"engines": {
|
|
160
|
+
"node": ">= 10"
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
|
164
|
+
"version": "2.10.0",
|
|
165
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz",
|
|
166
|
+
"integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==",
|
|
167
|
+
"cpu": [
|
|
168
|
+
"x64"
|
|
169
|
+
],
|
|
170
|
+
"dev": true,
|
|
171
|
+
"license": "Apache-2.0 OR MIT",
|
|
172
|
+
"optional": true,
|
|
173
|
+
"os": [
|
|
174
|
+
"linux"
|
|
175
|
+
],
|
|
176
|
+
"engines": {
|
|
177
|
+
"node": ">= 10"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
|
181
|
+
"version": "2.10.0",
|
|
182
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz",
|
|
183
|
+
"integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==",
|
|
184
|
+
"cpu": [
|
|
185
|
+
"arm64"
|
|
186
|
+
],
|
|
187
|
+
"dev": true,
|
|
188
|
+
"license": "Apache-2.0 OR MIT",
|
|
189
|
+
"optional": true,
|
|
190
|
+
"os": [
|
|
191
|
+
"win32"
|
|
192
|
+
],
|
|
193
|
+
"engines": {
|
|
194
|
+
"node": ">= 10"
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
|
198
|
+
"version": "2.10.0",
|
|
199
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz",
|
|
200
|
+
"integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==",
|
|
201
|
+
"cpu": [
|
|
202
|
+
"ia32"
|
|
203
|
+
],
|
|
204
|
+
"dev": true,
|
|
205
|
+
"license": "Apache-2.0 OR MIT",
|
|
206
|
+
"optional": true,
|
|
207
|
+
"os": [
|
|
208
|
+
"win32"
|
|
209
|
+
],
|
|
210
|
+
"engines": {
|
|
211
|
+
"node": ">= 10"
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
|
215
|
+
"version": "2.10.0",
|
|
216
|
+
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz",
|
|
217
|
+
"integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==",
|
|
218
|
+
"cpu": [
|
|
219
|
+
"x64"
|
|
220
|
+
],
|
|
221
|
+
"dev": true,
|
|
222
|
+
"license": "Apache-2.0 OR MIT",
|
|
223
|
+
"optional": true,
|
|
224
|
+
"os": [
|
|
225
|
+
"win32"
|
|
226
|
+
],
|
|
227
|
+
"engines": {
|
|
228
|
+
"node": ">= 10"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
|
|
2
2
|
use std::path::PathBuf;
|
|
3
3
|
use std::time::Duration;
|
|
4
4
|
|
|
5
|
-
const DEFAULT_PORTS: &[u16] = &[
|
|
5
|
+
const DEFAULT_PORTS: &[u16] = &[80, 3001, 8080, 8443, 9001];
|
|
6
6
|
const PROBE_TIMEOUT: Duration = Duration::from_millis(800);
|
|
7
7
|
|
|
8
8
|
#[derive(Debug, Serialize, Deserialize)]
|
|
@@ -14,15 +14,57 @@ pub struct DiscoveryResult {
|
|
|
14
14
|
#[derive(Debug, Deserialize)]
|
|
15
15
|
struct A2AConfig {
|
|
16
16
|
onboarding: Option<OnboardingConfig>,
|
|
17
|
+
agent: Option<AgentConfig>,
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
#[derive(Debug, Deserialize)]
|
|
20
21
|
struct OnboardingConfig {
|
|
22
|
+
#[serde(alias = "serverPort")]
|
|
21
23
|
server_port: Option<u16>,
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
#[derive(Debug, Deserialize)]
|
|
27
|
+
struct AgentConfig {
|
|
28
|
+
hostname: Option<String>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn parse_port_from_hostname(hostname: &str) -> Option<u16> {
|
|
32
|
+
let trimmed = hostname.trim();
|
|
33
|
+
if trimmed.is_empty() {
|
|
34
|
+
return None;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Allow values like:
|
|
38
|
+
// - 149.28.213.47:3007
|
|
39
|
+
// - localhost:3007
|
|
40
|
+
// - http://localhost:3007
|
|
41
|
+
// - [::1]:3007
|
|
42
|
+
let without_scheme = trimmed.split("://").nth(1).unwrap_or(trimmed);
|
|
43
|
+
let host_segment = without_scheme.split('/').next().unwrap_or(without_scheme);
|
|
44
|
+
|
|
45
|
+
if host_segment.starts_with('[') {
|
|
46
|
+
let end = host_segment.find(']')?;
|
|
47
|
+
let remainder = &host_segment[end + 1..];
|
|
48
|
+
let port_str = remainder.strip_prefix(':')?;
|
|
49
|
+
return port_str.parse::<u16>().ok();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Bare IPv6 literals contain multiple colons and no explicit port delimiter.
|
|
53
|
+
if host_segment.matches(':').count() > 1 {
|
|
54
|
+
return None;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let (_, port_str) = host_segment.rsplit_once(':')?;
|
|
58
|
+
if !port_str.chars().all(|ch| ch.is_ascii_digit()) {
|
|
59
|
+
return None;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
port_str.parse::<u16>().ok()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Read likely server ports from ~/.config/openclaw/a2a-config.json.
|
|
66
|
+
/// We prefer explicit onboarding server_port, then agent.hostname port.
|
|
67
|
+
pub fn read_config_ports() -> Vec<u16> {
|
|
26
68
|
let config_dir = std::env::var("A2A_CONFIG_DIR")
|
|
27
69
|
.or_else(|_| std::env::var("OPENCLAW_CONFIG_DIR"))
|
|
28
70
|
.map(PathBuf::from)
|
|
@@ -34,14 +76,36 @@ pub fn read_config_port() -> Option<u16> {
|
|
|
34
76
|
});
|
|
35
77
|
|
|
36
78
|
let config_path = config_dir.join("a2a-config.json");
|
|
37
|
-
let content = std::fs::read_to_string(config_path)
|
|
38
|
-
|
|
39
|
-
|
|
79
|
+
let content = match std::fs::read_to_string(config_path) {
|
|
80
|
+
Ok(data) => data,
|
|
81
|
+
Err(_) => return vec![],
|
|
82
|
+
};
|
|
83
|
+
let config: A2AConfig = match serde_json::from_str(&content) {
|
|
84
|
+
Ok(parsed) => parsed,
|
|
85
|
+
Err(_) => return vec![],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
let mut ports = Vec::new();
|
|
89
|
+
|
|
90
|
+
if let Some(port) = config.onboarding.and_then(|ob| ob.server_port) {
|
|
91
|
+
ports.push(port);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if let Some(port) = config
|
|
95
|
+
.agent
|
|
96
|
+
.and_then(|agent| agent.hostname)
|
|
97
|
+
.and_then(|hostname| parse_port_from_hostname(&hostname))
|
|
98
|
+
{
|
|
99
|
+
if !ports.contains(&port) {
|
|
100
|
+
ports.push(port);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
ports
|
|
40
105
|
}
|
|
41
106
|
|
|
42
107
|
/// Probe a single port — returns true if a2a server responds
|
|
43
108
|
async fn probe_port(port: u16) -> bool {
|
|
44
|
-
let url = format!("http://127.0.0.1:{}/api/a2a/ping", port);
|
|
45
109
|
let client = reqwest::Client::builder()
|
|
46
110
|
.timeout(PROBE_TIMEOUT)
|
|
47
111
|
.build();
|
|
@@ -51,16 +115,36 @@ async fn probe_port(port: u16) -> bool {
|
|
|
51
115
|
Err(_) => return false,
|
|
52
116
|
};
|
|
53
117
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
118
|
+
for host in ["127.0.0.1", "localhost"] {
|
|
119
|
+
let url = format!("http://{}:{}/api/a2a/ping", host, port);
|
|
120
|
+
let ok = match client.get(&url).send().await {
|
|
121
|
+
Ok(resp) => {
|
|
122
|
+
if !resp.status().is_success() {
|
|
123
|
+
false
|
|
124
|
+
} else {
|
|
125
|
+
match resp.text().await {
|
|
126
|
+
Ok(body) => body.contains("\"pong\":true"),
|
|
127
|
+
Err(_) => false,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
Err(_) => false,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
if ok {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
57
137
|
}
|
|
138
|
+
|
|
139
|
+
false
|
|
58
140
|
}
|
|
59
141
|
|
|
60
142
|
/// Discover the running a2a server
|
|
61
143
|
pub async fn discover_server() -> DiscoveryResult {
|
|
62
|
-
|
|
63
|
-
|
|
144
|
+
let config_ports = read_config_ports();
|
|
145
|
+
|
|
146
|
+
// 1. Try config-derived ports first
|
|
147
|
+
for &port in &config_ports {
|
|
64
148
|
if probe_port(port).await {
|
|
65
149
|
return DiscoveryResult {
|
|
66
150
|
port: Some(port),
|
|
@@ -69,8 +153,11 @@ pub async fn discover_server() -> DiscoveryResult {
|
|
|
69
153
|
}
|
|
70
154
|
}
|
|
71
155
|
|
|
72
|
-
// 2. Scan default ports
|
|
156
|
+
// 2. Scan default ports (skip those already checked from config)
|
|
73
157
|
for &port in DEFAULT_PORTS {
|
|
158
|
+
if config_ports.contains(&port) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
74
161
|
if probe_port(port).await {
|
|
75
162
|
return DiscoveryResult {
|
|
76
163
|
port: Some(port),
|