car-ffi-common 0.24.1

Shared logic for FFI bindings (NAPI, PyO3) — JSON wrappers for verify, multi-agent, scheduler
#![recursion_limit = "512"]

//! Shared logic for FFI bindings (NAPI, PyO3).
//!
//! Pure Rust functions that accept JSON strings and return `Result<String, String>`.
//! Each FFI crate wraps these with its own error type (napi::Error, PyErr).

/// Initialize a `tracing_subscriber` honoring `RUST_LOG`. Idempotent
/// — subsequent calls return immediately. Used by FFI bindings to
/// surface `[voice] dropped …` and other crate-side warn/error lines
/// when callers set `RUST_LOG=car_voice=debug` (or similar).
///
/// Without this call, `tracing::warn!`/`error!` lines emitted from
/// car-voice / car-inference / etc. go nowhere in the FFI binary —
/// `#120`'s diagnostic dead-end. We can't `init` from a binding's
/// `[lib]` ctor (no static init in cdylib that's safe for tracing),
/// so each entry-point function calls this and the OnceLock guard
/// makes it cheap.
///
/// Falls back silently if another global subscriber is already set
/// (e.g., when the embedder loads `car-server` and the FFI module
/// in the same process).
pub fn ensure_tracing_subscriber() {
    use std::sync::OnceLock;
    static INIT: OnceLock<()> = OnceLock::new();
    INIT.get_or_init(|| {
        let filter = tracing_subscriber::EnvFilter::try_from_default_env()
            .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"));
        // try_init returns Err if a subscriber is already set; that's
        // fine, we just want at-most-one to win.
        let _ = tracing_subscriber::fmt()
            .with_env_filter(filter)
            .with_writer(std::io::stderr)
            .with_target(false)
            .try_init();
    });
}

pub mod a2ui;
pub mod accounts;
pub mod auth_token;
pub mod automation;
pub mod browser;
pub mod coder;
pub mod declagents;
pub mod discovery;
pub mod external_agents;
pub mod health;
pub mod integrations;
pub mod memory_path;
pub mod multi;
pub mod notifications;
pub mod parslee;
pub mod permissions;
pub mod projects;
pub mod proxy;
pub mod registry;
pub mod scheduler;
pub mod secrets;
pub mod supervisor;
pub mod verify;
pub mod vision;
pub mod voice;
pub mod workflow;
// (moved in v0.8.x phase 7.3) `a2a`, `a2a_dispatch`, `meeting`,
// and `voice_turn` modules used to live here. They were daemon-only
// (FFI bindings now proxy through `proxy::*` over WebSocket), and
// their car-engine / car-inference / car-a2a / car-meeting deps
// were the last reason this crate pulled those heavy graphs. The
// moved modules now live in `car-server-core/src/` next to the
// JSON-RPC handler that calls them. `a2a_dispatch` was deleted
// outright (no callers — the daemon's WS routes use their own
// `ServerState`-held dispatcher).

// (removed in v0.8) tracked_result_to_json — was a typed
// InferenceResult → JSON helper for FFI bindings. After phase 7.2,
// FFI binding methods build requests as raw json! and pass through
// the daemon's full InferenceResult JSON unmodified. The helper
// pulled car-inference (and transitively MLX) for what amounts to a
// thin field re-projection, so it's gone.

/// Process-wide serialization lock for tests that mutate environment
/// variables. The process environment is global, so any test that
/// `set_var`/`remove_var`s — or that *reads* an env-derived path while
/// another test mutates it — must hold this. Previously each module
/// (`auth_token`, `memory_path`, `proxy`) had its OWN lock, so e.g. an
/// `auth_token` test reading `HOME` raced a `memory_path` test writing
/// `HOME`; a panic under one lock also poisoned it and cascaded the
/// failure across that module's sibling tests. A single shared lock
/// removes the cross-module race, and recovering the guard on poison
/// (`into_inner`) stops one failure from cascading into many.
#[cfg(test)]
pub(crate) fn env_test_lock() -> std::sync::MutexGuard<'static, ()> {
    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
    LOCK.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}