a2acalling 0.6.74 → 0.6.75

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.
Files changed (118) hide show
  1. package/.a2a-manifest.json +2 -2
  2. package/.c8rc.json +16 -0
  3. package/.node-version +1 -0
  4. package/.serena/project.yml +126 -0
  5. package/ARCHITECTURE.md +11 -0
  6. package/CONVENTIONS.md +9 -0
  7. package/coverage/base.css +224 -0
  8. package/coverage/block-navigation.js +87 -0
  9. package/coverage/favicon.png +0 -0
  10. package/coverage/index.html +146 -0
  11. package/coverage/prettify.css +1 -0
  12. package/coverage/prettify.js +2 -0
  13. package/coverage/sort-arrow-sprite.png +0 -0
  14. package/coverage/sorter.js +210 -0
  15. package/coverage/src/index.html +131 -0
  16. package/coverage/src/index.js.html +313 -0
  17. package/coverage/src/lib/agent-card.js.html +418 -0
  18. package/coverage/src/lib/call-monitor.js.html +700 -0
  19. package/coverage/src/lib/callbook.js.html +1183 -0
  20. package/coverage/src/lib/claude-subagent.js.html +2173 -0
  21. package/coverage/src/lib/client.js.html +2134 -0
  22. package/coverage/src/lib/config.js.html +1525 -0
  23. package/coverage/src/lib/conversation-driver.js.html +1909 -0
  24. package/coverage/src/lib/conversations.js.html +2575 -0
  25. package/coverage/src/lib/crypto.js.html +424 -0
  26. package/coverage/src/lib/dashboard-events.js.html +724 -0
  27. package/coverage/src/lib/disclosure.js.html +2461 -0
  28. package/coverage/src/lib/external-ip.js.html +718 -0
  29. package/coverage/src/lib/index.html +506 -0
  30. package/coverage/src/lib/invite-host.js.html +754 -0
  31. package/coverage/src/lib/local-request.js.html +292 -0
  32. package/coverage/src/lib/logger.js.html +2116 -0
  33. package/coverage/src/lib/openclaw-integration.js.html +1102 -0
  34. package/coverage/src/lib/pid-file.js.html +394 -0
  35. package/coverage/src/lib/port-scanner.js.html +334 -0
  36. package/coverage/src/lib/prompt-template.js.html +1150 -0
  37. package/coverage/src/lib/runtime-adapter.js.html +2188 -0
  38. package/coverage/src/lib/summarizer.js.html +553 -0
  39. package/coverage/src/lib/summary-formatter.js.html +589 -0
  40. package/coverage/src/lib/summary-prompt.js.html +694 -0
  41. package/coverage/src/lib/tokens.js.html +2689 -0
  42. package/coverage/src/lib/turn-timeout.js.html +241 -0
  43. package/coverage/src/lib/update-checker.js.html +364 -0
  44. package/coverage/src/lib/update-manager.js.html +1024 -0
  45. package/coverage/src/routes/a2a.js.html +3724 -0
  46. package/coverage/src/routes/callbook.js.html +511 -0
  47. package/coverage/src/routes/dashboard.js.html +4819 -0
  48. package/coverage/src/routes/index.html +146 -0
  49. package/coverage/src/server.js.html +3622 -0
  50. package/coverage/tmp/coverage-1605378-1772576706365-0.json +1 -0
  51. package/coverage/tmp/coverage-1605384-1772576607459-0.json +1 -0
  52. package/coverage/tmp/coverage-1605410-1772576631155-0.json +1 -0
  53. package/coverage/tmp/coverage-1606942-1772576636869-0.json +1 -0
  54. package/coverage/tmp/coverage-1607004-1772576637454-0.json +1 -0
  55. package/coverage/tmp/coverage-1607044-1772576637876-0.json +1 -0
  56. package/coverage/tmp/coverage-1607096-1772576638356-0.json +1 -0
  57. package/coverage/tmp/coverage-1607145-1772576638777-0.json +1 -0
  58. package/coverage/tmp/coverage-1607201-1772576639277-0.json +1 -0
  59. package/coverage/tmp/coverage-1607247-1772576639755-0.json +1 -0
  60. package/coverage/tmp/coverage-1607317-1772576640083-0.json +1 -0
  61. package/coverage/tmp/coverage-1607381-1772576640465-0.json +1 -0
  62. package/coverage/tmp/coverage-1607446-1772576640868-0.json +1 -0
  63. package/coverage/tmp/coverage-1607501-1772576641662-0.json +1 -0
  64. package/coverage/tmp/coverage-1607534-1772576641565-0.json +1 -0
  65. package/coverage/tmp/coverage-1607627-1772576641871-0.json +1 -0
  66. package/coverage/tmp/coverage-1607665-1772576642172-0.json +1 -0
  67. package/coverage/tmp/coverage-1607714-1772576642577-0.json +1 -0
  68. package/coverage/tmp/coverage-1607788-1772576643466-0.json +1 -0
  69. package/coverage/tmp/coverage-1607924-1772576644678-0.json +1 -0
  70. package/coverage/tmp/coverage-1607978-1772576645154-0.json +1 -0
  71. package/coverage/tmp/coverage-1608035-1772576645564-0.json +1 -0
  72. package/coverage/tmp/coverage-1608106-1772576645967-0.json +1 -0
  73. package/coverage/tmp/coverage-1608179-1772576648656-0.json +1 -0
  74. package/coverage/tmp/coverage-1608196-1772576647367-0.json +1 -0
  75. package/coverage/tmp/coverage-1608217-1772576648557-0.json +1 -0
  76. package/coverage/tmp/coverage-1608256-1772576651378-0.json +1 -0
  77. package/coverage/tmp/coverage-1608265-1772576650058-0.json +1 -0
  78. package/coverage/tmp/coverage-1608289-1772576651358-0.json +1 -0
  79. package/coverage/tmp/coverage-1608591-1772576660465-0.json +1 -0
  80. package/coverage/tmp/coverage-1608648-1772576659272-0.json +1 -0
  81. package/coverage/tmp/coverage-1608665-1772576660374-0.json +1 -0
  82. package/coverage/tmp/coverage-1608677-1772576661268-0.json +1 -0
  83. package/coverage/tmp/coverage-1608684-1772576663968-0.json +1 -0
  84. package/coverage/tmp/coverage-1608692-1772576662575-0.json +1 -0
  85. package/coverage/tmp/coverage-1608701-1772576663873-0.json +1 -0
  86. package/coverage/tmp/coverage-1608718-1772576666674-0.json +1 -0
  87. package/coverage/tmp/coverage-1608725-1772576665463-0.json +1 -0
  88. package/coverage/tmp/coverage-1608738-1772576666577-0.json +1 -0
  89. package/coverage/tmp/coverage-1608753-1772576669664-0.json +1 -0
  90. package/coverage/tmp/coverage-1608763-1772576668275-0.json +1 -0
  91. package/coverage/tmp/coverage-1608771-1772576669563-0.json +1 -0
  92. package/coverage/tmp/coverage-1608828-1772576676574-0.json +1 -0
  93. package/coverage/tmp/coverage-1609244-1772576675272-0.json +1 -0
  94. package/coverage/tmp/coverage-1609342-1772576676478-0.json +1 -0
  95. package/coverage/tmp/coverage-1609450-1772576686954-0.json +1 -0
  96. package/coverage/tmp/coverage-1609841-1772576685466-0.json +1 -0
  97. package/coverage/tmp/coverage-1609925-1772576686855-0.json +1 -0
  98. package/coverage/tmp/coverage-1610399-1772576692469-0.json +1 -0
  99. package/coverage/tmp/coverage-1611283-1772576703062-0.json +1 -0
  100. package/coverage/tmp/coverage-1611294-1772576703755-0.json +1 -0
  101. package/docs/plans/2026-03-03-a2a-91-macos-packaging-plan.md +144 -0
  102. package/docs/signing-setup.md +49 -0
  103. package/native/macos/certs/appldevcert.cer +0 -0
  104. package/native/macos/src-tauri/binaries/.gitkeep +0 -0
  105. package/native/macos/src-tauri/capabilities/default.json +11 -1
  106. package/native/macos/src-tauri/entitlements.plist +14 -0
  107. package/native/macos/src-tauri/src/discovery.rs +14 -3
  108. package/native/macos/src-tauri/src/health.rs +4 -0
  109. package/native/macos/src-tauri/src/lib.rs +52 -11
  110. package/native/macos/src-tauri/src/server.rs +262 -26
  111. package/native/macos/src-tauri/tauri.conf.json +13 -4
  112. package/package.json +7 -2
  113. package/pkg.config.json +14 -0
  114. package/scripts/build-standalone.sh +106 -0
  115. package/scripts/smoke-test-standalone.sh +101 -0
  116. package/scripts/sync-version.sh +28 -0
  117. package/scripts/verify-app-bundle.sh +34 -0
  118. package/.maestro/inbox/release-workflow-spam.md +0 -25
