feat(v2): Deno-first sidecar with Node.js fallback and signing key
Refactor SidecarManager to use SidecarCommand struct abstracting runtime choice. resolve_sidecar_command() prefers Deno (runs TS directly, no build step), falls back to Node.js if deno not in PATH. Both runners bundled in tauri.conf.json resources. Set auto-update signing pubkey in updater config.
This commit is contained in:
parent
035d4186fa
commit
a2bc8838b4
2 changed files with 63 additions and 26 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
// Node.js sidecar lifecycle management
|
// Sidecar lifecycle management (Deno-first, Node.js fallback)
|
||||||
// Spawns agent-runner.ts (compiled), communicates via stdio NDJSON
|
// Spawns agent-runner-deno.ts (or agent-runner.mjs), communicates via stdio NDJSON
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io::{BufRead, BufReader, Write};
|
use std::io::{BufRead, BufReader, Write};
|
||||||
|
|
@ -18,6 +18,11 @@ pub struct AgentQueryOptions {
|
||||||
pub resume_session_id: Option<String>,
|
pub resume_session_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SidecarCommand {
|
||||||
|
program: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SidecarManager {
|
pub struct SidecarManager {
|
||||||
child: Arc<Mutex<Option<Child>>>,
|
child: Arc<Mutex<Option<Child>>>,
|
||||||
stdin_writer: Arc<Mutex<Option<Box<dyn Write + Send>>>>,
|
stdin_writer: Arc<Mutex<Option<Box<dyn Write + Send>>>>,
|
||||||
|
|
@ -39,13 +44,13 @@ impl SidecarManager {
|
||||||
return Err("Sidecar already running".to_string());
|
return Err("Sidecar already running".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve sidecar binary path relative to the app
|
// Resolve sidecar command (Deno-first, Node.js fallback)
|
||||||
let sidecar_path = Self::resolve_sidecar_path(app)?;
|
let cmd = Self::resolve_sidecar_command(app)?;
|
||||||
|
|
||||||
log::info!("Starting sidecar: node {}", sidecar_path.display());
|
log::info!("Starting sidecar: {} {}", cmd.program, cmd.args.join(" "));
|
||||||
|
|
||||||
let mut child = Command::new("node")
|
let mut child = Command::new(&cmd.program)
|
||||||
.arg(&sidecar_path)
|
.args(&cmd.args)
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
|
|
@ -184,35 +189,63 @@ impl SidecarManager {
|
||||||
*self.ready.lock().unwrap()
|
*self.ready.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_sidecar_path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
|
fn resolve_sidecar_command(app: &AppHandle) -> Result<SidecarCommand, String> {
|
||||||
// In dev mode, use the sidecar source directory
|
|
||||||
// In production, the built sidecar is bundled with the app
|
|
||||||
let resource_dir = app
|
let resource_dir = app
|
||||||
.path()
|
.path()
|
||||||
.resource_dir()
|
.resource_dir()
|
||||||
.map_err(|e| format!("Failed to get resource dir: {e}"))?;
|
.map_err(|e| format!("Failed to get resource dir: {e}"))?;
|
||||||
|
|
||||||
let prod_path = resource_dir.join("sidecar").join("dist").join("agent-runner.mjs");
|
let dev_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
if prod_path.exists() {
|
|
||||||
return Ok(prod_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dev fallback: look relative to the Cargo project root
|
|
||||||
let dev_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.parent()
|
.parent()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join("sidecar")
|
.to_path_buf();
|
||||||
.join("dist")
|
|
||||||
.join("agent-runner.mjs");
|
|
||||||
|
|
||||||
if dev_path.exists() {
|
// Try Deno first (runs TypeScript directly, no build step needed)
|
||||||
return Ok(dev_path);
|
let deno_paths = [
|
||||||
|
resource_dir.join("sidecar").join("agent-runner-deno.ts"),
|
||||||
|
dev_root.join("sidecar").join("agent-runner-deno.ts"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for path in &deno_paths {
|
||||||
|
if path.exists() {
|
||||||
|
// Check if deno is available
|
||||||
|
if Command::new("deno").arg("--version").output().is_ok() {
|
||||||
|
return Ok(SidecarCommand {
|
||||||
|
program: "deno".to_string(),
|
||||||
|
args: vec![
|
||||||
|
"run".to_string(),
|
||||||
|
"--allow-run".to_string(),
|
||||||
|
"--allow-env".to_string(),
|
||||||
|
"--allow-read".to_string(),
|
||||||
|
path.to_string_lossy().to_string(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
log::warn!("Deno sidecar found at {} but deno not in PATH, falling back to Node.js", path.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to Node.js
|
||||||
|
let node_paths = [
|
||||||
|
resource_dir.join("sidecar").join("dist").join("agent-runner.mjs"),
|
||||||
|
dev_root.join("sidecar").join("dist").join("agent-runner.mjs"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for path in &node_paths {
|
||||||
|
if path.exists() {
|
||||||
|
return Ok(SidecarCommand {
|
||||||
|
program: "node".to_string(),
|
||||||
|
args: vec![path.to_string_lossy().to_string()],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(format!(
|
Err(format!(
|
||||||
"Sidecar not found at {} or {}",
|
"Sidecar not found. Checked Deno ({}, {}) and Node.js ({}, {})",
|
||||||
prod_path.display(),
|
deno_paths[0].display(),
|
||||||
dev_path.display()
|
deno_paths[1].display(),
|
||||||
|
node_paths[0].display(),
|
||||||
|
node_paths[1].display(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
"https://github.com/DexterFromLab/BTerminal/releases/latest/download/latest.json"
|
"https://github.com/DexterFromLab/BTerminal/releases/latest/download/latest.json"
|
||||||
],
|
],
|
||||||
"dialog": true,
|
"dialog": true,
|
||||||
"pubkey": ""
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEJCRkZEMERDMTUwMzY5MjIKUldRaWFRTVYzTkQvdTYwRDh6YStaSE9rWUZYYkRGd3UvVUcydE1IQVdTM29uNTRPTlpjQmFqVFEK"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
|
@ -43,6 +43,10 @@
|
||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
|
"resources": [
|
||||||
|
"../sidecar/agent-runner-deno.ts",
|
||||||
|
"../sidecar/dist/agent-runner.mjs"
|
||||||
|
],
|
||||||
"category": "DeveloperTool",
|
"category": "DeveloperTool",
|
||||||
"shortDescription": "Multi-session Claude agent dashboard",
|
"shortDescription": "Multi-session Claude agent dashboard",
|
||||||
"longDescription": "BTerminal is a terminal emulator with integrated Claude AI agent sessions, SSH management, and a tiling pane layout. Built with Tauri, Svelte 5, and xterm.js.",
|
"longDescription": "BTerminal is a terminal emulator with integrated Claude AI agent sessions, SSH management, and a tiling pane layout. Built with Tauri, Svelte 5, and xterm.js.",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue