Option C: Rewrite gyandoot-browser for camoufox-browser@0.1.0 daemon IPC

Complete rewrite removing old HTTP REST client. The latest camoufox-browser
npm package (v0.1.0) uses Unix socket JSON-line IPC, not HTTP REST on :9377.

Changes:
- gyandoot-browser/client.rs: Full daemon IPC via Unix socket (~/.camoufox-browser/*.sock)
  - JSON-line protocol matching ensureDaemonRunning + daemon.js
  - Auto-launches browser on first command (daemon manages lifecycle)
  - Supports navigate, click, type, scroll, snapshot, search macros, tab management
  - Proper HeathResponse, NavigationResult, PageSnapshot types
- gyandoot-browser/lib.rs: Exports new client API (no SearchMacro enum, no SessionManager)
- gyandoot-browser/Cargo.toml: reqwest -> tokio Unix socket deps
- Removed: macros.rs, session.rs, snapshot.rs (all merged into client.rs)
- browser_manager.rs: Triggers daemon via npx, waits for Unix socket
- browser_tools.rs: Simplified API (no tab_id, no create_tab/close_tab)

Fix: camoufox-browser process exited with status 1 error on fresh install
This commit is contained in:
2026-06-12 11:12:51 +05:30
parent b2650a569c
commit e06094c0a2
9 changed files with 733 additions and 1102 deletions

View File

@@ -3,16 +3,68 @@
//! These tools are registered with the `ToolExecutor` and provide
//! LLM-accessible functions for searching the web, browsing pages,
//! clicking elements, and extracting content.
//!
//! Uses the camoufox-browser daemon protocol (Unix socket IPC).
use serde_json;
use tracing::{info, warn};
use gyandoot_browser::{BrowserClient, SearchMacro};
use gyandoot_browser::BrowserClient;
use gyandoot_core::ToolDefinition;
use crate::tools::Tool;
/// Tool for searching the web using camofox-browser search macros.
/// Search macro name strings sent to the camoufox daemon.
const MACRO_GOOGLE: &str = "@google_search";
const MACRO_YOUTUBE: &str = "@youtube_search";
const MACRO_AMAZON: &str = "@amazon_search";
const MACRO_REDDIT: &str = "@reddit_search";
const MACRO_WIKIPEDIA: &str = "@wikipedia_search";
const MACRO_TWITTER: &str = "@twitter_search";
const MACRO_YELP: &str = "@yelp_search";
const MACRO_SPOTIFY: &str = "@spotify_search";
const MACRO_LINKEDIN: &str = "@linkedin_search";
const MACRO_INSTAGRAM: &str = "@instagram_search";
const MACRO_TIKTOK: &str = "@tiktok_search";
const MACRO_TWITCH: &str = "@twitch_search";
fn site_to_macro(site: &str) -> &'static str {
match site {
"google" => MACRO_GOOGLE,
"youtube" => MACRO_YOUTUBE,
"amazon" => MACRO_AMAZON,
"reddit" => MACRO_REDDIT,
"wikipedia" => MACRO_WIKIPEDIA,
"twitter" | "x" => MACRO_TWITTER,
"yelp" => MACRO_YELP,
"spotify" => MACRO_SPOTIFY,
"linkedin" => MACRO_LINKEDIN,
"instagram" => MACRO_INSTAGRAM,
"tiktok" => MACRO_TIKTOK,
"twitch" => MACRO_TWITCH,
_ => MACRO_GOOGLE,
}
}
fn site_name(site: &str) -> &'static str {
match site {
"google" => "Google",
"youtube" => "YouTube",
"amazon" => "Amazon",
"reddit" => "Reddit",
"wikipedia" => "Wikipedia",
"twitter" | "x" => "Twitter/X",
"yelp" => "Yelp",
"spotify" => "Spotify",
"linkedin" => "LinkedIn",
"instagram" => "Instagram",
"tiktok" => "TikTok",
"twitch" => "Twitch",
_ => "Google",
}
}
/// Tool for searching the web using camoufox daemon macros.
pub struct WebSearchTool {
client: BrowserClient,
}
@@ -61,46 +113,23 @@ impl Tool for WebSearchTool {
return Err(anyhow::anyhow!("query is required"));
}
let macro_name = match site.as_str() {
"google" => SearchMacro::GoogleSearch,
"youtube" => SearchMacro::YoutubeSearch,
"amazon" => SearchMacro::AmazonSearch,
"reddit" => SearchMacro::RedditSearch,
"wikipedia" => SearchMacro::WikipediaSearch,
"twitter" | "x" => SearchMacro::TwitterSearch,
"yelp" => SearchMacro::YelpSearch,
"spotify" => SearchMacro::SpotifySearch,
"linkedin" => SearchMacro::LinkedinSearch,
"instagram" => SearchMacro::InstagramSearch,
"tiktok" => SearchMacro::TiktokSearch,
"twitch" => SearchMacro::TwitchSearch,
_ => SearchMacro::GoogleSearch,
};
let mname = site_to_macro(&site);
let sname = site_name(&site);
info!("web_search: searching {} for '{}'", macro_name.site_name(), query);
info!("web_search: searching {} for '{}'", sname, query);
let session_key = format!("web_search_{}", std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs());
let tab = self.client.create_tab("gyandoot", &session_key, None).await
.map_err(|e| anyhow::anyhow!("Failed to create browser tab: {}", e))?;
let tab_id = tab.tab_id;
let result = self.client.search(&tab_id, "gyandoot", &macro_name, &query, Some(3000)).await
// Execute search — daemon auto-launches browser on first call
let result = self.client.search(mname, &query).await
.map_err(|e| anyhow::anyhow!("Search failed: {}", e))?;
let snap = self.client.snapshot(&tab_id, "gyandoot").await
// Get snapshot of results
let snap = self.client.snapshot(true).await
.map_err(|e| anyhow::anyhow!("Failed to get snapshot: {}", e))?;
// Close the tab to free resources
let _ = self.client.close_tab(&tab_id, "gyandoot").await;
let refs = snap.extract_refs();
let response = format!(
"## {} Search Results\n\n**Query:** {}\n**Page:** {}\n**Title:** {}\n\n**Snapshot content:**\n{}\n\n**Available element refs:** {:?}\n\n*Use `web_browse` to interact with specific results.*",
macro_name.site_name(),
sname,
query,
result.url,
result.title,
@@ -112,7 +141,7 @@ impl Tool for WebSearchTool {
}
}
/// Tool for browsing a specific URL using camofox-browser.
/// Tool for browsing a specific URL using camoufox daemon browser.
pub struct WebBrowseTool {
client: BrowserClient,
}
@@ -189,62 +218,52 @@ impl Tool for WebBrowseTool {
return Err(anyhow::anyhow!("url is required"));
}
let session_key = format!("web_browse_{}", std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs());
let tab = self.client.create_tab("gyandoot", &session_key, Some(&url)).await
.map_err(|e| anyhow::anyhow!("Failed to create browser tab: {}", e))?;
let tab_id = tab.tab_id;
match action.as_str() {
"navigate" => {
self.client.navigate(&url).await
.map_err(|e| anyhow::anyhow!("Navigation failed: {}", e))?;
}
"click" => {
if let Some(ref r#ref) = element_ref {
self.client.click(&tab_id, "gyandoot", r#ref).await
.map_err(|e| anyhow::anyhow!("Click failed: {}", e))?;
} else {
return Err(anyhow::anyhow!("element_ref is required for click action"));
}
let r#ref = element_ref.as_ref()
.ok_or_else(|| anyhow::anyhow!("element_ref is required for click action"))?;
self.client.click(r#ref).await
.map_err(|e| anyhow::anyhow!("Click failed: {}", e))?;
}
"type" => {
if let Some(ref r#ref) = element_ref {
if let Some(ref t) = text {
self.client.type_text(&tab_id, "gyandoot", r#ref, t, press_enter).await
.map_err(|e| anyhow::anyhow!("Type failed: {}", e))?;
}
} else {
return Err(anyhow::anyhow!("element_ref and text are required for type action"));
}
let r#ref = element_ref.as_ref()
.ok_or_else(|| anyhow::anyhow!("element_ref is required for type action"))?;
let t = text.as_ref()
.ok_or_else(|| anyhow::anyhow!("text is required for type action"))?;
self.client.type_text(r#ref, t, press_enter).await
.map_err(|e| anyhow::anyhow!("Type failed: {}", e))?;
}
"scroll" => {
self.client.scroll(&tab_id, "gyandoot", &direction, amount).await
self.client.scroll(&direction, amount).await
.map_err(|e| anyhow::anyhow!("Scroll failed: {}", e))?;
}
"back" => {
self.client.go_back(&tab_id, "gyandoot").await
self.client.go_back().await
.map_err(|e| anyhow::anyhow!("Go back failed: {}", e))?;
}
"forward" => {
self.client.go_forward(&tab_id, "gyandoot").await
self.client.go_forward().await
.map_err(|e| anyhow::anyhow!("Go forward failed: {}", e))?;
}
"refresh" => {
self.client.refresh(&tab_id, "gyandoot").await
self.client.refresh().await
.map_err(|e| anyhow::anyhow!("Refresh failed: {}", e))?;
}
_ => {} // already navigated via create_tab
_ => {}
}
let snap = self.client.snapshot(&tab_id, "gyandoot").await
let snap = self.client.snapshot(false).await
.map_err(|e| anyhow::anyhow!("Failed to get snapshot: {}", e))?;
let _ = self.client.close_tab(&tab_id, "gyandoot").await;
let refs = snap.extract_refs();
let current_url = if snap.url.is_empty() { &url } else { &snap.url };
let response = format!(
"## Page Snapshot\n\n**URL:** {}\n\n**Snapshot:**\n{}\n\n**Element refs:** {:?}\n\n*Use `web_browse` with action=\"click\" and element_ref to interact with elements.*",
url,
current_url,
snap.snapshot,
refs,
);
@@ -281,4 +300,11 @@ mod tests {
let params = tool.parameters();
assert!(params["required"].as_array().unwrap().contains(&"url".to_string().into()));
}
#[test]
fn test_site_to_macro() {
assert_eq!(site_to_macro("google"), "@google_search");
assert_eq!(site_to_macro("youtube"), "@youtube_search");
assert_eq!(site_to_macro("reddit"), "@reddit_search");
}
}