edgecrab-tools 0.11.0

Tool registry, ToolHandler trait, and 50+ tool implementations
//! # edgecrab-tools
//!
//! Tool registry (`ToolHandler` trait), toolset composition, and all tool
//! implementations. Uses `inventory` crate for compile-time registration.
//!
//! ```text
//!   edgecrab-tools
//!     ├── registry.rs     — ToolHandler trait, ToolRegistry, ToolContext
//!     ├── config_ref.rs   — AppConfigRef (lightweight config for tool context)
//!     ├── toolsets.rs      — CORE_TOOLS, acp_tools(), alias resolution
//!     └── tools/           — individual tool implementations (Phase 2.2+)
//! ```

#![deny(clippy::unwrap_used)]
#![cfg_attr(test, allow(clippy::unwrap_used))]
#![allow(clippy::result_large_err)]

pub mod approval_runtime;
pub mod artifact_spill;
pub mod budget_config;
mod command_interaction;
pub mod preview_grant;
pub use command_interaction::{command_invokes_screencapture, command_is_inspect_only};
pub mod browser_diagnostics;
pub mod config_ref;
pub mod copilot_model_normalize;
pub mod delegation_state;
pub mod dev_server;
pub mod edit_contract;
pub mod execution_fs;
pub mod execution_tmp;
pub mod fuzzy_match;
pub mod harness_gates;
pub mod isolated_worktree;
pub mod kanban_gating;
mod local_pty;
pub mod lsp_gate;
#[cfg(target_os = "macos")]
pub mod macos_permissions;
#[cfg(not(target_os = "macos"))]
#[path = "macos_permissions_stub.rs"]
pub mod macos_permissions;
pub mod mcp_auth;
pub mod mutation_turn_policy;
pub mod mutations;
pub mod path_utils;
pub mod process_table;
pub mod provider_factory;
pub mod provider_tracing;
pub mod read_tracker;
pub mod recovery_catalog;
pub mod registry;
pub mod schema_mode;
mod shell_syntax;
pub mod skills;
pub mod smart_approval;
pub mod structured_browser;
pub mod subagent_ids;
pub mod terminal_result;
pub mod tool_argument_pipeline;
pub mod tool_call_pipeline;
pub mod tool_input_examples;
pub mod tool_loop_guardrails;
pub mod tool_name_repair;
pub mod tool_progress_tail;
pub mod tool_schema_index;
pub mod tool_search_bm25;
pub mod tools;
pub mod toolsets;
pub mod vision_models;

/// Truncate `s` to at most `max_bytes` bytes, always stopping at a valid UTF-8
/// char boundary so that multi-byte / emoji characters are never split.
#[inline]
pub(crate) fn safe_truncate(s: &str, max_bytes: usize) -> &str {
    if s.len() <= max_bytes {
        return s;
    }
    let boundary = (0..=max_bytes)
        .rev()
        .find(|&i| s.is_char_boundary(i))
        .unwrap_or(0);
    &s[..boundary]
}

