lattice_ai/install.rs
1//! AI-1b / AG‑4: the crate-owned `install(boot)` entry point.
2//!
3//! After the AG‑4 fold, `lattice-ai` owns both agent transports, so the
4//! single host-facing `lattice_ai::install(&mut boot)` line wires **both**:
5//! the ACP agent client (`install_acp`, below) and the MCP IDE peer
6//! (`crate::mcp::install`). The host's Phase-B install list holds one line
7//! and zero host internals (no `Editor::` method, no host `Effect`/`Action`
8//! variant, no `Editor` field for the logger). AG‑5 relocates `install_acp`
9//! under `crate::acp` and puts each call behind its `#[cfg(feature = …)]`.
10//!
11//! The `:ai-log` picker that OPENS a buffer is a later task; `install_acp`
12//! only wires the producer side (`AiLogger`), the supervisor, the ex-commands,
13//! and `AiLogMode` (which seeds/streams into a buffer once one is opened by
14//! name).
15
16use std::sync::Arc;
17
18use lattice_config::ConfigRegistry;
19use lattice_mode::SubsystemBoot;
20
21use lattice_agent::{AiLogLevel, AiLogMode, AiLogger};
22
23use crate::commands::register_ai_log_command;
24
25/// Wire the AI subsystem into the editor at boot. The transport-neutral log
26/// substrate (`AiLogger` + `AiLogMode` + `:ai-log`) is always installed; each
27/// agent transport is wired only when its feature is enabled.
28pub fn install(boot: &mut impl SubsystemBoot) {
29 let logger = install_ai_log(boot);
30
31 // The v1 opencode integration: `:opencode` runs opencode's TUI in a
32 // terminal buffer. Always wired (no transport feature) -- it's the primary
33 // opencode experience. The `acp` adapter below is the alternative
34 // buffer-conversation path (`:opencode-acp`).
35 crate::opencode::install(boot);
36
37 // The v1 pi integration: `:pi` runs pi's TUI in a terminal buffer.
38 // Always wired (no transport feature) -- same policy as opencode.
39 crate::pi::install(boot);
40
41 #[cfg(feature = "acp")]
42 crate::acp::install::install(boot, &logger);
43
44 #[cfg(feature = "mcp")]
45 crate::mcp::install::install(boot);
46
47 // `AiLogger` is a port-level service (`AiLogMode`'s `on_activate` reads it
48 // via `ctx.service::<AiLogger>()`, as does the `:ai-log` picker) -- register
49 // it regardless of which transport(s) produce records. This consumes the
50 // logger; the ACP supervisor above took a clone.
51 boot.register_service::<AiLogger>(logger);
52}
53
54/// Port-level: construct the `AiLogger` (seeded from the `ai.*` config options,
55/// registered Phase-A before this Phase-B `install` runs), wire its event
56/// publisher, register the `AiLogMode` major, and register the transport-neutral
57/// `:ai-log` command. Returns the logger for the ACP supervisor to clone.
58fn install_ai_log(boot: &mut impl SubsystemBoot) -> AiLogger {
59 let logger = AiLogger::with_defaults();
60 if let Some(config) = boot.service::<Arc<ConfigRegistry>>() {
61 // ai.log_level -> default min level.
62 if let Some(level_str) = config.get_typed::<lattice_config::core_options::AiLogLevel>()
63 && let Some(level) = AiLogLevel::parse(&level_str)
64 {
65 logger.set_default_level(level);
66 }
67 // ai.log = false -> disable capture (cap-0 rings drop every record).
68 if let Some(enabled) = config.get_typed::<lattice_config::core_options::AiLog>()
69 && !*enabled
70 {
71 logger.set_default_capacity(0);
72 }
73 }
74
75 // Publisher: every append -> runtime bus (AiLogMode's drain task subscribes
76 // to refresh open `*ai:<provider>:<index>*` buffers).
77 let bus = boot.event_bus().clone();
78 logger.set_event_publisher(Arc::new(move |event| bus.publish_typed(event)));
79
80 // Register the AiLogMode major (mirrors `register_lsp_log_modes`).
81 boot.modes_mut()
82 .register(AiLogMode)
83 .expect("ai-log-mode register");
84
85 // The transport-neutral `:ai-log` command (no ACP handle needed).
86 register_ai_log_command(boot.commands_mut());
87
88 logger
89}