a2acalling 0.6.73 → 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.
- package/.a2a-manifest.json +2 -2
- package/.c8rc.json +16 -0
- package/.node-version +1 -0
- package/.serena/project.yml +126 -0
- package/ARCHITECTURE.md +40 -16
- package/CONVENTIONS.md +39 -6
- package/biome.json +27 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +146 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/index.html +131 -0
- package/coverage/src/index.js.html +313 -0
- package/coverage/src/lib/agent-card.js.html +418 -0
- package/coverage/src/lib/call-monitor.js.html +700 -0
- package/coverage/src/lib/callbook.js.html +1183 -0
- package/coverage/src/lib/claude-subagent.js.html +2173 -0
- package/coverage/src/lib/client.js.html +2134 -0
- package/coverage/src/lib/config.js.html +1525 -0
- package/coverage/src/lib/conversation-driver.js.html +1909 -0
- package/coverage/src/lib/conversations.js.html +2575 -0
- package/coverage/src/lib/crypto.js.html +424 -0
- package/coverage/src/lib/dashboard-events.js.html +724 -0
- package/coverage/src/lib/disclosure.js.html +2461 -0
- package/coverage/src/lib/external-ip.js.html +718 -0
- package/coverage/src/lib/index.html +506 -0
- package/coverage/src/lib/invite-host.js.html +754 -0
- package/coverage/src/lib/local-request.js.html +292 -0
- package/coverage/src/lib/logger.js.html +2116 -0
- package/coverage/src/lib/openclaw-integration.js.html +1102 -0
- package/coverage/src/lib/pid-file.js.html +394 -0
- package/coverage/src/lib/port-scanner.js.html +334 -0
- package/coverage/src/lib/prompt-template.js.html +1150 -0
- package/coverage/src/lib/runtime-adapter.js.html +2188 -0
- package/coverage/src/lib/summarizer.js.html +553 -0
- package/coverage/src/lib/summary-formatter.js.html +589 -0
- package/coverage/src/lib/summary-prompt.js.html +694 -0
- package/coverage/src/lib/tokens.js.html +2689 -0
- package/coverage/src/lib/turn-timeout.js.html +241 -0
- package/coverage/src/lib/update-checker.js.html +364 -0
- package/coverage/src/lib/update-manager.js.html +1024 -0
- package/coverage/src/routes/a2a.js.html +3724 -0
- package/coverage/src/routes/callbook.js.html +511 -0
- package/coverage/src/routes/dashboard.js.html +4819 -0
- package/coverage/src/routes/index.html +146 -0
- package/coverage/src/server.js.html +3622 -0
- package/coverage/tmp/coverage-1605378-1772576706365-0.json +1 -0
- package/coverage/tmp/coverage-1605384-1772576607459-0.json +1 -0
- package/coverage/tmp/coverage-1605410-1772576631155-0.json +1 -0
- package/coverage/tmp/coverage-1606942-1772576636869-0.json +1 -0
- package/coverage/tmp/coverage-1607004-1772576637454-0.json +1 -0
- package/coverage/tmp/coverage-1607044-1772576637876-0.json +1 -0
- package/coverage/tmp/coverage-1607096-1772576638356-0.json +1 -0
- package/coverage/tmp/coverage-1607145-1772576638777-0.json +1 -0
- package/coverage/tmp/coverage-1607201-1772576639277-0.json +1 -0
- package/coverage/tmp/coverage-1607247-1772576639755-0.json +1 -0
- package/coverage/tmp/coverage-1607317-1772576640083-0.json +1 -0
- package/coverage/tmp/coverage-1607381-1772576640465-0.json +1 -0
- package/coverage/tmp/coverage-1607446-1772576640868-0.json +1 -0
- package/coverage/tmp/coverage-1607501-1772576641662-0.json +1 -0
- package/coverage/tmp/coverage-1607534-1772576641565-0.json +1 -0
- package/coverage/tmp/coverage-1607627-1772576641871-0.json +1 -0
- package/coverage/tmp/coverage-1607665-1772576642172-0.json +1 -0
- package/coverage/tmp/coverage-1607714-1772576642577-0.json +1 -0
- package/coverage/tmp/coverage-1607788-1772576643466-0.json +1 -0
- package/coverage/tmp/coverage-1607924-1772576644678-0.json +1 -0
- package/coverage/tmp/coverage-1607978-1772576645154-0.json +1 -0
- package/coverage/tmp/coverage-1608035-1772576645564-0.json +1 -0
- package/coverage/tmp/coverage-1608106-1772576645967-0.json +1 -0
- package/coverage/tmp/coverage-1608179-1772576648656-0.json +1 -0
- package/coverage/tmp/coverage-1608196-1772576647367-0.json +1 -0
- package/coverage/tmp/coverage-1608217-1772576648557-0.json +1 -0
- package/coverage/tmp/coverage-1608256-1772576651378-0.json +1 -0
- package/coverage/tmp/coverage-1608265-1772576650058-0.json +1 -0
- package/coverage/tmp/coverage-1608289-1772576651358-0.json +1 -0
- package/coverage/tmp/coverage-1608591-1772576660465-0.json +1 -0
- package/coverage/tmp/coverage-1608648-1772576659272-0.json +1 -0
- package/coverage/tmp/coverage-1608665-1772576660374-0.json +1 -0
- package/coverage/tmp/coverage-1608677-1772576661268-0.json +1 -0
- package/coverage/tmp/coverage-1608684-1772576663968-0.json +1 -0
- package/coverage/tmp/coverage-1608692-1772576662575-0.json +1 -0
- package/coverage/tmp/coverage-1608701-1772576663873-0.json +1 -0
- package/coverage/tmp/coverage-1608718-1772576666674-0.json +1 -0
- package/coverage/tmp/coverage-1608725-1772576665463-0.json +1 -0
- package/coverage/tmp/coverage-1608738-1772576666577-0.json +1 -0
- package/coverage/tmp/coverage-1608753-1772576669664-0.json +1 -0
- package/coverage/tmp/coverage-1608763-1772576668275-0.json +1 -0
- package/coverage/tmp/coverage-1608771-1772576669563-0.json +1 -0
- package/coverage/tmp/coverage-1608828-1772576676574-0.json +1 -0
- package/coverage/tmp/coverage-1609244-1772576675272-0.json +1 -0
- package/coverage/tmp/coverage-1609342-1772576676478-0.json +1 -0
- package/coverage/tmp/coverage-1609450-1772576686954-0.json +1 -0
- package/coverage/tmp/coverage-1609841-1772576685466-0.json +1 -0
- package/coverage/tmp/coverage-1609925-1772576686855-0.json +1 -0
- package/coverage/tmp/coverage-1610399-1772576692469-0.json +1 -0
- package/coverage/tmp/coverage-1611283-1772576703062-0.json +1 -0
- package/coverage/tmp/coverage-1611294-1772576703755-0.json +1 -0
- package/docs/assessments/2026-02-27-google-a2a-protocol-assessment.md +292 -0
- package/docs/plans/2026-03-01-a2a-68-openclaw-integration-tests.md +676 -0
- package/docs/plans/2026-03-01-a2a-77-invoke-security-tests.md +661 -0
- package/docs/plans/2026-03-03-a2a-91-macos-packaging-plan.md +144 -0
- package/docs/signing-setup.md +49 -0
- package/eslint.config.js +16 -0
- package/knip.json +17 -0
- package/native/macos/certs/appldevcert.cer +0 -0
- package/native/macos/src-tauri/binaries/.gitkeep +0 -0
- package/native/macos/src-tauri/capabilities/default.json +11 -1
- package/native/macos/src-tauri/entitlements.plist +14 -0
- package/native/macos/src-tauri/src/discovery.rs +14 -3
- package/native/macos/src-tauri/src/health.rs +4 -0
- package/native/macos/src-tauri/src/lib.rs +52 -11
- package/native/macos/src-tauri/src/server.rs +262 -26
- package/native/macos/src-tauri/tauri.conf.json +13 -4
- package/package.json +16 -2
- package/pkg.config.json +14 -0
- package/scripts/build-standalone.sh +106 -0
- package/scripts/install-openclaw.js +3 -5
- package/scripts/smoke-test-standalone.sh +101 -0
- package/scripts/sync-version.sh +28 -0
- package/scripts/verify-app-bundle.sh +34 -0
- package/src/lib/agent-card.js +111 -0
- package/src/lib/client.js +290 -49
- package/src/lib/conversations.js +2 -0
- package/src/lib/local-request.js +69 -0
- package/src/lib/logger.js +2 -0
- package/src/lib/runtime-adapter.js +41 -1
- package/src/routes/a2a.js +393 -66
- package/src/routes/dashboard.js +1 -27
- package/src/server.js +19 -0
- package/.maestro/inbox/release-workflow-spam.md +0 -25
|
@@ -1,55 +1,287 @@
|
|
|
1
1
|
use serde::Serialize;
|
|
2
2
|
use std::process::Command;
|
|
3
|
+
use std::sync::atomic::{AtomicBool, AtomicU16, AtomicU32, Ordering};
|
|
4
|
+
use std::sync::Mutex;
|
|
5
|
+
use std::time::{Duration, Instant};
|
|
6
|
+
use tauri::{Emitter, Manager};
|
|
7
|
+
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
|
8
|
+
use tauri_plugin_shell::ShellExt;
|
|
9
|
+
|
|
10
|
+
const MAX_CRASH_COUNT: u32 = 5;
|
|
11
|
+
const STABLE_THRESHOLD: Duration = Duration::from_secs(60);
|
|
12
|
+
const MAX_BACKOFF_MS: u64 = 30_000;
|
|
3
13
|
|
|
4
14
|
#[derive(Debug, Serialize)]
|
|
5
15
|
pub struct StartResult {
|
|
6
16
|
pub success: bool,
|
|
7
17
|
pub message: String,
|
|
18
|
+
pub port: Option<u16>,
|
|
19
|
+
pub source: String, // "sidecar" | "external" | "none"
|
|
8
20
|
}
|
|
9
21
|
|
|
10
|
-
///
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
/// Holds the sidecar child process, port, and crash recovery state.
|
|
23
|
+
pub struct SidecarState {
|
|
24
|
+
pub child: Mutex<Option<CommandChild>>,
|
|
25
|
+
pub port: AtomicU16,
|
|
26
|
+
pub crash_count: AtomicU32,
|
|
27
|
+
pub shutting_down: AtomicBool,
|
|
28
|
+
last_start: Mutex<Option<Instant>>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl SidecarState {
|
|
32
|
+
pub fn new() -> Self {
|
|
33
|
+
SidecarState {
|
|
34
|
+
child: Mutex::new(None),
|
|
35
|
+
port: AtomicU16::new(0),
|
|
36
|
+
crash_count: AtomicU32::new(0),
|
|
37
|
+
shutting_down: AtomicBool::new(false),
|
|
38
|
+
last_start: Mutex::new(None),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fn port(&self) -> u16 {
|
|
43
|
+
self.port.load(Ordering::Relaxed)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Calculate exponential backoff: 1s, 2s, 4s, 8s, 16s (capped at 30s).
|
|
48
|
+
fn backoff_ms(crash_count: u32) -> u64 {
|
|
49
|
+
let exponent = crash_count.saturating_sub(1).min(4);
|
|
50
|
+
let ms = 1000u64 << exponent;
|
|
51
|
+
ms.min(MAX_BACKOFF_MS)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Pick a port for the sidecar: prefer config, then OS-assigned.
|
|
55
|
+
fn pick_port() -> u16 {
|
|
56
|
+
let config_ports = crate::discovery::read_config_ports();
|
|
57
|
+
if let Some(&port) = config_ports.first() {
|
|
58
|
+
return port;
|
|
59
|
+
}
|
|
60
|
+
// Let the OS pick an available port
|
|
61
|
+
std::net::TcpListener::bind("127.0.0.1:0")
|
|
62
|
+
.and_then(|l| l.local_addr())
|
|
63
|
+
.map(|addr| addr.port())
|
|
64
|
+
.unwrap_or(3001)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Start the A2A server via Tauri sidecar (bundled binary).
|
|
68
|
+
pub fn start_sidecar(app: &tauri::AppHandle) -> StartResult {
|
|
69
|
+
let port = pick_port();
|
|
70
|
+
let port_str = port.to_string();
|
|
71
|
+
|
|
72
|
+
let sidecar_cmd = match app.shell().sidecar("a2a-server") {
|
|
73
|
+
Ok(cmd) => cmd,
|
|
74
|
+
Err(_) => return start_external_server(port),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let (rx, child) = match sidecar_cmd
|
|
78
|
+
.env("PORT", &port_str)
|
|
79
|
+
.spawn()
|
|
80
|
+
{
|
|
81
|
+
Ok(pair) => pair,
|
|
82
|
+
Err(_) => return start_external_server(port),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Store the child handle and record start time
|
|
86
|
+
if let Some(state) = app.try_state::<SidecarState>() {
|
|
87
|
+
state.port.store(port, Ordering::Relaxed);
|
|
88
|
+
if let Ok(mut guard) = state.child.lock() {
|
|
89
|
+
*guard = Some(child);
|
|
90
|
+
}
|
|
91
|
+
if let Ok(mut guard) = state.last_start.lock() {
|
|
92
|
+
*guard = Some(Instant::now());
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Monitor sidecar stdout/stderr and detect exit for crash recovery
|
|
97
|
+
spawn_sidecar_monitor(app.clone(), rx);
|
|
98
|
+
|
|
99
|
+
StartResult {
|
|
100
|
+
success: true,
|
|
101
|
+
message: format!("Sidecar server starting on port {}...", port),
|
|
102
|
+
port: Some(port),
|
|
103
|
+
source: "sidecar".to_string(),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// Monitor sidecar output and process exit for crash recovery.
|
|
108
|
+
fn spawn_sidecar_monitor(
|
|
109
|
+
app: tauri::AppHandle,
|
|
110
|
+
mut rx: tokio::sync::mpsc::Receiver<CommandEvent>,
|
|
111
|
+
) {
|
|
112
|
+
tauri::async_runtime::spawn(async move {
|
|
113
|
+
while let Some(event) = rx.recv().await {
|
|
114
|
+
match event {
|
|
115
|
+
CommandEvent::Stdout(line) => {
|
|
116
|
+
let text = String::from_utf8_lossy(&line);
|
|
117
|
+
let _ = app.emit(
|
|
118
|
+
"sidecar-log",
|
|
119
|
+
serde_json::json!({
|
|
120
|
+
"stream": "stdout",
|
|
121
|
+
"line": text.trim_end()
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
CommandEvent::Stderr(line) => {
|
|
126
|
+
let text = String::from_utf8_lossy(&line);
|
|
127
|
+
let _ = app.emit(
|
|
128
|
+
"sidecar-log",
|
|
129
|
+
serde_json::json!({
|
|
130
|
+
"stream": "stderr",
|
|
131
|
+
"line": text.trim_end()
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
CommandEvent::Terminated(payload) => {
|
|
136
|
+
handle_sidecar_exit(&app, payload.code);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
CommandEvent::Error(msg) => {
|
|
140
|
+
let _ = app.emit(
|
|
141
|
+
"sidecar-log",
|
|
142
|
+
serde_json::json!({
|
|
143
|
+
"stream": "stderr",
|
|
144
|
+
"line": format!("[sidecar error] {}", msg)
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
27
147
|
}
|
|
148
|
+
_ => {}
|
|
28
149
|
}
|
|
29
150
|
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Handle unexpected sidecar exit with auto-restart and exponential backoff.
|
|
155
|
+
fn handle_sidecar_exit(app: &tauri::AppHandle, exit_code: Option<i32>) {
|
|
156
|
+
let state = match app.try_state::<SidecarState>() {
|
|
157
|
+
Some(s) => s,
|
|
158
|
+
None => return,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Don't restart during intentional shutdown
|
|
162
|
+
if state.shutting_down.load(Ordering::Relaxed) {
|
|
163
|
+
return;
|
|
30
164
|
}
|
|
31
165
|
|
|
166
|
+
// Clear the dead child from state
|
|
167
|
+
if let Ok(mut guard) = state.child.lock() {
|
|
168
|
+
guard.take();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Reset crash counter if server ran long enough (60s of stable operation)
|
|
172
|
+
if let Ok(guard) = state.last_start.lock() {
|
|
173
|
+
if let Some(start_time) = *guard {
|
|
174
|
+
if start_time.elapsed() >= STABLE_THRESHOLD {
|
|
175
|
+
state.crash_count.store(0, Ordering::Relaxed);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let crashes = state.crash_count.fetch_add(1, Ordering::Relaxed) + 1;
|
|
181
|
+
|
|
182
|
+
crate::health::set_disconnected();
|
|
183
|
+
|
|
184
|
+
let _ = app.emit(
|
|
185
|
+
"server-status",
|
|
186
|
+
serde_json::json!({
|
|
187
|
+
"connected": false,
|
|
188
|
+
"crashed": true,
|
|
189
|
+
"crashCount": crashes,
|
|
190
|
+
"exitCode": exit_code
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Stop restarting after too many consecutive crashes
|
|
195
|
+
if crashes >= MAX_CRASH_COUNT {
|
|
196
|
+
let _ = app.emit(
|
|
197
|
+
"server-status",
|
|
198
|
+
serde_json::json!({
|
|
199
|
+
"connected": false,
|
|
200
|
+
"crashed": true,
|
|
201
|
+
"crashCount": crashes,
|
|
202
|
+
"fatal": true,
|
|
203
|
+
"message": "Server crashed too many times. Use View > Restart Server to try again."
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Schedule restart with exponential backoff
|
|
210
|
+
let delay = backoff_ms(crashes);
|
|
211
|
+
let app_clone = app.clone();
|
|
212
|
+
tauri::async_runtime::spawn(async move {
|
|
213
|
+
tokio::time::sleep(Duration::from_millis(delay)).await;
|
|
214
|
+
|
|
215
|
+
if let Some(st) = app_clone.try_state::<SidecarState>() {
|
|
216
|
+
if st.shutting_down.load(Ordering::Relaxed) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let result = start_sidecar(&app_clone);
|
|
222
|
+
if result.success {
|
|
223
|
+
if let Some(port) = result.port {
|
|
224
|
+
crate::health::set_connected(port);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Restart the sidecar — kills existing process, resets crash counter, starts fresh.
|
|
231
|
+
pub fn restart_sidecar(app: &tauri::AppHandle) -> StartResult {
|
|
232
|
+
kill_sidecar(app);
|
|
233
|
+
|
|
234
|
+
if let Some(state) = app.try_state::<SidecarState>() {
|
|
235
|
+
state.crash_count.store(0, Ordering::Relaxed);
|
|
236
|
+
state.shutting_down.store(false, Ordering::Relaxed);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
start_sidecar(app)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Kill the running sidecar process (called on app exit or restart).
|
|
243
|
+
pub fn kill_sidecar(app: &tauri::AppHandle) {
|
|
244
|
+
if let Some(state) = app.try_state::<SidecarState>() {
|
|
245
|
+
state.shutting_down.store(true, Ordering::Relaxed);
|
|
246
|
+
if let Ok(mut guard) = state.child.lock() {
|
|
247
|
+
if let Some(child) = guard.take() {
|
|
248
|
+
let _ = child.kill();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── External CLI fallback ──
|
|
255
|
+
|
|
256
|
+
/// Find the `a2a` CLI binary on PATH
|
|
257
|
+
fn find_a2a_binary() -> Option<String> {
|
|
258
|
+
let result = Command::new("which").arg("a2a").output();
|
|
259
|
+
if let Ok(output) = result {
|
|
260
|
+
if output.status.success() {
|
|
261
|
+
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
262
|
+
if !path.is_empty() {
|
|
263
|
+
return Some(path);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
32
267
|
None
|
|
33
268
|
}
|
|
34
269
|
|
|
35
|
-
/// Start the
|
|
36
|
-
|
|
270
|
+
/// Start the A2A server via external CLI (fallback when sidecar unavailable).
|
|
271
|
+
fn start_external_server(port: u16) -> StartResult {
|
|
37
272
|
let binary = match find_a2a_binary() {
|
|
38
273
|
Some(b) => b,
|
|
39
274
|
None => {
|
|
40
275
|
return StartResult {
|
|
41
276
|
success: false,
|
|
42
|
-
message: "Could not find 'a2a' CLI. Is a2acalling installed? Run: npm install -g a2acalling".to_string(),
|
|
277
|
+
message: "Could not find bundled sidecar or 'a2a' CLI. Is a2acalling installed? Run: npm install -g a2acalling".to_string(),
|
|
278
|
+
port: None,
|
|
279
|
+
source: "none".to_string(),
|
|
43
280
|
};
|
|
44
281
|
}
|
|
45
282
|
};
|
|
46
283
|
|
|
47
|
-
let port = crate::discovery::read_config_ports()
|
|
48
|
-
.first()
|
|
49
|
-
.copied()
|
|
50
|
-
.unwrap_or(3001);
|
|
51
284
|
let port_str = port.to_string();
|
|
52
|
-
|
|
53
285
|
let result = Command::new(&binary)
|
|
54
286
|
.args(["server", "--port", &port_str])
|
|
55
287
|
.stdout(std::process::Stdio::null())
|
|
@@ -60,11 +292,15 @@ pub fn start_server() -> StartResult {
|
|
|
60
292
|
match result {
|
|
61
293
|
Ok(_child) => StartResult {
|
|
62
294
|
success: true,
|
|
63
|
-
message: format!("
|
|
295
|
+
message: format!("External server starting on port {}...", port),
|
|
296
|
+
port: Some(port),
|
|
297
|
+
source: "external".to_string(),
|
|
64
298
|
},
|
|
65
299
|
Err(err) => StartResult {
|
|
66
300
|
success: false,
|
|
67
301
|
message: format!("Failed to start server: {}", err),
|
|
302
|
+
port: None,
|
|
303
|
+
source: "none".to_string(),
|
|
68
304
|
},
|
|
69
305
|
}
|
|
70
306
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
|
|
3
3
|
"productName": "A2A Callbook",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.74",
|
|
5
5
|
"identifier": "com.openclaw.a2a-callbook",
|
|
6
6
|
"build": {
|
|
7
7
|
"frontendDist": "../index.html"
|
|
@@ -20,7 +20,13 @@
|
|
|
20
20
|
},
|
|
21
21
|
"bundle": {
|
|
22
22
|
"active": true,
|
|
23
|
-
"targets": [
|
|
23
|
+
"targets": [
|
|
24
|
+
"dmg",
|
|
25
|
+
"app"
|
|
26
|
+
],
|
|
27
|
+
"externalBin": [
|
|
28
|
+
"binaries/a2a-server"
|
|
29
|
+
],
|
|
24
30
|
"icon": [
|
|
25
31
|
"icons/32x32.png",
|
|
26
32
|
"icons/128x128.png",
|
|
@@ -28,13 +34,16 @@
|
|
|
28
34
|
],
|
|
29
35
|
"macOS": {
|
|
30
36
|
"minimumSystemVersion": "12.0",
|
|
31
|
-
"frameworks": []
|
|
37
|
+
"frameworks": [],
|
|
38
|
+
"entitlements": "entitlements.plist"
|
|
32
39
|
}
|
|
33
40
|
},
|
|
34
41
|
"plugins": {
|
|
35
42
|
"deep-link": {
|
|
36
43
|
"desktop": {
|
|
37
|
-
"schemes": [
|
|
44
|
+
"schemes": [
|
|
45
|
+
"a2a"
|
|
46
|
+
]
|
|
38
47
|
}
|
|
39
48
|
}
|
|
40
49
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "a2acalling",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.75",
|
|
4
4
|
"description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"postinstall": "node scripts/postinstall.js",
|
|
12
12
|
"start": "node src/server.js",
|
|
13
|
-
"test": "node test/run.js"
|
|
13
|
+
"test": "node test/run.js",
|
|
14
|
+
"test:coverage": "c8 node test/run.js",
|
|
15
|
+
"lint": "biome lint src/",
|
|
16
|
+
"lint:check": "biome check src/",
|
|
17
|
+
"knip": "knip",
|
|
18
|
+
"build:standalone": "bash scripts/build-standalone.sh",
|
|
19
|
+
"build:sync-version": "bash scripts/sync-version.sh"
|
|
14
20
|
},
|
|
15
21
|
"keywords": [
|
|
16
22
|
"openclaw",
|
|
@@ -36,5 +42,13 @@
|
|
|
36
42
|
"dependencies": {
|
|
37
43
|
"better-sqlite3": "^11.10.0",
|
|
38
44
|
"express": "^4.21.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@yao-pkg/pkg": "^6.0.0",
|
|
48
|
+
"@biomejs/biome": "^2.4.4",
|
|
49
|
+
"c8": "^11.0.0",
|
|
50
|
+
"eslint": "^10.0.2",
|
|
51
|
+
"eslint-plugin-sonarjs": "^4.0.0",
|
|
52
|
+
"knip": "^5.85.0"
|
|
39
53
|
}
|
|
40
54
|
}
|
package/pkg.config.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pkg": {
|
|
3
|
+
"scripts": [
|
|
4
|
+
"src/**/*.js",
|
|
5
|
+
"bin/cli.js"
|
|
6
|
+
],
|
|
7
|
+
"assets": [
|
|
8
|
+
"src/dashboard/public/**/*",
|
|
9
|
+
"node_modules/better-sqlite3/build/Release/better_sqlite3.node",
|
|
10
|
+
"node_modules/better-sqlite3/prebuilds/**/*"
|
|
11
|
+
],
|
|
12
|
+
"outputPath": "native/macos/src-tauri/binaries"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Build standalone Node.js binary for the A2A server using @yao-pkg/pkg.
|
|
3
|
+
# Usage:
|
|
4
|
+
# scripts/build-standalone.sh # Build for current arch
|
|
5
|
+
# scripts/build-standalone.sh --universal # Build macOS universal binary (aarch64 + x86_64)
|
|
6
|
+
# scripts/build-standalone.sh --arch arm64 # Build for specific arch
|
|
7
|
+
# scripts/build-standalone.sh --arch x64 # Build for specific arch
|
|
8
|
+
#
|
|
9
|
+
# Output: native/macos/src-tauri/binaries/a2a-server-<target-triple>
|
|
10
|
+
# Requires: Node.js 20+, npm, macOS (for native binary builds)
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
15
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
16
|
+
OUTPUT_DIR="$PROJECT_DIR/native/macos/src-tauri/binaries"
|
|
17
|
+
PKG_TARGET_NODE="node20"
|
|
18
|
+
BINARY_NAME="a2a-server"
|
|
19
|
+
|
|
20
|
+
cd "$PROJECT_DIR"
|
|
21
|
+
|
|
22
|
+
# ── Parse args ──
|
|
23
|
+
UNIVERSAL=false
|
|
24
|
+
TARGET_ARCH=""
|
|
25
|
+
while [[ $# -gt 0 ]]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--universal) UNIVERSAL=true; shift ;;
|
|
28
|
+
--arch) TARGET_ARCH="$2"; shift 2 ;;
|
|
29
|
+
*) echo "Unknown option: $1" >&2; exit 2 ;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
# ── Detect platform ──
|
|
34
|
+
OS="$(uname -s)"
|
|
35
|
+
if [[ "$OS" != "Darwin" ]]; then
|
|
36
|
+
echo "Warning: This build script is designed for macOS. Binary will target macOS regardless." >&2
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
NATIVE_ARCH="$(uname -m)"
|
|
40
|
+
case "$NATIVE_ARCH" in
|
|
41
|
+
arm64|aarch64) NATIVE_ARCH="arm64" ;;
|
|
42
|
+
x86_64) NATIVE_ARCH="x64" ;;
|
|
43
|
+
*) echo "Unsupported architecture: $NATIVE_ARCH" >&2; exit 1 ;;
|
|
44
|
+
esac
|
|
45
|
+
|
|
46
|
+
# ── Ensure @yao-pkg/pkg is available ──
|
|
47
|
+
if ! npx --no-install pkg --version >/dev/null 2>&1; then
|
|
48
|
+
echo "Installing @yao-pkg/pkg..."
|
|
49
|
+
npm install --save-dev @yao-pkg/pkg
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# ── Ensure production dependencies are built ──
|
|
53
|
+
echo "Rebuilding native modules..."
|
|
54
|
+
npm rebuild better-sqlite3
|
|
55
|
+
|
|
56
|
+
# ── Build function ──
|
|
57
|
+
build_arch() {
|
|
58
|
+
local arch="$1"
|
|
59
|
+
local target="${PKG_TARGET_NODE}-macos-${arch}"
|
|
60
|
+
local triple
|
|
61
|
+
|
|
62
|
+
case "$arch" in
|
|
63
|
+
arm64) triple="aarch64-apple-darwin" ;;
|
|
64
|
+
x64) triple="x86_64-apple-darwin" ;;
|
|
65
|
+
*) echo "Unsupported arch: $arch" >&2; return 1 ;;
|
|
66
|
+
esac
|
|
67
|
+
|
|
68
|
+
local output_path="$OUTPUT_DIR/${BINARY_NAME}-${triple}"
|
|
69
|
+
|
|
70
|
+
echo "Building standalone binary: $target → $output_path"
|
|
71
|
+
|
|
72
|
+
npx pkg src/server.js \
|
|
73
|
+
--config pkg.config.json \
|
|
74
|
+
--target "$target" \
|
|
75
|
+
--output "$output_path" \
|
|
76
|
+
--compress GZip
|
|
77
|
+
|
|
78
|
+
chmod +x "$output_path"
|
|
79
|
+
echo "Built: $output_path ($(du -h "$output_path" | cut -f1))"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# ── Execute builds ──
|
|
83
|
+
mkdir -p "$OUTPUT_DIR"
|
|
84
|
+
|
|
85
|
+
if $UNIVERSAL; then
|
|
86
|
+
echo "=== Building macOS universal binary ==="
|
|
87
|
+
build_arch "arm64"
|
|
88
|
+
build_arch "x64"
|
|
89
|
+
|
|
90
|
+
ARM_BIN="$OUTPUT_DIR/${BINARY_NAME}-aarch64-apple-darwin"
|
|
91
|
+
X64_BIN="$OUTPUT_DIR/${BINARY_NAME}-x86_64-apple-darwin"
|
|
92
|
+
UNIVERSAL_BIN="$OUTPUT_DIR/${BINARY_NAME}-universal-apple-darwin"
|
|
93
|
+
|
|
94
|
+
echo "Creating universal binary with lipo..."
|
|
95
|
+
lipo -create "$ARM_BIN" "$X64_BIN" -output "$UNIVERSAL_BIN"
|
|
96
|
+
chmod +x "$UNIVERSAL_BIN"
|
|
97
|
+
echo "Universal: $UNIVERSAL_BIN ($(du -h "$UNIVERSAL_BIN" | cut -f1))"
|
|
98
|
+
elif [[ -n "$TARGET_ARCH" ]]; then
|
|
99
|
+
build_arch "$TARGET_ARCH"
|
|
100
|
+
else
|
|
101
|
+
build_arch "$NATIVE_ARCH"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
echo ""
|
|
105
|
+
echo "=== Build complete ==="
|
|
106
|
+
ls -lh "$OUTPUT_DIR"/${BINARY_NAME}-* 2>/dev/null || echo "(no binaries found)"
|
|
@@ -770,8 +770,8 @@ async function install() {
|
|
|
770
770
|
const runtimeLine = forceStandalone
|
|
771
771
|
? 'Runtime forced to standalone mode for this setup run.'
|
|
772
772
|
: hasOpenClawBinary
|
|
773
|
-
? 'Runtime auto-selects OpenClaw when available and falls back to
|
|
774
|
-
: 'Runtime defaults to
|
|
773
|
+
? 'Runtime auto-selects OpenClaw when available and falls back to test mode if needed.'
|
|
774
|
+
: 'Runtime defaults to test mode (no OpenClaw dependency required).';
|
|
775
775
|
|
|
776
776
|
const { splitHostPort, isLocalOrUnroutableHost } = require('../src/lib/invite-host');
|
|
777
777
|
const inviteParsed = splitHostPort(inviteHost);
|
|
@@ -890,10 +890,8 @@ ${standaloneBootstrap
|
|
|
890
890
|
${green(standaloneBootstrap.notifyScript)}
|
|
891
891
|
|
|
892
892
|
Optional bridge wiring:
|
|
893
|
-
export A2A_RUNTIME=
|
|
893
|
+
export A2A_RUNTIME=test
|
|
894
894
|
export A2A_AGENT_COMMAND="${standaloneBootstrap.turnScript}"
|
|
895
|
-
export A2A_SUMMARY_COMMAND="${standaloneBootstrap.summaryScript}"
|
|
896
|
-
export A2A_NOTIFY_COMMAND="${standaloneBootstrap.notifyScript}"
|
|
897
895
|
${standaloneBootstrap.generatedAdminToken ? `
|
|
898
896
|
Suggested dashboard admin token (set in env, do not commit):
|
|
899
897
|
export A2A_ADMIN_TOKEN="${standaloneBootstrap.generatedAdminToken}"` : ''}`
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# A2A-97: fast smoke coverage for packaged standalone server startup + SQLite-backed routes.
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
7
|
+
BINARY_PATH="${1:-}"
|
|
8
|
+
|
|
9
|
+
if [[ -z "$BINARY_PATH" ]]; then
|
|
10
|
+
for candidate in \
|
|
11
|
+
"$PROJECT_DIR/native/macos/src-tauri/binaries/a2a-server-universal-apple-darwin" \
|
|
12
|
+
"$PROJECT_DIR/native/macos/src-tauri/binaries/a2a-server-aarch64-apple-darwin" \
|
|
13
|
+
"$PROJECT_DIR/native/macos/src-tauri/binaries/a2a-server-x86_64-apple-darwin"
|
|
14
|
+
do
|
|
15
|
+
if [[ -x "$candidate" ]]; then
|
|
16
|
+
BINARY_PATH="$candidate"
|
|
17
|
+
break
|
|
18
|
+
fi
|
|
19
|
+
done
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if [[ -z "$BINARY_PATH" || ! -x "$BINARY_PATH" ]]; then
|
|
23
|
+
echo "Standalone binary not found or not executable: ${BINARY_PATH:-<unset>}" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
TMP_ROOT="$(mktemp -d)"
|
|
28
|
+
CONFIG_DIR="$TMP_ROOT/config"
|
|
29
|
+
LOG_FILE="$TMP_ROOT/server.log"
|
|
30
|
+
mkdir -p "$CONFIG_DIR"
|
|
31
|
+
|
|
32
|
+
PORT="$((
|
|
33
|
+
$(node -e "const net=require('net'); const s=net.createServer(); s.listen(0,'127.0.0.1',()=>{console.log(s.address().port); s.close();});")
|
|
34
|
+
))"
|
|
35
|
+
|
|
36
|
+
PID=""
|
|
37
|
+
cleanup() {
|
|
38
|
+
if [[ -n "$PID" ]] && kill -0 "$PID" 2>/dev/null; then
|
|
39
|
+
kill "$PID" 2>/dev/null || true
|
|
40
|
+
wait "$PID" 2>/dev/null || true
|
|
41
|
+
fi
|
|
42
|
+
rm -rf "$TMP_ROOT"
|
|
43
|
+
}
|
|
44
|
+
trap cleanup EXIT
|
|
45
|
+
|
|
46
|
+
fail() {
|
|
47
|
+
echo "Smoke test failed: $1" >&2
|
|
48
|
+
if [[ -f "$LOG_FILE" ]]; then
|
|
49
|
+
echo "---- server log tail (last 50 lines) ----" >&2
|
|
50
|
+
tail -n 50 "$LOG_FILE" >&2 || true
|
|
51
|
+
fi
|
|
52
|
+
exit 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
A2A_RUNTIME=test \
|
|
56
|
+
NO_AUTO_UPDATE=1 \
|
|
57
|
+
A2A_CONFIG_DIR="$CONFIG_DIR" \
|
|
58
|
+
OPENCLAW_CONFIG_DIR="$CONFIG_DIR" \
|
|
59
|
+
"$BINARY_PATH" "$PORT" >"$LOG_FILE" 2>&1 &
|
|
60
|
+
PID="$!"
|
|
61
|
+
|
|
62
|
+
health_ok=0
|
|
63
|
+
for _ in $(seq 1 60); do
|
|
64
|
+
if curl -fsS "http://127.0.0.1:${PORT}/api/a2a/status" >/dev/null 2>&1; then
|
|
65
|
+
health_ok=1
|
|
66
|
+
break
|
|
67
|
+
fi
|
|
68
|
+
sleep 1
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
if [[ "$health_ok" -ne 1 ]]; then
|
|
72
|
+
fail "server did not become healthy within 60 seconds"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# A2A-97: hit conversation list endpoint to exercise SQLite-backed dashboard storage path.
|
|
76
|
+
curl -fsS "http://127.0.0.1:${PORT}/api/a2a/dashboard/calls" >/dev/null \
|
|
77
|
+
|| fail "dashboard calls endpoint failed (possible SQLite/native binding issue)"
|
|
78
|
+
|
|
79
|
+
create_resp="$(
|
|
80
|
+
curl -fsS -X POST "http://127.0.0.1:${PORT}/api/a2a/dashboard/invites" \
|
|
81
|
+
-H 'content-type: application/json' \
|
|
82
|
+
-d '{"name":"CI Smoke","tier":"public","expires":"1h"}'
|
|
83
|
+
)"
|
|
84
|
+
|
|
85
|
+
invite_id="$(node -e "const payload=JSON.parse(process.argv[1]); process.stdout.write(payload.token && payload.token.id ? payload.token.id : '');" "$create_resp")"
|
|
86
|
+
if [[ -z "$invite_id" ]]; then
|
|
87
|
+
fail "invite creation returned no token id"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
curl -fsS -X POST "http://127.0.0.1:${PORT}/api/a2a/dashboard/invites/${invite_id}/revoke" >/dev/null \
|
|
91
|
+
|| fail "invite revoke failed"
|
|
92
|
+
|
|
93
|
+
if ! kill -0 "$PID" 2>/dev/null; then
|
|
94
|
+
fail "server exited unexpectedly during smoke checks"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
kill "$PID" 2>/dev/null || true
|
|
98
|
+
wait "$PID" 2>/dev/null || true
|
|
99
|
+
PID=""
|
|
100
|
+
|
|
101
|
+
echo "Standalone smoke test passed for ${BINARY_PATH}"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
6
|
+
TAURI_CONF="$PROJECT_DIR/native/macos/src-tauri/tauri.conf.json"
|
|
7
|
+
|
|
8
|
+
PACKAGE_VERSION="$(node -p "require('$PROJECT_DIR/package.json').version")"
|
|
9
|
+
|
|
10
|
+
# A2A-95: force native bundle version to match npm package version for release parity.
|
|
11
|
+
node - "$TAURI_CONF" "$PACKAGE_VERSION" <<'NODE'
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const tauriConfPath = process.argv[2];
|
|
14
|
+
const nextVersion = process.argv[3];
|
|
15
|
+
|
|
16
|
+
const raw = fs.readFileSync(tauriConfPath, 'utf8');
|
|
17
|
+
const config = JSON.parse(raw);
|
|
18
|
+
const prevVersion = config.version;
|
|
19
|
+
|
|
20
|
+
if (prevVersion === nextVersion) {
|
|
21
|
+
console.log(`Tauri version already in sync: ${nextVersion}`);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
config.version = nextVersion;
|
|
26
|
+
fs.writeFileSync(tauriConfPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
27
|
+
console.log(`Updated tauri.conf.json version: ${prevVersion} -> ${nextVersion}`);
|
|
28
|
+
NODE
|