pub use artifact_spill::{
    SpillConfig, SpillContext, SpillOutcome, SpillSequence, SpillWritten, WEB_EXTRACT_INLINE_BYTES,
    WEB_SEARCH_INLINE_BYTES, detect_spill_without_read,
};
pub use browser_diagnostics::{
    BrowserDiagnosticsInput, BrowserDiagnosticsReport, UrlProbe, browser_diagnostics_doctor_detail,
    collect_browser_diagnostics, format_browser_diagnostics, probe_loopback_url,
};
pub use config_ref::AppConfigRef;
pub use edgecrab_security::approval::ApprovalMode;
pub use execution_fs::{ExecutionFilesystemView, describe_execution_filesystem};
pub use harness_gates::{
    HarnessAdvisorySignals, HarnessBuildInput, HarnessSnapshot, OracleGateFailure,
    TerminalMutationToolError, UnresolvedMutationFailure, build_harness_snapshot,
    is_browser_esm_artifact, oracle_command_for_path, terminal_mutation_tool_error,
};
pub use lsp_gate::{
    LspEditContext, LspGate, LspWriteHook, ToolDiagnostic, attach_post_write_diagnostics,
};
pub use mutations::{
    MutationKind, MutationRecord, MutationTurnState, extract_file_mutation_targets,
    file_mutation_result_landed, render_failure_footer, render_success_footer,
    render_success_footer_width,
};
pub use process_table::ProcessTable;
pub use provider_factory::{build_copilot_provider, create_provider_for_model};
pub use provider_tracing::{llm_tracing_enabled, wrap_provider_with_tracing};
pub use recovery_catalog::tools_to_materialize_from_error_json;
pub use registry::{
    CapabilityGrants, SideEffect, SubAgentResult, SubAgentRunner, ToolContext, ToolHandler,
    ToolProgressUpdate, ToolRegistry, build_wire_llm_definitions, to_llm_definitions,
    to_llm_definitions_with_materialized, to_llm_definitions_with_mode,
};
pub use schema_mode::{
    ToolSchemaMode, compact_tool_schema, prepare_schemas_for_mode, resolve_effective_schema_mode,
};
pub use smart_approval::handle_approvals_slash;
pub use structured_browser::{
    ContentClass, StructuredBrowserResult, browser_content_class, classify_browser_content,
    parse_structured_browser_result, structured_browser_nav_succeeded, url_is_chrome_error,
};
pub use terminal_result::{ParsedTerminalResult, parse_terminal_result, terminal_result_succeeded};
pub use tool_argument_pipeline::{
    canonical_tool_args_json, parse_tool_arguments_json, prepare_parsed_tool_arguments,
    repair_stream_tool_arguments, repair_tool_arguments,
};
pub use tool_call_pipeline::{
    MAX_INVALID_TOOL_RETRIES, PreparedToolCall, UNKNOWN_TOOL_SAMPLE_LIMIT,
    classify_unknown_tool_batch, is_tool_registered, normalize_incoming_tool_call,
    prepare_tool_call, repair_tool_call_arguments_for_api, sanitize_assistant_tool_calls_for_api,
    unknown_tool_error_response, unknown_tool_names, unknown_tool_recovery_sample,
    unknown_tool_search_query,
};
pub use tool_input_examples::{
    MAX_INPUT_EXAMPLES_PER_TOOL, input_examples_for_names, input_examples_for_tool,
};
pub use tool_name_repair::{
    ResolvedToolName, fuzzy_match_tool_name, repair_tool_name, resolve_tool_call_name,
};
pub use tool_schema_index::{
    AUTO_INDEXED_TOOL_COUNT_THRESHOLD, DEFAULT_MAX_MATERIALIZED_TOOLS, DEFAULT_PREFETCH_LIMIT,
    MAX_TOOLSET_MATERIALIZE, MaterializeOutcome, MaterializeSchemaStyle, MaterializedToolSet,
    TOOL_SEARCH_NAME, deferred_names_for_toolset, deferred_tool_error_response,
    format_deferred_index, is_deferred_not_on_wire, materialize_tool_names, partition_schemas,
    read_materialized_set, wire_partition_counts, wire_schemas,
};
pub use tool_search_bm25::{
    CatalogEntry, build_deferred_catalog, build_registry_catalog, looks_like_create_file_intent,
    prefetch_tools_for_user_message, search_deferred_catalog,
};
pub use tools::checkpoint::{
    AutoPruneResult, CheckpointConfig, CheckpointManager, PruneCounts, RollbackOutcome,
    checkpoint_new_turn, clear_all, clear_legacy, format_checkpoint_list, format_store_status,
    handle_rollback_command, maybe_auto_prune_checkpoints, prune_checkpoints, store_status,
};
pub use tools::computer_use::permissions_status;
pub use tools::computer_use::{
    COMPUTER_USE_GUIDANCE_COMPACT, ComputerUseReportContext, ComputerUseStatusConfig,
    collect_snapshot, computer_command_overlay, computer_command_usage, computer_status_one_liner,
    format_computer_command, format_computer_enable_result, format_computer_setup_report,
    install_cua_driver, is_computer_use_toolset_active, open_computer_use_settings,
    parse_install_args, provider_accepts_multimodal_tool_result, render_install_report,
    should_route_capture_to_aux_vision,
};
pub use tools::todo::TodoStore;
pub use tools::web::{
    ResolvedChain, SearchResult, WebChainEditor, WebPickerCatalog, WebSearchBackend,
    WebSearchChainUpdate, WebSearchTool, WebSectionUpdate, active_search_section_override,
    capability_label, chain_summary_after_save, clear_web_search_chain_in_config,
    clear_web_search_section_overrides, clear_web_section_overrides, collect_web_diagnostics,
    default_chain_for_backend, effective_web_search_config, ensure_web_search_config_coherence,
    ensure_web_search_config_coherence_at, format_extract_doctor_detail, format_picker_label,
    format_saved_chain_summary, format_search_chain_summary, format_search_doctor_detail,
    format_web_search_result_count, format_web_search_status_line, format_web_setup_report,
    gateway_web_command_reply, get_web_search_backend, list_web_search_backends,
    load_web_search_config_from_disk, load_web_search_config_from_path,
    migrate_legacy_search_override, persist_search_backend_as_chain, persist_search_chain_order,
    persist_search_chain_with_timeout, persist_web_backend_in_config,
    persist_web_search_chain_in_config, persist_web_section_in_config, print_provider_detail_cli,
    provider_detail_lines, register_web_search_backend, render_web_dashboard, reset_web_to_auto,
    search_override_warning, search_section_override_from_path, summarize_web_search_backend,
    web_command_overlay, web_command_usage, web_menu_status_hint, web_provider_picker_rows,
    web_search_is_available, web_search_result_note, web_status_one_liner,
};
pub use toolsets::{
    CORE_TOOLS, HONCHO_TOOLS, INDEXED_HOT_TOOLS, LSP_TOOLS, MCP_EXTENDED_TOOLS, MOA_TOOLS,
    RESEARCH_EXTRA_TOOLS, acp_tools, is_acp_tool, resolve_active_toolsets, resolve_alias,
    toolset_covered_by, toolset_enabled,
};

