Files
Kigi-CLI/crates/codegen/kigi-shell/src/agent/init.rs
T
ZacharyZhang-NY 6f31415ed6 §9 acceptance: grep-zero sweep — every internal x.ai/grok identifier renamed
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.
2026-07-18 02:48:46 -04:00

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",
);
});
}