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).
122 lines
4.5 KiB
Rust
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(())
|
|
})
|
|
})?)
|
|
}
|