#[cfg(test)]
pub(crate) mod test_support {
    use std::path::Path;
    use std::sync::{Mutex, MutexGuard};

    use tempfile::TempDir;

    static EDGECRAB_HOME_LOCK: Mutex<()> = Mutex::new(());

    pub(crate) struct TestEdgecrabHome {
        _guard: MutexGuard<'static, ()>,
        dir: TempDir,
        previous: Option<std::ffi::OsString>,
    }

    impl TestEdgecrabHome {
        pub(crate) fn new() -> Self {
            let guard = match EDGECRAB_HOME_LOCK.lock() {
                Ok(g) => g,
                Err(poisoned) => poisoned.into_inner(),
            };
            let dir = TempDir::new().expect("tempdir");
            let previous = std::env::var_os("EDGECRAB_HOME");
            // SAFETY: serialized by EDGECRAB_HOME_LOCK for the guard lifetime.
            unsafe { std::env::set_var("EDGECRAB_HOME", dir.path()) };
            Self {
                _guard: guard,
                dir,
                previous,
            }
        }

        pub(crate) fn path(&self) -> &Path {
            self.dir.path()
        }
    }

    impl Drop for TestEdgecrabHome {
        fn drop(&mut self) {
            match &self.previous {
                Some(previous) => {
                    // SAFETY: serialized by EDGECRAB_HOME_LOCK for the guard lifetime.
                    unsafe { std::env::set_var("EDGECRAB_HOME", previous) };
                }
                None => {
                    // SAFETY: serialized by EDGECRAB_HOME_LOCK for the guard lifetime.
                    unsafe { std::env::remove_var("EDGECRAB_HOME") };
                }
            }
        }
    }
}