@@ -0,0 +1,144 @@
1
+ # A2A-91: macOS App Packaging Plan
2
+
3
+ ## Executive Summary
4
+
5
+ A2A Callbook currently exists as a Tauri v2 wrapper (`native/macos/`) that discovers a separately-running Node.js server via port scanning. To distribute a self-contained macOS app, we need to bundle the Node.js Express server as a Tauri sidecar binary, add code signing + notarization, and set up CI/CD for automated builds.
6
+
7
+ **Recommended approach**: Tauri v2 + `@yao-pkg/pkg` sidecar (pragmatic today), with migration path to Node.js SEA as it matures.
8
+
9
+ **App Store verdict**: Not viable. Node.js uses V8 JIT which conflicts with Apple's sandbox requirements. Direct DMG distribution with Developer ID signing + notarization is the correct path.
10
+
11
+ ## Current State
12
+
13
+ | Aspect | Status |
14
+ |--------|--------|
15
+ | Tauri v2 shell | Working (v0.1.0) — menus, tray, deep links, SSE notifications |
16
+ | Server integration | External discovery only (`which a2a` + port scan) |
17
+ | Code signing | Not configured |
18
+ | Notarization | Not configured |
19
+ | Distribution | Unsigned `.app` + `.dmg` via GitHub Releases |
20
+ | CI/CD | `tauri-build.yml` exists but no signing secrets |
21
+
22
+ ## Architecture Decision
23
+
24
+ ### Why Tauri + Sidecar (not Electron)
25
+
26
+ | Factor | Tauri + Sidecar | Electron |
27
+ |--------|----------------|----------|
28
+ | App size | ~95MB | ~150MB |
29
+ | Memory | ~80MB | ~300MB |
30
+ | Existing code | Keep all Rust code | Rewrite from scratch |
31
+ | Native feel | WebKit (system) | Chromium (bundled) |
32
+ | Risk | Medium (new sidecar pipeline) | High (full rewrite) |
33
+
34
+ Tauri is already working. The incremental cost of adding a sidecar is far lower than an Electron migration.
35
+
36
+ ### Why @yao-pkg/pkg (not Node.js SEA today)
37
+
38
+ - **pkg** auto-extracts native `.node` addons (better-sqlite3) — SEA requires manual `process.dlopen()` shims
39
+ - Tauri's official sidecar-nodejs guide recommends pkg
40
+ - Migration to Node.js SEA is straightforward once `--build-sea` stabilizes (Node 25.5+)
41
+
42
+ ### Why not App Store
43
+
44
+ Node.js embeds V8 JIT. Apple's Guideline 2.5.2 restricts JIT in sandboxed apps. The `com.apple.security.cs.allow-jit` entitlement exists but requires justification and has no precedent for Tauri+Node.js apps. Direct DMG distribution with notarization is the proven path.
45
+
46
+ ## Implementation Phases
47
+
48
+ ### Phase 1: Standalone Node.js Binary (Core)
49
+
50
+ Create a build pipeline that compiles the Express server + better-sqlite3 into a single executable using `@yao-pkg/pkg`.
51
+
52
+ **Key challenges:**
53
+ - better-sqlite3 native addon must be compiled for the exact Node.js version pkg embeds
54
+ - Universal binary support (aarch64 + x86_64) requires two pkg builds + `lipo`
55
+ - The standalone binary must respect `A2A_CONFIG_DIR` and all existing env vars
56
+
57
+ **Deliverables:**
58
+ - `scripts/build-standalone.sh` — compiles server into standalone binary
59
+ - Verification: binary starts, serves dashboard, handles calls in `A2A_RUNTIME=test`
60
+
61
+ ### Phase 2: Tauri Sidecar Integration
62
+
63
+ Replace the current `which a2a` server discovery in `server.rs` with Tauri's sidecar API.
64
+
65
+ **Changes:**
66
+ - Place pkg output in `src-tauri/binaries/a2a-server-{target-triple}`
67
+ - Add `externalBin` to `tauri.conf.json`
68
+ - Rewrite `server.rs` to use `app.shell().sidecar("a2a-server")` instead of `which`
69
+ - Update `discovery.rs` to check sidecar health before falling back to external
70
+ - Add graceful shutdown: send SIGTERM to sidecar on app quit
71
+
72
+ ### Phase 3: Code Signing + Notarization
73
+
74
+ Set up Apple Developer ID signing and notarization for Gatekeeper compliance.
75
+
76
+ **Requirements:**
77
+ - Apple Developer Program membership ($99/year)
78
+ - Developer ID Application certificate
79
+ - App-specific password or App Store Connect API key
80
+
81
+ **Changes:**
82
+ - Add signing identity to `tauri.conf.json` `bundle.macOS.signingIdentity`
83
+ - Create entitlements plist (network access, JIT for V8)
84
+ - Add GitHub Actions secrets for signing credentials
85
+ - Notarization via `xcrun notarytool` (Tauri handles this automatically when env vars are set)
86
+
87
+ ### Phase 4: CI/CD Pipeline
88
+
89
+ Automate the full build → sign → notarize → release pipeline.
90
+
91
+ **Changes:**
92
+ - Update `tauri-build.yml` to:
93
+ 1. Build standalone Node.js binary (pkg)
94
+ 2. Place in `src-tauri/binaries/`
95
+ 3. Build universal Tauri app (`--target universal-apple-darwin`)
96
+ 4. Sign + notarize
97
+ 5. Upload `.dmg` to GitHub Release
98
+ - Add version sync between `package.json` and `tauri.conf.json`
99
+
100
+ ### Phase 5: Server Lifecycle Management
101
+
102
+ The self-contained app should fully manage the A2A server lifecycle.
103
+
104
+ **Changes:**
105
+ - Start sidecar on app launch, stop on quit
106
+ - Restart on crash (with backoff)
107
+ - Port allocation: pick available port, pass to sidecar via args
108
+ - Show server status in tray icon (connected/disconnected already exists)
109
+ - Log sidecar stdout/stderr for debugging
110
+
111
+ ### Phase 6: Testing Infrastructure
112
+
113
+ Add automated testing for the packaging pipeline.
114
+
115
+ **Changes:**
116
+ - Smoke test: build standalone binary, verify it starts and responds to `/api/a2a/status`
117
+ - Integration test: build full `.app`, verify sidecar spawns correctly
118
+ - CI matrix: test on macOS 12 (Monterey), 13 (Ventura), 14 (Sonoma), 15 (Sequoia)
119
+
120
+ ## Resource Estimates
121
+
122
+ | Item | Cost | Recurrence |
123
+ |------|------|------------|
124
+ | Apple Developer Program | $99 | Annual |
125
+ | GitHub Actions macOS runner | ~$0.08/min | Per build |
126
+ | Developer time (Phases 1-6) | ~40-60 hours | One-time |
127
+
128
+ ## Risk Register
129
+
130
+ | Risk | Likelihood | Impact | Mitigation |
131
+ |------|-----------|--------|------------|
132
+ | better-sqlite3 version mismatch with pkg | Medium | High | Pin Node.js version, test in CI |
133
+ | Apple notarization rejection | Low | High | Entitlements plist, hardened runtime |
134
+ | pkg deprecation (community fork) | Medium | Medium | Migration path to Node.js SEA ready |
135
+ | V8 JIT blocked by future macOS | Low | High | Monitor Apple policy; sql.js (Wasm) fallback for SQLite |
136
+
137
+ ## Migration Path to Node.js SEA
138
+
139
+ When Node.js SEA `--build-sea` reaches stability (currently 1.1 as of Node 25.5):
140
+ 1. Replace `@yao-pkg/pkg` with `node --build-sea`
141
+ 2. Add esbuild bundling step (pkg handles this; SEA needs it explicit)
142
+ 3. Add `process.dlopen()` shim for better-sqlite3
143
+ 4. Update `scripts/build-standalone.sh`
144
+ 5. No Tauri changes needed — sidecar interface is the same
@@ -0,0 +1,49 @@
1
+ # macOS Signing & Notarization Setup (A2A-94)
2
+
3
+ This document describes the required Apple credentials for signed, notarized macOS release artifacts.
4
+
5
+ ## Prerequisites
6
+
7
+ - Apple Developer Program membership (active)
8
+ - Developer ID Application certificate for your team
9
+ - Access to GitHub repository secrets for this project
10
+
11
+ ## 1) Create a Developer ID Application certificate
12
+
13
+ 1. Open Apple Developer portal → Certificates, IDs & Profiles.
14
+ 2. Create a new **Developer ID Application** certificate.
15
+ 3. Export the certificate + private key from Keychain as `.p12`.
16
+ 4. Protect the `.p12` export with a strong password.
17
+
18
+ ## 2) Create GitHub Actions secrets
19
+
20
+ Upload the following repository secrets:
21
+
22
+ - `APPLE_CERTIFICATE`: base64-encoded `.p12` archive
23
+ - `APPLE_CERTIFICATE_PASSWORD`: password used for `.p12` export
24
+ - `APPLE_SIGNING_IDENTITY`: exact certificate identity string
25
+ - `APPLE_ID`: Apple account email used for notarization
26
+ - `APPLE_PASSWORD`: app-specific password for notarization
27
+ - `APPLE_TEAM_ID`: Apple Developer Team ID
28
+
29
+ Example identity format:
30
+
31
+ ```text
32
+ Developer ID Application: Your Name (TEAMID)
33
+ ```
34
+
35
+ ## 3) Validate the release output
36
+
37
+ The macOS build workflow verifies all three checks before publishing artifacts:
38
+
39
+ - `codesign --verify --deep --strict` on the `.app`
40
+ - `spctl --assess --type execute` on the `.app`
41
+ - `xcrun stapler validate` on the `.dmg`
42
+
43
+ A release is considered valid only when all checks pass.
44
+
45
+ ## Security notes
46
+
47
+ - Never commit `.p12`, `.cer`, or private key material.
48
+ - Keep certificate exports in a local, encrypted store only.
49
+ - Rotate Apple app-specific passwords if they are exposed.
File without changes
@@ -11,6 +11,16 @@
11
11
  "notification:allow-request-permission",
