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.
167 lines
6.1 KiB
Rust
167 lines
6.1 KiB
Rust
#![allow(
|
|
unused_imports,
|
|
unused_variables,
|
|
unused_mut,
|
|
unreachable_code,
|
|
dead_code
|
|
)]
|
|
//! Core workspace library: FS, VCS, permissions, tool config, and subsystem wiring.
|
|
pub mod activity;
|
|
pub mod capability;
|
|
pub mod channel;
|
|
pub mod config;
|
|
pub mod discovery;
|
|
pub mod envrc;
|
|
pub mod error;
|
|
pub mod file_system;
|
|
pub mod folder_trust;
|
|
pub mod foreign_sessions;
|
|
pub mod fs_notify;
|
|
pub mod handle;
|
|
pub mod permission;
|
|
pub mod project_config;
|
|
pub mod session;
|
|
pub mod status_config;
|
|
pub use status_config::StatusConfig;
|
|
pub mod trust;
|
|
pub mod util;
|
|
pub mod workspace_ops;
|
|
pub mod worktree;
|
|
pub use capability::CapabilityMode;
|
|
pub use channel::{TransportCallResult, TransportContext, TransportError, TransportNotification};
|
|
pub use config::{
|
|
AgentSessionConfig, DEFAULT_EVENT_BUFFER_CAPACITY, HookSourceConfig, IsolationMode,
|
|
MemoryConfig, SessionContextFactory, SessionTerminalBackend, WorkspaceConfig,
|
|
};
|
|
pub use error::{WorkspaceError, WorkspaceResult};
|
|
pub use file_system::*;
|
|
pub use handle::WorkspaceHandle;
|
|
pub use kigi_hunk_tracker::HunkTrackerHandle;
|
|
pub use kigi_workspace_types::WorkspaceEvent;
|
|
pub use permission::*;
|
|
pub use session::{WorkspaceSession, WorkspaceShared};
|
|
pub use session::{file_state, git, jj};
|
|
pub use workspace_ops::{WorkspaceOp, WorkspaceOps};
|
|
/// Zero-init every workspace metric family so idle panels render a `0` baseline
|
|
/// instead of "No data". Idempotent; call once at startup.
|
|
pub fn init_metrics() {
|
|
handle::init_metrics();
|
|
session::swap_policy::init_metrics();
|
|
}
|
|
/// Crate-wide lock serializing every test that mutates the process-global
|
|
/// environment (`KIGI_SHARE_DIR`, `HOME`, …). nextest isolates each test in its own
|
|
/// process, but `cargo test --lib` shares ONE process across threads, so
|
|
/// per-module locks don't serialize cross-module — a peer test in another module
|
|
/// can clobber `KIGI_SHARE_DIR` mid-test. A single shared lock (used by every
|
|
/// env-mutating test module) is required for that single-process run to be
|
|
/// race-free.
|
|
#[cfg(test)]
|
|
pub(crate) static ENV_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
|
/// Crate-shared RAII guard for a single process env var in tests: sets (or
|
|
/// unsets) it on construction and restores the prior value on drop. The ONE
|
|
/// generic env-var guard for the whole crate (replaces the per-module copies).
|
|
///
|
|
/// Hold it together with [`ENV_TEST_LOCK`] for the test's lifetime, acquiring
|
|
/// the lock FIRST so it drops LAST — the env restore (this guard) then runs
|
|
/// before the lock releases, so no peer test observes the temporary value.
|
|
#[cfg(test)]
|
|
pub(crate) struct TestEnvGuard {
|
|
key: &'static str,
|
|
prev: Option<std::ffi::OsString>,
|
|
}
|
|
#[cfg(test)]
|
|
impl TestEnvGuard {
|
|
/// Set `key` to `val`, restoring the prior value on drop.
|
|
pub(crate) fn set(key: &'static str, val: &std::path::Path) -> Self {
|
|
let prev = std::env::var_os(key);
|
|
unsafe { std::env::set_var(key, val) };
|
|
Self { key, prev }
|
|
}
|
|
/// Unset `key`, restoring the prior value on drop.
|
|
pub(crate) fn unset(key: &'static str) -> Self {
|
|
let prev = std::env::var_os(key);
|
|
unsafe { std::env::remove_var(key) };
|
|
Self { key, prev }
|
|
}
|
|
}
|
|
#[cfg(test)]
|
|
impl Drop for TestEnvGuard {
|
|
fn drop(&mut self) {
|
|
match self.prev.take() {
|
|
Some(prev) => unsafe { std::env::set_var(self.key, prev) },
|
|
None => unsafe { std::env::remove_var(self.key) },
|
|
}
|
|
}
|
|
}
|
|
/// Holds [`ENV_TEST_LOCK`] AND a set of [`TestEnvGuard`]s as ONE value, so a
|
|
/// test (or fixture) can return/bind it however it likes and still be correct.
|
|
///
|
|
/// Struct fields drop in DECLARATION order, so `_env` (declared first) restores
|
|
/// every env var BEFORE `_lock` (declared last) releases the lock — making the
|
|
/// "restore before unlock" invariant compile-enforced, not convention-dependent
|
|
/// (a single `let _ = LockedTestEnv::lock()…;` binding can't reorder it).
|
|
///
|
|
/// Acquire the lock first via [`lock`](Self::lock), then mutate env under it with
|
|
/// the chained [`set`](Self::set) builder.
|
|
#[cfg(test)]
|
|
pub(crate) struct LockedTestEnv {
|
|
_env: Vec<TestEnvGuard>,
|
|
_lock: std::sync::MutexGuard<'static, ()>,
|
|
}
|
|
#[cfg(test)]
|
|
impl LockedTestEnv {
|
|
/// Acquire [`ENV_TEST_LOCK`] (held until this value drops).
|
|
pub(crate) fn lock() -> Self {
|
|
Self {
|
|
_env: Vec::new(),
|
|
_lock: ENV_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()),
|
|
}
|
|
}
|
|
/// Set `key` to `val` under the held lock, restoring the prior value on drop.
|
|
///
|
|
/// Intended for DISTINCT keys; the restore order across repeated `set`s of
|
|
/// the SAME key is unspecified (guards restore in insertion order).
|
|
pub(crate) fn set(mut self, key: &'static str, val: &std::path::Path) -> Self {
|
|
self._env.push(TestEnvGuard::set(key, val));
|
|
self
|
|
}
|
|
}
|
|
#[cfg(test)]
|
|
mod init_metrics_tests {
|
|
/// `init_metrics()` is idempotent (a double call must not panic on
|
|
/// re-register) and populates a `0` baseline series for each family so
|
|
/// panels render `0` instead of "No data".
|
|
#[test]
|
|
fn init_metrics_is_idempotent_and_registers_baselines() {
|
|
super::init_metrics();
|
|
super::init_metrics();
|
|
let families = prometheus::gather();
|
|
let has = |name: &str, want: &[(&str, &str)]| {
|
|
families
|
|
.iter()
|
|
.filter(|mf| mf.name() == name)
|
|
.flat_map(|mf| mf.get_metric())
|
|
.any(|m| {
|
|
want.iter().all(|(k, v)| {
|
|
m.get_label()
|
|
.iter()
|
|
.any(|l| l.name() == *k && l.value() == *v)
|
|
})
|
|
})
|
|
};
|
|
assert!(has(
|
|
"kigi_workspace_toolset_swap_rejected_total",
|
|
&[("reason", "turn_active"), ("trigger", "update_tool_config")]
|
|
));
|
|
assert!(has(
|
|
"kigi_workspace_rewind_checkpoint_capture_total",
|
|
&[("domain", "fs"), ("outcome", "completed")]
|
|
));
|
|
assert!(
|
|
families
|
|
.iter()
|
|
.any(|mf| mf.name() == "kigi_workspace_terminal_backend_orphaned_total")
|
|
);
|
|
}
|
|
}
|