M2 audit: excise the Computer Hub stack — Kigi's last remote-cloud surface
Removed root-and-branch for the zero-egress guarantee (the hub was xAI's remote-workspace/cloud-sandbox service): - Crates deleted: kigi-computer-hub-core, kigi-computer-hub-sdk, kigi-computer-hub-mcp-adapter, kigi-workspace-client (hub-proxied workspace RPC client), and kigi-tracing (its sole network path was the OTLP gRPC exporter; zero consumers remained). kigi-tracing-macros (purely local) stays. - kigi-workspace: every hub surface deleted — hub server/channel/auth, HITL-over-hub permissions, donation/metrics pumps, file upload RPCs, hub tool-snapshot merge (resolve pipeline is MCP-only now), WorkspaceOps::Proxy. Local worktrees, sessions, leader IPC, MCP, and the ACP permission prompt path are untouched; LocalRegistry re-homed into kigi-tool-runtime on the existing ToolDyn types so in-process tool dispatch is unchanged. - kigi-shell: leader workspace-exposure control surface (incl. the wss://computer-hub... URL), [hub] config, ObservabilityBridge, hub WebSocket proxy, dead OTLP config knobs. ClientMode::Headless (never constructed) removed. - kigi-tui/bin: hidden `kigi workspace` command removed (`kigi worktree` stays). - Renames: --xai-api-base-url → --api-base-url / KIGI_API_BASE_URL / [endpoints] api_base_url (serde alias keeps old configs working; the flag feeds BYOK/custom-endpoint routing, not main inference); grok_version → kigi_version in inspect/models-cache/trace metadata (old caches self-heal via version-mismatch refetch). - Dependency tree: dropped fastrace*, opentelemetry-otlp/http/proto, tokio-tungstenite from the workspace; fixed the 4 real useless_format violations the fastrace lint allowance was masking and removed the allowance. - marketplaceAllowlist kept: it gates the LOCAL plugin-marketplace feature, not an xAI service. Known §9 leftover (deliberate, for the M3 sweep): the BYOK default base URL string. Gates: workspace check/clippy 0/0, fmt, deny ok; suites green (workspace 1042, shell 4918, tui 6634, tools 2608, tool-runtime 47, mcp 154).
This commit is contained in:
@@ -37,7 +37,6 @@ use kigi_tui::app::{
|
||||
AgentCmd, Command, LeaderMgmtArgs, LeaderMgmtCommand, LeaderTargetArgs, PagerArgs,
|
||||
resolve_use_leader,
|
||||
};
|
||||
use kigi_tui::app::{WorkspaceMgmtArgs, WorkspaceMgmtCommand, WorkspaceStartArgs};
|
||||
use kigi_tui::client_identity::PAGER_CLIENT_VERSION;
|
||||
use kigi_update::{UpdateConfig, auto_update, enforce_minimum_version_or_exit};
|
||||
use std::env;
|
||||
@@ -48,8 +47,8 @@ fn apply_agent_endpoint_args(agent_args: &kigi_tui::app::AgentArgs, config: &mut
|
||||
if let Some(v) = &agent_args.coding_api_base_url {
|
||||
config.endpoints.coding_api_base_url = Some(v.clone());
|
||||
}
|
||||
if let Some(v) = &agent_args.xai_api_base_url {
|
||||
config.endpoints.xai_api_base_url = v.clone();
|
||||
if let Some(v) = &agent_args.api_base_url {
|
||||
config.endpoints.api_base_url = v.clone();
|
||||
}
|
||||
}
|
||||
/// Resolve --agent-profile path: canonicalize and verify the file exists.
|
||||
@@ -324,194 +323,6 @@ fn ensure_control_caps(reg: &LeaderRegistration) -> Result<&LeaderCapabilities>
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Leader does not advertise capabilities (legacy version)"))
|
||||
}
|
||||
/// Env override for the `kigi workspace` gate: any truthy value enables the
|
||||
/// command locally, a falsy one disables it. This is the only gate now that
|
||||
/// the server-side feature flag (xAI remote settings) is gone.
|
||||
const WORKSPACE_COMMAND_ENV: &str = "KIGI_WORKSPACE_COMMAND";
|
||||
/// The `KIGI_WORKSPACE_COMMAND` override, if set (`Some(true)`/`Some(false)`);
|
||||
/// `None` means unset (the command stays disabled by default).
|
||||
fn workspace_command_env_override() -> Option<bool> {
|
||||
std::env::var(WORKSPACE_COMMAND_ENV)
|
||||
.ok()
|
||||
.map(|v| env_flag_enabled(&v))
|
||||
}
|
||||
/// Resolve the gate: enabled exactly when the env override says so.
|
||||
fn workspace_command_gate(env_override: Option<bool>) -> bool {
|
||||
env_override.unwrap_or(false)
|
||||
}
|
||||
/// Truthy parse for grok on/off env vars: everything enables except the common
|
||||
/// falsy spellings (`0`, `false`, `off`, `no`, empty).
|
||||
fn env_flag_enabled(value: &str) -> bool {
|
||||
!matches!(
|
||||
value.trim().to_ascii_lowercase().as_str(),
|
||||
"" | "0" | "false" | "off" | "no"
|
||||
)
|
||||
}
|
||||
async fn run_workspace_mgmt(args: WorkspaceMgmtArgs) -> Result<()> {
|
||||
if !workspace_command_gate(workspace_command_env_override()) {
|
||||
anyhow::bail!(
|
||||
"`kigi workspace` is experimental and disabled by default. \
|
||||
Set {WORKSPACE_COMMAND_ENV}=1 to enable it."
|
||||
)
|
||||
}
|
||||
match args.command {
|
||||
WorkspaceMgmtCommand::Start(a) => workspace_start(a, false).await,
|
||||
WorkspaceMgmtCommand::Restart(a) => workspace_start(a, true).await,
|
||||
WorkspaceMgmtCommand::Pause { target, json } => {
|
||||
workspace_control(&target, json, ControlCommand::WorkspacePause).await
|
||||
}
|
||||
WorkspaceMgmtCommand::Resume { target, json } => {
|
||||
workspace_control(&target, json, ControlCommand::WorkspaceResume).await
|
||||
}
|
||||
WorkspaceMgmtCommand::Stop { target, json } => {
|
||||
workspace_control(&target, json, ControlCommand::WorkspaceStop).await
|
||||
}
|
||||
WorkspaceMgmtCommand::Status { target, json } => {
|
||||
workspace_control(&target, json, ControlCommand::WorkspaceStatus).await
|
||||
}
|
||||
}
|
||||
}
|
||||
fn ensure_workspace_caps(reg: &LeaderRegistration) -> Result<()> {
|
||||
let caps = ensure_control_caps(reg)?;
|
||||
if !caps.workspace_exposure {
|
||||
anyhow::bail!(
|
||||
"the running leader does not support workspace exposure — stop the \
|
||||
leader process and re-run to pick up the new version"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn connect_workspace_control(
|
||||
_agent_config: &AgentConfig,
|
||||
target: &LeaderTargetArgs,
|
||||
) -> Result<LeaderClient> {
|
||||
if target.pid.is_some() {
|
||||
let (_descriptor, client) = connect_to_leader(target).await?;
|
||||
return Ok(client);
|
||||
}
|
||||
let socket = default_socket_path();
|
||||
LeaderClient::connect(
|
||||
socket,
|
||||
"grok-workspace-cli",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities::default(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"no running leader ({e}). \
|
||||
Start a grok session, or run `grok workspace start`."
|
||||
)
|
||||
})
|
||||
}
|
||||
async fn workspace_control(
|
||||
target: &LeaderTargetArgs,
|
||||
json: bool,
|
||||
command: ControlCommand,
|
||||
) -> Result<()> {
|
||||
let raw_config = kigi_shell::config::load_effective_config_disk_only()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to load config: {e}"))?;
|
||||
let agent_config = AgentConfig::new_from_toml_cfg(&raw_config)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create agent config: {e}"))?;
|
||||
let client = connect_workspace_control(&agent_config, target).await?;
|
||||
ensure_workspace_caps(client.registration())?;
|
||||
let payload = client.send_control(command).await??;
|
||||
render_workspace_payload(&payload, json);
|
||||
client.cancel();
|
||||
Ok(())
|
||||
}
|
||||
async fn workspace_start(args: WorkspaceStartArgs, restart: bool) -> Result<()> {
|
||||
use kigi_shell::auth::ensure_authenticated;
|
||||
let raw_config = kigi_shell::config::load_effective_config()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to load config: {e}"))?;
|
||||
let agent_config = AgentConfig::new_from_toml_cfg(&raw_config)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create agent config: {e}"))?;
|
||||
let (use_leader, _) = resolve_use_leader(args.leader, args.no_leader, &raw_config, true);
|
||||
if !use_leader {
|
||||
anyhow::bail!(
|
||||
"`grok workspace` requires leader mode (the workspace is shared via the leader).\n\
|
||||
Enable it with `[cli] use_leader = true` in ~/.kigi/config.toml, or pass --leader."
|
||||
);
|
||||
}
|
||||
ensure_authenticated(
|
||||
&agent_config.kimi_code_config,
|
||||
false,
|
||||
Some("No cached credentials found. Run `kigi login` first."),
|
||||
)
|
||||
.await?;
|
||||
let capabilities = ClientCapabilities {
|
||||
client_version: Some(PAGER_CLIENT_VERSION.to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let conn = connect_or_spawn("grok-workspace-cli", ClientMode::Stdio, capabilities)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("failed to start or connect to leader: {e}"))?;
|
||||
drop(conn);
|
||||
let target = LeaderTargetArgs::default();
|
||||
let client = connect_workspace_control(&agent_config, &target).await?;
|
||||
ensure_workspace_caps(client.registration())?;
|
||||
if restart {
|
||||
let _ = client.send_control(ControlCommand::WorkspaceStop).await;
|
||||
}
|
||||
let cwd = match args.cwd {
|
||||
Some(p) => p,
|
||||
None => std::env::current_dir()
|
||||
.map_err(|e| anyhow::anyhow!("cannot determine current directory: {e}"))?,
|
||||
};
|
||||
let cwd = std::path::absolute(&cwd).unwrap_or(cwd);
|
||||
let payload = client
|
||||
.send_control(ControlCommand::WorkspaceStart {
|
||||
hub_url: args.hub_url.clone(),
|
||||
cwd: cwd.display().to_string(),
|
||||
})
|
||||
.await??;
|
||||
render_workspace_payload(&payload, args.json);
|
||||
client.cancel();
|
||||
Ok(())
|
||||
}
|
||||
fn render_workspace_payload(payload: &ControlPayload, json: bool) {
|
||||
let ControlPayload::WorkspaceStatus {
|
||||
state,
|
||||
hub_url,
|
||||
cwd,
|
||||
uptime_ms,
|
||||
active_tool_calls,
|
||||
sessions,
|
||||
pid,
|
||||
} = payload
|
||||
else {
|
||||
eprintln!("unexpected control response: {payload:?}");
|
||||
return;
|
||||
};
|
||||
if json {
|
||||
let value = serde_json::json!(
|
||||
{ "state" : state, "hubUrl" : hub_url, "cwd" : cwd, "uptimeMs" : uptime_ms,
|
||||
"activeToolCalls" : active_tool_calls, "sessions" : sessions, "pid" : pid, }
|
||||
);
|
||||
println!("{}", serde_json::to_string(&value).unwrap_or_default());
|
||||
return;
|
||||
}
|
||||
if state == "none" {
|
||||
println!("Workspace exposure: not running (leader PID {pid})");
|
||||
return;
|
||||
}
|
||||
println!("Workspace exposure: {state}");
|
||||
if let Some(url) = hub_url {
|
||||
println!(" hub: {url}");
|
||||
}
|
||||
if let Some(dir) = cwd {
|
||||
println!(" cwd: {dir}");
|
||||
}
|
||||
println!(" uptime: {}s", uptime_ms / 1000);
|
||||
println!(" active: {active_tool_calls} tool call(s)");
|
||||
let session_list = if sessions.is_empty() {
|
||||
"-".to_string()
|
||||
} else {
|
||||
sessions.join(", ")
|
||||
};
|
||||
println!(" sessions: {} ({session_list})", sessions.len());
|
||||
println!(" leader: PID {pid}");
|
||||
}
|
||||
/// How to rebuild one session's `session/load` after a leader reconnect.
|
||||
#[derive(Default, Clone)]
|
||||
struct CachedSession {
|
||||
@@ -1604,10 +1415,6 @@ async fn async_main() -> Result<()> {
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create agent config: {e}"))?;
|
||||
return kigi_tui::worktree_cmd::run(worktree_args, &agent_config).await;
|
||||
}
|
||||
Command::Workspace(workspace_args) => {
|
||||
init_tracing_simple("cli");
|
||||
return run_workspace_mgmt(workspace_args).await;
|
||||
}
|
||||
Command::Sessions(sessions_args) => {
|
||||
init_tracing_simple("cli");
|
||||
return kigi_tui::sessions_cmd::run(sessions_args).await;
|
||||
@@ -2094,23 +1901,6 @@ mod tests {
|
||||
"failure path must not flag the startup hook",
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn workspace_command_gate_resolution() {
|
||||
assert!(workspace_command_gate(Some(true)));
|
||||
assert!(!workspace_command_gate(Some(false)));
|
||||
assert!(!workspace_command_gate(None), "unset env defaults to off");
|
||||
}
|
||||
#[serial_test::serial(KIGI_WORKSPACE_COMMAND)]
|
||||
#[test]
|
||||
fn workspace_command_env_override_parsing() {
|
||||
unsafe { std::env::remove_var("KIGI_WORKSPACE_COMMAND") };
|
||||
assert_eq!(workspace_command_env_override(), None);
|
||||
unsafe { std::env::set_var("KIGI_WORKSPACE_COMMAND", "1") };
|
||||
assert_eq!(workspace_command_env_override(), Some(true));
|
||||
unsafe { std::env::set_var("KIGI_WORKSPACE_COMMAND", "off") };
|
||||
assert_eq!(workspace_command_env_override(), Some(false));
|
||||
unsafe { std::env::remove_var("KIGI_WORKSPACE_COMMAND") };
|
||||
}
|
||||
fn make_state() -> std::sync::Mutex<StdioReplayState> {
|
||||
std::sync::Mutex::new(StdioReplayState::default())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user