a2acalling 0.6.52 → 0.6.54

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.
@@ -0,0 +1,26 @@
1
+ ---
2
+ description: Call another A2A agent — starts a multi-turn conversation
3
+ allowed-tools: [Bash, Read]
4
+ argument-hint: <contact-or-url> <message>
5
+ ---
6
+
7
+ Call an A2A agent. This starts a multi-turn agent-to-agent conversation.
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ /a2a-call Alice "Hello! My owner wants to discuss the project."
13
+ /a2a-call a2a://host.com/fed_abc123 "Reaching out about collaboration"
14
+ ```
15
+
16
+ ## Instructions
17
+
18
+ Run the following command with the user's arguments:
19
+
20
+ ```bash
21
+ a2a call $ARGUMENTS
22
+ ```
23
+
24
+ If the call succeeds, summarize the conversation outcome for the user.
25
+ If it fails with "not onboarded", tell the user to run `/a2a-setup` first.
26
+ If it fails with "contact not found", suggest `/a2a-contacts` to see available contacts.
@@ -0,0 +1,31 @@
1
+ ---
2
+ description: List A2A contacts — agents you can call or who can call you
3
+ allowed-tools: [Bash]
4
+ argument-hint: [add|show|ping|rm] [args...]
5
+ ---
6
+
7
+ Manage your A2A contact list — see who you can call and who has access to you.
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ /a2a-contacts # list all contacts
13
+ /a2a-contacts add a2a://host/fed_xxx Alice # add contact from invite URL
14
+ /a2a-contacts show Alice # show contact details
15
+ /a2a-contacts ping Alice # check if contact is online
16
+ /a2a-contacts rm Alice # remove a contact
17
+ ```
18
+
19
+ ## Instructions
20
+
21
+ Run the appropriate command based on user input:
22
+
23
+ - No arguments: `a2a contacts`
24
+ - `add`: `a2a contacts add $ARGUMENTS`
25
+ - `show`: `a2a contacts show $ARGUMENTS`
26
+ - `ping`: `a2a contacts ping $ARGUMENTS`
27
+ - `rm`: `a2a contacts rm $ARGUMENTS`
28
+
29
+ If the user just wants to see their contacts, also run `a2a list` to show active tokens (outbound invites).
30
+
31
+ Format the output clearly: contact name, owner, status (online/offline), permission tier, last seen.
@@ -0,0 +1,33 @@
1
+ ---
2
+ description: Create an A2A invite token to share with another agent
3
+ allowed-tools: [Bash]
4
+ argument-hint: [name] [--tier public|friends|family] [--expires 7d]
5
+ ---
6
+
7
+ Create an A2A federation token and display the invite URL for sharing.
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ /a2a-invite Alice --tier friends --expires 7d
13
+ /a2a-invite "Bob's Agent" --tier public
14
+ /a2a-invite # interactive — uses defaults
15
+ ```
16
+
17
+ ## Instructions
18
+
19
+ Parse the user's arguments and run:
20
+
21
+ ```bash
22
+ a2a create $ARGUMENTS
23
+ ```
24
+
25
+ If no arguments provided, run `a2a create` with no flags (interactive mode).
26
+
27
+ After success, display the invite URL prominently and explain:
28
+ 1. The URL format: `a2a://<hostname>/<token>`
29
+ 2. Share this URL with the other agent's owner
30
+ 3. The token tier controls what the caller can access (public = read-only, friends = calendar/email/search read, family = full access)
31
+ 4. The token expires per the `--expires` flag (default: never)
32
+
33
+ Also suggest: "Run `/a2a-contacts` to see who already has access."
@@ -0,0 +1,30 @@
1
+ ---
2
+ description: Set up A2A Calling — onboard, start server, configure agent
3
+ allowed-tools: [Bash, Read, Write]
4
+ argument-hint: [--force]
5
+ ---
6
+
7
+ Set up or reset your A2A Calling installation. Runs onboarding, starts the server, and configures your agent.
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ /a2a-setup # first-time setup or resume incomplete onboarding
13
+ /a2a-setup --force # reset and re-run from scratch
14
+ ```
15
+
16
+ ## Instructions
17
+
18
+ 1. Check if already onboarded: `a2a config --show`
19
+ 2. If not onboarded (or `--force`): run `a2a quickstart $ARGUMENTS`
20
+ 3. If already onboarded but server not running: run `a2a server` in background
21
+ 4. After setup, show the status with `a2a config --show` and `a2a list`
22
+
23
+ The quickstart flow will:
24
+ - Detect an available port
25
+ - Start the A2A server
26
+ - Detect the hostname
27
+ - Prompt for disclosure topics (what your agent discusses)
28
+ - Save the configuration
29
+
30
+ If running non-interactively, quickstart auto-accepts defaults.
@@ -0,0 +1,24 @@
1
+ ---
2
+ description: Check A2A server status, active conversations, and agent health
3
+ allowed-tools: [Bash, Read]
4
+ ---
5
+
6
+ Check the health of your A2A installation — server running, conversations active, contacts online.
7
+
8
+ ## Instructions
9
+
10
+ Run these commands and compile a status report:
11
+
12
+ 1. **Config:** `a2a config --show`
13
+ 2. **Active tokens:** `a2a list`
14
+ 3. **Contacts:** `a2a contacts`
15
+ 4. **Recent conversations:** `a2a conversations --limit 5`
16
+
17
+ Present a clear status dashboard:
18
+ - Server: running/stopped (with port and hostname)
19
+ - Tokens: N active, N expired/revoked
20
+ - Contacts: N total
21
+ - Recent calls: last 5 conversations with status
22
+
23
+ If the server is not running, suggest `/a2a-setup` to start it.
24
+ If not onboarded, suggest `/a2a-setup` for first-time setup.
@@ -0,0 +1,154 @@
1
+ # A2A Calling — Installed Package Context
2
+
3
+ This file is automatically installed by the `a2acalling` package to give Claude Code
4
+ immediate context after `npm install`. It describes what A2A Calling is, how to get
5
+ started, and the full CLI reference — so you never need to grep source code.
6
+
7
+ ## Getting Started
8
+
9
+ After installing `a2acalling`, the FIRST thing to do is run onboarding:
10
+
11
+ ```bash
12
+ a2a quickstart
13
+ ```
14
+
15
+ This is the single entry point for all setup. It will:
16
+ 1. Detect an available port (prefers 80, falls back to 3001+)
17
+ 2. Start the A2A server
18
+ 3. Detect the hostname
19
+ 4. Prompt for disclosure topics (what your agent discusses with callers)
20
+ 5. On macOS, offer to install the native Callbook app
21
+ 6. Save configuration to `~/.config/openclaw/a2a-config.json`
22
+
23
+ If onboarding was already completed, re-run with `--force`:
24
+
25
+ ```bash
26
+ a2a quickstart --force
27
+ ```
28
+
29
+ ## Native macOS App (A2A Callbook)
30
+
31
+ On macOS, a native desktop app is available. It provides a dashboard GUI for managing
32
+ calls, contacts, tokens, and logs.
33
+
34
+ ```bash
35
+ a2a app install # Download and install from GitHub releases
36
+ a2a app status # Check if installed, show version and path
37
+ a2a app uninstall # Remove from ~/Applications
38
+ ```
39
+
40
+ After install, the app lives at `~/Applications/A2A Callbook.app`. You can also
41
+ open the dashboard in a browser:
42
+
43
+ ```bash
44
+ a2a gui # Open dashboard in browser (or native app if installed)
45
+ a2a gui --tab logs # Open specific tab: contacts|calls|logs|settings|invites
46
+ ```
47
+
48
+ ## Full CLI Reference
49
+
50
+ ### Token Management
51
+
52
+ ```bash
53
+ a2a create --name "AgentName" --owner "OwnerName" --expires 7d --permissions friends
54
+ # Options:
55
+ # --name, -n Token/agent name
56
+ # --owner, -o Owner name
57
+ # --expires, -e 1h, 1d, 7d, 30d, never (default: 1d)
58
+ # --permissions, -p public, friends, family (default: public)
59
+ # --disclosure, -d public, minimal, none (default: minimal)
60
+ # --notify all, summary, none (default: all)
61
+ # --max-calls Maximum invocations (default: 100)
62
+ # --topics Custom topics (comma-separated)
63
+ # --tools Custom tool allowlist (comma-separated)
64
+ # --link, -l Auto-link to contact name
65
+
66
+ a2a list # List active tokens
67
+ a2a revoke <token_id> # Revoke a token
68
+ ```
69
+
70
+ ### Contacts
71
+
72
+ ```bash
73
+ a2a contacts # List all contacts (shows permission badges)
74
+ a2a contacts add <url> --name "Alice" --owner "Alice Chen"
75
+ a2a contacts show <name> # Show contact details + linked token
76
+ a2a contacts edit <name> # Edit contact metadata
77
+ a2a contacts link <name> <token_id> # Link a token to a contact
78
+ a2a contacts ping <name> # Ping contact, update status
79
+ a2a contacts rm <name> # Remove contact
80
+ ```
81
+
82
+ ### Calling
83
+
84
+ ```bash
85
+ a2a call <contact|url> "<message>" # Multi-turn call (default: 8-25 turns)
86
+ a2a call Alice "Hello!" # Call by contact name
87
+ a2a call a2a://host/fed_xxx "Hi" # Call by invite URL
88
+ a2a call Alice "Quick q" --single # One-shot (single turn)
89
+ # --min-turns N Minimum turns before close (default: 8)
90
+ # --max-turns N Maximum turns (default: 25)
91
+
92
+ a2a ping <url> # Check if agent is reachable
93
+ a2a status <url> # Get remote A2A status
94
+ ```
95
+
96
+ ### Conversations
97
+
98
+ ```bash
99
+ a2a conversations # List all conversations
100
+ # --contact Filter by contact
101
+ # --status Filter: active, concluded, timeout
102
+ # --limit Max results (default: 20)
103
+ a2a conversations show <id> # Show conversation with messages
104
+ a2a conversations end <id> # End and summarize conversation
105
+ ```
106
+
107
+ ### Server & Setup
108
+
109
+ ```bash
110
+ a2a quickstart # First-time setup (port, hostname, disclosure)
111
+ # --port, -p Preferred port (default: 80, fallback: 3001+)
112
+ # --force Reset and re-run from scratch
113
+ # --submit '<json>' Submit disclosure JSON
114
+
115
+ a2a server --port 3001 # Start server manually
116
+ a2a config --show # Show current configuration
117
+ a2a status # Show local server status
118
+ a2a gui # Open dashboard GUI
119
+ a2a app install # Install native macOS app
120
+ a2a setup # Auto setup (gateway-aware)
121
+ ```
122
+
123
+ ### Maintenance
124
+
125
+ ```bash
126
+ a2a update # Update to latest version
127
+ a2a update --check # Check for updates without installing
128
+ a2a skills # Install Claude Code + Codex skill files
129
+ a2a uninstall --force # Stop server and remove config/DB
130
+ a2a version # Show installed version
131
+ ```
132
+
133
+ ## Permission Tiers
134
+
135
+ | Tier | Default Capabilities |
136
+ |------|---------------------|
137
+ | `public` | `context-read` |
138
+ | `friends` | `context-read`, `calendar.read`, `email.read`, `search` |
139
+ | `family` | `context-read`, `calendar`, `email`, `search`, `tools`, `memory` |
140
+
141
+ ## Disclosure Levels
142
+
143
+ | Level | Behavior |
144
+ |-------|----------|
145
+ | `public` | Agent shares freely within tier boundaries |
146
+ | `minimal` | Direct answers only, no volunteered context |
147
+ | `none` | Confirms capability, provides no information |
148
+
149
+ ## Key Paths
150
+
151
+ - Config: `~/.config/openclaw/a2a-config.json`
152
+ - Disclosure: `~/.config/openclaw/a2a-disclosure.json`
153
+ - Native app: `~/Applications/A2A Callbook.app` (macOS only)
154
+ - Dashboard: `http://127.0.0.1:<port>/dashboard/`
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
- config.setTier('public', {
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
- config.setTier('friends', {
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
- config.setTier('family', {
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 even when current version is present
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",
@@ -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: 3001, 80, 8080, 8443, 9001</p>
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 { invoke } = window.__TAURI__.core;
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
- const { listen } = window.__TAURI__.event;
136
-
137
- listen('server-status', (event) => {
138
- const { connected, port } = event.payload;
139
- if (!connected) {
140
- showReconnectionOverlay();
141
- } else {
142
- hideReconnectionOverlay();
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;