The PRD's first acceptance gate now holds: grep -RinE '\bx\.ai\b|grok' crates/ --include='*.rs' → 0 matches (exempt: NOTICE and third-party license archives, README provenance, and the required 'Based on Grok Build Open Source' attribution, now sourced from version_attribution.txt). Wire-visible renames (both sides in this repo, changed in lockstep): - Auth method id 'grok.com' → 'kimi-code' (AuthMethodKind::KimiCode). - Every x.ai/* and _x.ai/* ACP ext method and meta key → kigi/* / _kigi/* (~200 names; grokShell → kigiShell). Session-file replay keeps a read-side alias for the legacy '_x.ai/session/update' method so existing updates.jsonl histories load; writes emit only the new name (both directions test-pinned). - Agent types grok-build* → kigi* with a documented legacy-prefix alias at resolution time so persisted sessions keep resolving. - ToolNamespace/BuiltinAgentName GrokBuild* → Kigi* (wire snake_case kigi/kigi_concise/kigi_hashline; schema regenerated); grok_build implementation dirs renamed to kigi*. - x-grok-* headers → x-kigi-*, __GROK_* sentinels → __KIGI_*, themes grokday/groknight → kigiday/kiginight (old persisted values fall back to the default theme), web_fetch allowlist xAI hosts → kimi.com + moonshot platforms, changelog CDN → this repo, grok-build changelog archives deleted. - BYOK default endpoint removed: [endpoints] api_base_url is now truly optional with NO default — consumers fail fast with the flag name when unset (no silent x.ai egress). Mock harnesses inject it explicitly. - System-prompt identity fixed: 'released by xAI' → 'an unofficial community CLI for Kimi' (template + regenerated encrypted form). Also repaired pre-existing grok-era test debt found by the sweep: the stale trace_classify default-model pin, the grok-pager UA label test, pty-harness stale-binary reuse and non-hermetic moonshot routing (a PTY test could previously reach the real api.moonshot.cn), and the outdated oauth fixture scope key. Gates: §9 grep 0; fmt clean; workspace check/clippy 0/0 (-D warnings); FULL cargo test --workspace: 234 suites, 21,961 passed, 0 failed; deny advisories ok.
127 lines
4.9 KiB
Rust
127 lines
4.9 KiB
Rust
//! Agent bootstrap and lifecycle hooks.
|
|
//!
|
|
//! [`bootstrap`] runs the full init sequence (config resolution, process
|
|
//! singletons, model catalog) and returns a resolved config + `ModelsManager`.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use indexmap::IndexMap;
|
|
|
|
use crate::agent::config::{self, Config as AgentConfig, ModelEntry};
|
|
use crate::agent::models::ModelsManager;
|
|
use crate::auth::AuthManager;
|
|
use crate::config::StorageMode;
|
|
|
|
/// Resolve config, init process singletons, build the model catalog.
|
|
///
|
|
/// The `ModelsManager` is `Clone + Send`, so callers that need a handle
|
|
/// for the config watcher can clone it before passing it to
|
|
/// `MvpAgent::with_models`.
|
|
pub fn bootstrap(
|
|
cfg: &AgentConfig,
|
|
auth_manager: &Arc<AuthManager>,
|
|
prefetched: Option<IndexMap<String, ModelEntry>>,
|
|
) -> Result<(AgentConfig, ModelsManager), String> {
|
|
// Fail closed before any policy is read: a tampered managed policy must not run unmanaged.
|
|
crate::managed_config::managed_policy_gate()?;
|
|
let cfg = resolve_config(cfg, auth_manager);
|
|
cfg.validate_model_filters()?;
|
|
init_process(&cfg, auth_manager);
|
|
let models_manager = ModelsManager::from_config(&cfg, prefetched, auth_manager.clone())?;
|
|
|
|
// Refresh on every auth refresh — the FSEvents watcher can silently die after
|
|
// macOS sleep, stranding the catalog on bundled defaults.
|
|
models_manager.start_auth_refresh_watcher(auth_manager.refresh_notifier());
|
|
|
|
Ok((cfg, models_manager))
|
|
}
|
|
|
|
/// Print a `bootstrap`/`MvpAgent::new` config error and exit (process boundary).
|
|
///
|
|
/// Restores native stderr first: a managed-policy refusal on the ACP/server path reaches here
|
|
/// while fd 2 may still point at the `/dev/null` the TUI's `redirect_native_stderr()` set, which
|
|
/// would swallow the message. No-op when stderr was never redirected (headless).
|
|
pub(crate) fn exit_on_config_error<T>(e: String) -> T {
|
|
kigi_tty_utils::restore_native_stderr();
|
|
eprintln!("\nConfiguration error:\n\n {e}\n");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
/// Config transform: apply managed settings, fetch remote settings,
|
|
/// resolve storage mode.
|
|
fn resolve_config(cfg: &AgentConfig, auth_manager: &AuthManager) -> AgentConfig {
|
|
let mut cfg = cfg.clone();
|
|
|
|
if let Ok(layers) = crate::config::ConfigLayers::load()
|
|
&& layers.has_managed()
|
|
{
|
|
let origins = crate::config::config_origins(&layers);
|
|
let managed_keys: Vec<&str> = origins
|
|
.iter()
|
|
.filter(|(_, s)| matches!(s, config::ConfigSource::ManagedConfig))
|
|
.map(|(k, _)| k.as_str())
|
|
.collect();
|
|
if !managed_keys.is_empty() {
|
|
tracing::info!(keys = ?managed_keys, "managed_config.toml fields");
|
|
}
|
|
}
|
|
|
|
let managed_enforced = crate::config::apply_managed_settings_features(&mut cfg);
|
|
let requirements_enforced = crate::config::apply_requirements(&mut cfg);
|
|
|
|
for e in managed_enforced.iter().chain(&requirements_enforced) {
|
|
tracing::info!(field = %e.path, value = %e.value, source = %e.source, "policy override");
|
|
}
|
|
|
|
crate::util::config::sync_campaign_fields(&mut cfg);
|
|
crate::agent::config::apply_remote_settings_side_effects(cfg.remote_settings.as_ref());
|
|
|
|
// env var > remote settings > Local. Skip remote settings for Generic (kigi -p, subagents).
|
|
if cfg.storage_mode == StorageMode::Local
|
|
&& cfg.mode != crate::agent::config::AgentMode::Generic
|
|
{
|
|
cfg.storage_mode = StorageMode::resolve(None, cfg.remote_settings.as_ref());
|
|
}
|
|
// Writeback talks to the code backend; requires a Kimi Code session.
|
|
if cfg.storage_mode == StorageMode::Writeback
|
|
&& !auth_manager.current().is_some_and(|a| a.is_session_auth())
|
|
{
|
|
tracing::info!("Writeback is disabled: requires auth with kimi.com");
|
|
cfg.storage_mode = StorageMode::Local;
|
|
}
|
|
|
|
if let Some(rs) = cfg.remote_settings.as_ref()
|
|
&& let Some(v) = rs.path_not_found_hints
|
|
{
|
|
cfg.path_not_found_hints = v;
|
|
}
|
|
|
|
cfg
|
|
}
|
|
|
|
/// Initialize process-level singletons (deployment sync, bundled files).
|
|
/// `Once`-guarded: only the first call takes effect.
|
|
fn init_process(cfg: &AgentConfig, auth_manager: &AuthManager) {
|
|
use std::sync::Once;
|
|
static INIT: Once = Once::new();
|
|
INIT.call_once(|| {
|
|
if !cfg!(test) {
|
|
// Clear a logged-out team's files before the background sync runs.
|
|
crate::managed_config::clear_orphan();
|
|
crate::managed_config::spawn_sync(tokio_util::sync::CancellationToken::new());
|
|
}
|
|
|
|
let kigi_home = crate::util::kigi_home::kigi_home();
|
|
crate::builtin::extract_bundled_files(&kigi_home);
|
|
|
|
let feedback = cfg.resolve_feedback();
|
|
let feedback_url = cfg.endpoints.resolve_feedback_base_url();
|
|
tracing::info!(
|
|
feedback = %feedback,
|
|
feedback_url = %feedback_url,
|
|
feedback_url_custom = cfg.endpoints.feedback_base_url.is_some(),
|
|
"data capture config resolved",
|
|
);
|
|
});
|
|
}
|