Files
Kigi-CLI/crates/codegen/kigi-tui/src/acp/spawn.rs
T
ZacharyZhang-NY a02b555e66 docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
2026-07-23 16:55:39 -04:00

122 lines
4.5 KiB
Rust

//! Agent spawning — creates the agent process and ACP channels.
//!
//! Only KigiShell (in-process) mode is supported; subprocess and remote modes
//! can be added later if needed.
use std::rc::Rc;
use std::thread;
use anyhow::Result;
use tokio_util::sync::CancellationToken;
use kigi_acp_lib::{
AcpAgentChannel, AcpClientChannel, AcpClientTx, AcpGatewayReceiver, AcpGatewaySender,
acp_channels,
};
use kigi_shell::{
agent::{MvpAgent, config::Config as AgentConfig, models::RefreshStrategy},
auth::AuthManager,
util::kigi_home::kigi_home,
};
/// Result of spawning a child agent.
pub struct SpawnedAgent {
/// Kept alive so the thread isn't detached. Will be used for graceful shutdown.
pub _thread_handle: thread::JoinHandle<Result<()>>,
pub channel: AcpClientChannel,
pub cancel: CancellationToken,
/// The agent's `AuthManager`, shared so pager-side consumers resolve the
/// same refreshing bearer as chat traffic.
pub auth_manager: std::sync::Arc<AuthManager>,
}
/// Spawn a KigiShell agent in a background thread.
pub async fn spawn_kigi_shell(
agent_config: AgentConfig,
cancel: &CancellationToken,
memory_config: Option<kigi_shell::config::MemoryConfig>,
) -> Result<SpawnedAgent> {
let auth_manager = std::sync::Arc::new(AuthManager::new(
&kigi_home(),
agent_config.kimi_code_config.clone(),
));
auth_manager.configure_refresher();
// Pause token refreshes across system sleep so an OIDC refresh can't
// straddle a suspend (which can revoke the refresh token and force
// re-login). No-op where the OS listener is unavailable.
auth_manager.start_system_power_listener();
// Best-effort refresh of managed policy before bootstrap reads it (repairs a wrong-identity/missing
// cache). Never errors — the OS-protected system/MDM layers still apply.
kigi_shell::managed_config::ensure_managed_policy_present(&auth_manager).await;
// Run the full bootstrap sequence: config resolution, process-level
// singletons (including `extract_bundled_files` which writes compiled-in
// skills to ~/.kigi/skills/), and model catalog construction.
let (agent_config, models_manager) =
kigi_shell::agent::init::bootstrap(&agent_config, &auth_manager, None)
.map_err(|e| anyhow::anyhow!(e))?;
models_manager
.list_models(RefreshStrategy::OnlineIfUncached)
.await;
let agent_cancel = cancel.child_token();
let (acp_client, acp_agent) = acp_channels();
// Clone before `auth_manager` is moved into the agent closure below, so the
// pager can share the same refreshing bearer.
let auth_manager_for_pager = auth_manager.clone();
let spawn_fn: Box<dyn FnOnce(AcpClientTx) -> Result<Rc<MvpAgent>> + Send + 'static> = {
Box::new(move |client_tx| {
let gateway = AcpGatewaySender::new(client_tx);
let mut agent =
MvpAgent::with_models(gateway, &agent_config, auth_manager, models_manager);
if let Some(mc) = memory_config {
agent.set_memory_config(mc);
}
Ok(Rc::new(agent))
})
};
let handle = spawn_agent_thread_direct(spawn_fn, acp_agent, agent_cancel.clone())?;
Ok(SpawnedAgent {
_thread_handle: handle,
channel: acp_client,
cancel: agent_cancel,
auth_manager: auth_manager_for_pager,
})
}
/// Spawn an agent in a dedicated thread with direct RPC dispatch.
///
/// The agent runs on a single-threaded tokio LocalSet runtime.
/// RPC requests go directly to the agent via Rc, bypassing simplex pipes.
fn spawn_agent_thread_direct(
spawn_agent: Box<dyn FnOnce(AcpClientTx) -> Result<Rc<MvpAgent>> + Send + 'static>,
channel: AcpAgentChannel,
cancel: CancellationToken,
) -> Result<thread::JoinHandle<Result<()>>> {
Ok(thread::Builder::new()
.name("acp-agent-worker".into())
.spawn(move || -> Result<()> {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
let local = tokio::task::LocalSet::new();
local.block_on(&rt, async move {
let client_tx = channel.tx.clone();
let agent_rc = spawn_agent(client_tx)?;
let gw_rx = AcpGatewayReceiver::new(channel.rx, agent_rc).with_tracing(true);
tokio::task::spawn_local(gw_rx.run());
tokio::task::yield_now().await;
cancel.cancelled().await;
anyhow::Result::Ok(())
})
})?)
}