Skip to main content

harn_hostlib/
lib.rs

1//! `harn-hostlib`: opt-in host builtins for code intelligence (tree-sitter,
2//! repo scanning, deterministic indexing) and tool execution (search, file
3//! I/O, git, process lifecycle, file watcher).
4//!
5//! This crate is the Rust home of two classes of optional host capabilities:
6//!
7//! 1. **Code intelligence** — `ast/`, `code_index/`, `scanner/`, `fs_watch/`.
8//! 2. **Deterministic tools** — `tools/` (search, fs, git, process).
9//!
10//! These don't belong inside `harn-vm` — pulling tree-sitter grammars,
11//! ripgrep, and `notify` into the VM would balloon the footprint of every
12//! pipeline that doesn't index host code. Instead, this crate exposes a
13//! single [`HostlibCapability`] trait. Embedders such as `harn-cli`'s ACP
14//! server) compose the modules they need via [`HostlibRegistry`] and wire
15//! the resulting builtins into the VM through [`harn_vm::Vm::register_builtin`]
16//! / [`harn_vm::Vm::register_async_builtin`].
17//!
18//! ## Status
19//!
20//! The AST, scanner, code-index, and deterministic-tool surfaces are
21//! implemented. `fs_watch/` still registers its public contract with
22//! [`HostlibError::Unimplemented`] handlers. Module names, method names,
23//! and JSON schemas under `schemas/` are the source of truth for hostlib
24//! request/response compatibility, so they must stay stable while module
25//! bodies evolve.
26
27#![deny(rust_2018_idioms)]
28#![warn(missing_docs)]
29
30#[cfg(feature = "ast")]
31pub mod ast;
32#[cfg(feature = "ast")]
33pub mod code_index;
34#[cfg(feature = "computer")]
35pub mod computer;
36pub mod embed;
37pub mod error;
38pub mod fs;
39pub mod fs_snapshot;
40pub mod fs_watch;
41pub mod host_conditions;
42pub mod host_env_custody;
43pub mod host_lease;
44pub mod host_lease_capability;
45pub mod process;
46mod process_liveness;
47pub mod sandbox;
48pub mod scanner;
49pub mod schemas;
50pub mod secret_store;
51pub mod session;
52#[cfg(feature = "terminal-session")]
53pub mod terminal_session;
54pub mod tools;
55pub mod verdict;
56
57mod json;
58mod registry;
59mod text;
60mod value_args;
61
62pub use error::HostlibError;
63pub use host_conditions::{
64    HostConditionObservation, HostConditionStatus, HostConditionsCapability,
65    HostConditionsSnapshot, HostConditionsSource, HostContentionQuestion, HostEnvironment,
66    InjectedHostConditionsSource, LocalHostConditionsSource, HOST_CONDITIONS_SCHEMA_VERSION,
67};
68pub use host_lease::{
69    HostLeaseAcquireReceipt, HostLeaseAcquireStatus, HostLeaseCargoExecutionContext,
70    HostLeaseDeferReason, HostLeaseDeferReceipt, HostLeaseError, HostLeaseExecutionContext,
71    HostLeaseHandle, HostLeaseMetadataUpdateReceipt, HostLeaseOperationKind, HostLeasePathIdentity,
72    HostLeasePriorityClass, HostLeaseProcessExit, HostLeaseReleaseReceipt, HostLeaseRenewReceipt,
73    HostLeaseRequest, HostLeaseResourceClass, HostLeaseResourceDefinition, HostLeaseResourceKey,
74    HostLeaseRunLaunchFailure, HostLeaseRunReceipt, HostLeaseRunReleaseOutcome,
75    HostLeaseRunStartFailure, HostLeaseRunState, HostLeaseState, HostLeaseStore,
76    DEFAULT_HOST_LEASE_DOMAIN, HOST_LEASE_ROOT_ENV,
77};
78pub use registry::{BuiltinRegistry, HostlibCapability, HostlibRegistry, RegisteredBuiltin};
79
80/// Handles retained from [`install_default_with_handles`] so embedders can
81/// warm or introspect capabilities out-of-band of the VM.
82pub struct DefaultHostlibHandles {
83    /// Shared code-index capability when the `ast` feature is enabled.
84    #[cfg(feature = "ast")]
85    pub code_index: code_index::CodeIndexCapability,
86}
87
88/// Convenience: build a `HostlibRegistry` populated with every capability
89/// the crate ships, register them on the supplied VM, and return the
90/// registry so callers can introspect (e.g. for schema-drift tests).
91///
92/// This is the canonical entry point for embedders that want the full
93/// hostlib surface; pick-and-choose embedders should construct
94/// [`HostlibRegistry`] directly. Embedders that need the retained
95/// [`code_index::CodeIndexCapability`] handle (for
96/// [`code_index::CodeIndexCapability::warm_session`]) should call
97/// [`install_default_with_handles`] instead.
98pub fn install_default(vm: &mut harn_vm::Vm) -> HostlibRegistry {
99    install_default_with_handles(vm).0
100}
101
102/// Like [`install_default`], but also returns retained capability handles.
103///
104/// The code-index handle shares the same [`code_index::SharedIndex`] cell
105/// installed into the VM, so a session-start
106/// [`code_index::CodeIndexCapability::warm_session`] populates the index
107/// visible to later agent turns.
108pub fn install_default_with_handles(
109    vm: &mut harn_vm::Vm,
110) -> (HostlibRegistry, DefaultHostlibHandles) {
111    let mut registry = HostlibRegistry::new();
112    let embed = embed::EmbedCapability::default();
113    let session = session::SessionCapability::with_embedder(embed.embedder().clone());
114    // The code-intelligence capabilities (`ast` + `code_index`) are only
115    // compiled when the `ast` feature is on. Lean clients that omit it get
116    // the deterministic tool surface without tree-sitter or any grammar.
117    #[cfg(feature = "ast")]
118    let code_index_handle = {
119        let code_index = code_index::CodeIndexCapability::new();
120        let handle = code_index.clone();
121        registry = registry
122            .with(ast::AstCapabilityWithCodeIndex::new(code_index.shared()))
123            .with(code_index);
124        handle
125    };
126    registry = registry
127        .with(scanner::ScannerCapability)
128        .with(embed)
129        .with(session)
130        .with(fs::FsCapability)
131        .with(fs_snapshot::FsSnapshotCapability)
132        .with(fs_watch::FsWatchCapability)
133        .with(tools::ToolsCapability)
134        .with(secret_store::SecretStoreCapability)
135        .with(verdict::VerdictCapability)
136        .with(host_conditions::HostConditionsCapability::default())
137        .with(host_lease_capability::HostLeaseCapability);
138    #[cfg(feature = "terminal-session")]
139    {
140        registry = registry.with(terminal_session::TerminalSessionCapability::new());
141    }
142    // Computer use (screenshot + mouse/keyboard) is opt-in at the feature
143    // level AND default-deny at runtime: even with `computer-local` compiled,
144    // the backend is a NullBackend unless `BURIN_COMPUTER_USE_TRANSPORT` is
145    // explicitly set to `local` (or `helper`/`remote`). In the product it is
146    // gated again by an off-by-default setting. Registering the builtins is
147    // therefore harmless when unarmed — every call fails with an explanatory
148    // message until the transport is explicitly chosen.
149    #[cfg(feature = "computer")]
150    {
151        registry = registry.with(computer::ComputerUseCapability::new());
152    }
153    registry.register_into_vm(vm);
154    // Compatibility stub: typed `HarnessTools` replaced the thread-local
155    // `hostlib_enable` gate. Legacy ambient callers still invoke
156    // `hostlib_enable("tools:deterministic")` before hostlib_* builtins; keep
157    // that spelling as a no-op so dispatch does not fall through to an
158    // embedder host bridge.
159    vm.register_builtin("hostlib_enable", |_args, _out| Ok(harn_vm::VmValue::Nil));
160    let handles = DefaultHostlibHandles {
161        #[cfg(feature = "ast")]
162        code_index: code_index_handle,
163    };
164    (registry, handles)
165}