12
12
  "notification:allow-notify",
13
13
  "deep-link:default",
14
- "window-state:default"
14
+ "window-state:default",
15
+ {
16
+ "identifier": "shell:allow-execute",
17
+ "allow": [
18
+ {
19
+ "name": "binaries/a2a-server",
20
+ "sidecar": true,
21
+ "args": true
22
+ }
23
+ ]
24
+ }
15
25
  ]
16
26
  }
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>com.apple.security.cs.allow-jit</key>
6
+ <true/>
7
+ <key>com.apple.security.network.client</key>
8
+ <true/>
9
+ <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
10
+ <true/>
11
+ <key>com.apple.security.cs.disable-library-validation</key>
12
+ <true/>
13
+ </dict>
14
+ </plist>
@@ -139,11 +139,22 @@ async fn probe_port(port: u16) -> bool {
139
139
  false
140
140
  }
141
141
 
142
- /// Discover the running a2a server
143
- pub async fn discover_server() -> DiscoveryResult {
142
+ /// Discover the running a2a server.
143
+ /// If `sidecar_port` is provided, check it first before config/scan fallback.
144
+ pub async fn discover_server(sidecar_port: Option<u16>) -> DiscoveryResult {
145
+ // 0. Check sidecar port first (highest priority)
146
+ if let Some(port) = sidecar_port {
147
+ if probe_port(port).await {
148
+ return DiscoveryResult {
149
+ port: Some(port),
150
+ source: "sidecar".to_string(),
151
+ };
152
+ }
153
+ }
154
+
144
155
  let config_ports = read_config_ports();
145
156
 
146
- // 1. Try config-derived ports first
157
+ // 1. Try config-derived ports
147
158
  for &port in &config_ports {
148
159
  if probe_port(port).await {
149
160
  return DiscoveryResult {
@@ -18,6 +18,10 @@ pub fn set_connected(port: u16) {
18
18
  CONNECTED.store(true, Ordering::Relaxed);
19
19
  }
20
20
 
21
+ pub fn set_disconnected() {
22
+ CONNECTED.store(false, Ordering::Relaxed);
23
+ }
24
+
21
25
  /// Start background health check loop — emits "server-status" events
22
26
  pub fn start_health_monitor(app: tauri::AppHandle) {
23
27
  tauri::async_runtime::spawn(async move {
@@ -9,8 +9,13 @@ mod notifications;
9
9
  mod server;
10
10
 
11
11
  #[tauri::command]
12
- async fn discover_server() -> Result<discovery::DiscoveryResult, String> {
13
- let result = discovery::discover_server().await;
12
+ async fn discover_server(app: tauri::AppHandle) -> Result<discovery::DiscoveryResult, String> {
13
+ let sidecar_port = app
14
+ .try_state::<server::SidecarState>()
15
+ .map(|state| state.port())
16
+ .filter(|&p| p > 0);
17
+
18
+ let result = discovery::discover_server(sidecar_port).await;
14
19
  if let Some(port) = result.port {
15
20
  health::set_connected(port);
16
21
  }
@@ -18,8 +23,19 @@ async fn discover_server() -> Result<discovery::DiscoveryResult, String> {
18
23
  }
19
24
 
20
25
  #[tauri::command]
21
- fn start_server() -> Result<server::StartResult, String> {
22
- Ok(server::start_server())
26
+ fn start_server(app: tauri::AppHandle) -> Result<server::StartResult, String> {
27
+ Ok(server::start_sidecar(&app))
28
+ }
29
+
30
+ #[tauri::command]
31
+ fn restart_server(app: tauri::AppHandle) -> Result<server::StartResult, String> {
32
+ let result = server::restart_sidecar(&app);
33
+ if result.success {
34
+ if let Some(port) = result.port {
35
+ health::set_connected(port);
36
+ }
37
+ }
38
+ Ok(result)
23
39
  }
24
40
 
25
41
  fn build_menu(app: &tauri::AppHandle) -> tauri::Result<Menu<tauri::Wry>> {
@@ -45,9 +61,10 @@ fn build_menu(app: &tauri::AppHandle) -> tauri::Result<Menu<tauri::Wry>> {
45
61
  let logs = MenuItem::with_id(app, "tab-logs", "Logs", true, Some("CmdOrCtrl+5"))?;
46
62
  let sep2 = PredefinedMenuItem::separator(app)?;
47
63
  let refresh = MenuItem::with_id(app, "refresh", "Refresh", true, Some("CmdOrCtrl+R"))?;
64
+ let restart = MenuItem::with_id(app, "restart-server", "Restart Server", true, None::<&str>)?;
48
65
 
49
66
  let view_menu = Submenu::with_items(app, "View", true, &[
50
- &contacts, &calls, &settings, &invites, &logs, &sep2, &refresh,
67
+ &contacts, &calls, &settings, &invites, &logs, &sep2, &refresh, &restart,
51
68
  ])?;
52
69
 
53
70
  // Edit menu (standard macOS)
@@ -74,7 +91,8 @@ pub fn run() {
74
91
  .plugin(tauri_plugin_notification::init())
75
92
  .plugin(tauri_plugin_deep_link::init())
76
93
  .plugin(tauri_plugin_window_state::Builder::new().build())
77
- .invoke_handler(tauri::generate_handler![discover_server, start_server])
94
+ .manage(server::SidecarState::new())
95
+ .invoke_handler(tauri::generate_handler![discover_server, start_server, restart_server])
78
96
  .setup(|app| {
79
97
  let menu = build_menu(app.handle())?;
80
98
  app.set_menu(menu)?;
@@ -96,6 +114,15 @@ pub fn run() {
96
114
  }
97
115
  None
98
116
  }
117
+ "restart-server" => {
118
+ let result = server::restart_sidecar(&app_handle);
119
+ if result.success {
120
+ if let Some(port) = result.port {
121
+ health::set_connected(port);
122
+ }
123
+ }
124
+ None
125
+ }
99
126
  _ => None,
100
127
  };
101
128
 
@@ -107,6 +134,14 @@ pub fn run() {
107
134
  }
108
135
  });
109
136
 
137
+ // Start the bundled A2A server sidecar
138
+ let start_result = server::start_sidecar(app.handle());
139
+ if start_result.success {
140
+ if let Some(port) = start_result.port {
141
+ health::set_connected(port);
142
+ }
143
+ }
144
+
110
145
  // Start background health monitor
111
146
  health::start_health_monitor(app.handle().clone());
112
147
 
@@ -173,13 +208,19 @@ pub fn run() {
173
208
  .build(tauri::generate_context!())
174
209
  .expect("error building A2A Callbook");
175
210
 
176
- // Cmd+W hides window instead of quitting
211
+ // Cmd+W hides window instead of quitting; kill sidecar on exit
177
212
  app.run(|app_handle, event| {
178
- if let RunEvent::WindowEvent { label, event: WindowEvent::CloseRequested { api, .. }, .. } = &event {
179
- api.prevent_close();
180
- if let Some(window) = app_handle.get_webview_window(label) {
181
- let _ = window.hide();
213
+ match &event {
214
+ RunEvent::WindowEvent { label, event: WindowEvent::CloseRequested { api, .. }, .. } => {
215
+ api.prevent_close();
216
+ if let Some(window) = app_handle.get_webview_window(label) {
217
+ let _ = window.hide();
218
+ }
219
+ }
220
+ RunEvent::Exit => {
221
+ server::kill_sidecar(app_handle);
182
222
  }
223
+ _ => {}
183
224
  }
184
225
  });
185
226
  }