F3: Kimi inference pipeline + full grok cloud-surface excision
Sampler / inference (PRD F3):
- kimi_compat.rs: single adaptation point for the Kimi chat/completions
dialect (thinking-field mapping, model_id stripping, empty-content
tool-call message fix, stream_options.include_usage), with kimi-cli
source citations
- Rate-limit handling reworked for Kimi/Moonshot semantics; UA kigi/{version}
- /models replaces the xAI models-v2 endpoint everywhere; idle model
refresh carries X-Msh-* device headers only (X-XAI-Token-Auth and
x-grok-client-mode/CLIENT_MODE_HEADER machinery deleted)
Cloud-surface excision (PRD §5, zero-egress):
- remote/ conversations lane, cli-chat-proxy-types crate, prod/ dir,
share command, credit bar: deleted (single local session lane;
paginate() replaces merge_and_paginate)
- Subscription/tier gate stack deleted end-to-end: AppView
gate/tier/team/ZDR fields, app/subscription.rs watch loop,
dispatch/billing.rs paywall + SuperGrok upsell, free-usage-exhausted
chain, tier-restricted commands, GateInfo, RemoteSettings gate fields,
SettingsUpdateNotification gate fields
- /privacy + coding-data-sharing setting deleted (backed by a dead xAI
RPC; Kigi is zero-egress — nothing to share or retain remotely)
Auth UX correctness (user-reported):
- Device-flow fixtures now mirror the live Kimi payload shape
(https://www.kimi.com/code/authorize_device?user_code=..., verified
against auth.kimi.com); the fabricated auth.kimi.com/device?code=...
URLs are gone
- open_browser_detached is a no-op under cfg(test): unit tests drove
wiremock fixture URLs into the real browser (root cause of the
"garbage mock link" ABCD-1234 tabs)
- Welcome/pager-minimal rebrand: Grok Build -> Kigi, grok.com ->
kimi.com, "Sign in to Grok" -> "Sign in to Kimi"
This commit is contained in:
@@ -595,10 +595,6 @@ pub(crate) async fn run(
|
||||
.as_ref()
|
||||
.and_then(|s| s.show_resolved_model)
|
||||
.unwrap_or(true);
|
||||
app.sharing_enabled = remote_settings
|
||||
.as_ref()
|
||||
.and_then(|s| s.sharing_enabled)
|
||||
.unwrap_or(false);
|
||||
app.session_picker_grouped = std::env::var("KIGI_SESSION_PICKER_GROUPED")
|
||||
.ok()
|
||||
.and_then(|v| match v.as_str() {
|
||||
@@ -681,7 +677,7 @@ pub(crate) async fn run(
|
||||
// else: auth_state defaults to Done (already authenticated eagerly)
|
||||
// Effects stashed until after the initial render, so the user sees the
|
||||
// welcome/auth UI right away.
|
||||
let mut post_render_effects = if needs_interactive_login {
|
||||
let post_render_effects = if needs_interactive_login {
|
||||
if connection.auth_methods.is_empty() {
|
||||
app.auth_state = super::app_view::AuthState::Pending {
|
||||
error: Some("No login method available".to_string()),
|
||||
@@ -711,21 +707,6 @@ pub(crate) async fn run(
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: prefetch may have gate info the shell's AuthMeta missed.
|
||||
// Errs on the side of blocking if stale.
|
||||
if app.gate.is_none()
|
||||
&& let Some(rs) = remote_settings.as_ref()
|
||||
{
|
||||
app.gate = AppView::gate_from_settings(rs);
|
||||
}
|
||||
|
||||
// Re-impose the startup gate through the chokepoint: cached auth meta
|
||||
// and the settings prefetch are both possibly stale, so a consumer
|
||||
// session's gate is deferred for live verification before first paint.
|
||||
if let Some(gate) = app.gate.take() {
|
||||
post_render_effects.extend(app.impose_gate(gate));
|
||||
}
|
||||
|
||||
// Load config layers once, resolve tips and feature flags.
|
||||
let requirements = kigi_shell::config::load_merged_requirements();
|
||||
let user_config = kigi_shell::config::load_from_disk().ok();
|
||||
@@ -756,17 +737,6 @@ pub(crate) async fn run(
|
||||
);
|
||||
}
|
||||
|
||||
app.zdr_access_enabled = kigi_shell::util::config::resolve_zdr_access_enabled(
|
||||
requirements.as_ref(),
|
||||
user_config.as_ref(),
|
||||
managed_config.as_ref(),
|
||||
remote_settings.as_ref(),
|
||||
);
|
||||
|
||||
app.subscription_watch_interval_secs = remote_settings
|
||||
.as_ref()
|
||||
.and_then(|rs| rs.subscription_watch_interval_secs);
|
||||
|
||||
// Full layered resolve (env/requirements/remote may beat plain `[ui]`).
|
||||
crate::appearance::cache::set_show_thinking_blocks(
|
||||
kigi_shell::util::config::resolve_show_thinking_blocks(
|
||||
@@ -796,14 +766,6 @@ pub(crate) async fn run(
|
||||
.value,
|
||||
);
|
||||
|
||||
app.usage_billing_redirect_url = remote_settings
|
||||
.as_ref()
|
||||
.and_then(|s| s.usage_billing_redirect_url.clone());
|
||||
|
||||
if app.is_access_blocked() {
|
||||
app.welcome_prompt_focused = false;
|
||||
}
|
||||
|
||||
{
|
||||
use kigi_shell::util::config::resolve_tips;
|
||||
|
||||
@@ -1124,20 +1086,6 @@ pub(crate) async fn run(
|
||||
// iteration so it is popped on every close path.
|
||||
let mut gboom_keyboard_pushed = false;
|
||||
|
||||
const BILLING_POLL_INTERVAL: Duration = Duration::from_secs(30);
|
||||
let mut billing_poll_at: Option<Instant> = None;
|
||||
|
||||
const GATE_POLL_INTERVAL: Duration = Duration::from_secs(30);
|
||||
let mut gate_poll_at: Option<Instant> = None;
|
||||
|
||||
// Free→paid subscription watch (see `app::subscription`).
|
||||
let mut subscription_watch_at: Option<Instant> = if app.subscription_watch_wanted() {
|
||||
app.subscription_watch_interval()
|
||||
.map(|iv| Instant::now() + iv)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Leader-mode roster poll (FleetView dashboard). Only fires while the
|
||||
// dashboard is open AND we're connected via a leader. Armed to fire
|
||||
// immediately at loop start so an already-open dashboard refreshes
|
||||
@@ -1168,22 +1116,12 @@ pub(crate) async fn run(
|
||||
if process_effects(effs, &mut tasks, &mut app, &progress_tx) {
|
||||
return Ok(make_run_result(&app));
|
||||
}
|
||||
// Fetch billing early so the welcome screen can show a credit warning.
|
||||
if app.usage_visible {
|
||||
let effs = vec![super::actions::Effect::FetchAppBilling];
|
||||
if process_effects(effs, &mut tasks, &mut app, &progress_tx) {
|
||||
return Ok(make_run_result(&app));
|
||||
}
|
||||
}
|
||||
// Fetch changelog off the render path so the welcome screen
|
||||
// can display bullets and /release-notes uses the cached result.
|
||||
let effs = vec![super::actions::Effect::FetchChangelog];
|
||||
if process_effects(effs, &mut tasks, &mut app, &progress_tx) {
|
||||
return Ok(make_run_result(&app));
|
||||
}
|
||||
if !app.has_access() {
|
||||
gate_poll_at = Some(Instant::now() + GATE_POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
if !post_render_effects.is_empty()
|
||||
@@ -1276,16 +1214,14 @@ pub(crate) async fn run(
|
||||
app.draw(terminal);
|
||||
}
|
||||
|
||||
// Initial prompt from the CLI positional (`grok "fix the bug"`). When
|
||||
// Initial prompt from the CLI positional (`kigi "fix the bug"`). When
|
||||
// already authenticated, hand it to the shared dispatcher helper (same
|
||||
// `NewSession`/`SendPrompt` path the welcome screen uses). ZDR-blocked
|
||||
// accounts cannot start a session, so drop the prompt — this mirrors the
|
||||
// deferred post-login path, which clears the startup prompt for ZDR-blocked
|
||||
// accounts. When not yet authenticated, stash it for `AuthComplete`.
|
||||
// `NewSession`/`SendPrompt` path the welcome screen uses). When not yet
|
||||
// authenticated, stash it for `AuthComplete`.
|
||||
if let Some(initial_prompt) = args.initial_prompt() {
|
||||
if !app.session_startup_allowed() {
|
||||
app.deferred_startup.prompt = Some(initial_prompt.to_string());
|
||||
} else if !app.is_zdr_blocked() {
|
||||
} else {
|
||||
let effs = dispatch::dispatch_initial_prompt(&mut app, initial_prompt.to_string());
|
||||
if process_effects(effs, &mut tasks, &mut app, &progress_tx) {
|
||||
return Ok(make_run_result(&app));
|
||||
@@ -1322,10 +1258,7 @@ pub(crate) async fn run(
|
||||
// empty one so the user lands directly at the prompt. Unauthenticated /
|
||||
// ZDR-blocked startup stays on Welcome, where `crate::minimal::live` shows
|
||||
// a sign-in hint instead of a blank region.
|
||||
if term_state.screen_mode.is_minimal()
|
||||
&& matches!(app.active_view, ActiveView::Welcome)
|
||||
&& !app.is_zdr_blocked()
|
||||
{
|
||||
if term_state.screen_mode.is_minimal() && matches!(app.active_view, ActiveView::Welcome) {
|
||||
if app.session_startup_allowed() {
|
||||
// Already authenticated + trusted: open the empty session now so the
|
||||
// user lands directly at the prompt.
|
||||
@@ -1456,15 +1389,6 @@ pub(crate) async fn run(
|
||||
roster_poll_at = Some(Instant::now());
|
||||
}
|
||||
|
||||
// (Re-)arm the subscription watch on the dormant→wanted transition
|
||||
// and after each fired tick.
|
||||
if subscription_watch_at.is_none()
|
||||
&& app.subscription_watch_wanted()
|
||||
&& let Some(iv) = app.subscription_watch_interval()
|
||||
{
|
||||
subscription_watch_at = Some(Instant::now() + iv);
|
||||
}
|
||||
|
||||
// Future that sleeps until the next animation tick, or waits forever if none.
|
||||
let animation_tick = async {
|
||||
match animation_tick_at {
|
||||
@@ -1511,27 +1435,6 @@ pub(crate) async fn run(
|
||||
}
|
||||
};
|
||||
|
||||
let billing_poll = async {
|
||||
match billing_poll_at {
|
||||
Some(at) => sleep_until(at).await,
|
||||
None => std::future::pending().await,
|
||||
}
|
||||
};
|
||||
|
||||
let gate_poll = async {
|
||||
match gate_poll_at {
|
||||
Some(at) => sleep_until(at).await,
|
||||
None => std::future::pending().await,
|
||||
}
|
||||
};
|
||||
|
||||
let subscription_watch = async {
|
||||
match subscription_watch_at {
|
||||
Some(at) => sleep_until(at).await,
|
||||
None => std::future::pending().await,
|
||||
}
|
||||
};
|
||||
|
||||
let roster_poll = async {
|
||||
match roster_poll_at {
|
||||
Some(at) => sleep_until(at).await,
|
||||
@@ -1629,18 +1532,6 @@ pub(crate) async fn run(
|
||||
schedule_tick(&mut animation_tick_at, &app, tick_interval);
|
||||
resize_debounce_at = None;
|
||||
|
||||
// Schedule/clear poll timers.
|
||||
if app.billing_poll_wanted && billing_poll_at.is_none() {
|
||||
billing_poll_at = Some(Instant::now() + BILLING_POLL_INTERVAL);
|
||||
} else if !app.billing_poll_wanted {
|
||||
billing_poll_at = None;
|
||||
}
|
||||
if !app.has_access() && gate_poll_at.is_none() {
|
||||
gate_poll_at = Some(Instant::now() + GATE_POLL_INTERVAL);
|
||||
} else if app.has_access() {
|
||||
gate_poll_at = None;
|
||||
}
|
||||
|
||||
app.draw(terminal);
|
||||
last_draw_at = Instant::now();
|
||||
draw_scheduled_at = None;
|
||||
@@ -1810,41 +1701,6 @@ pub(crate) async fn run(
|
||||
schedule_tick(&mut animation_tick_at, &app, tick_interval);
|
||||
}
|
||||
|
||||
_ = billing_poll => {
|
||||
billing_poll_at = None;
|
||||
if let ActiveView::Agent(id) = app.active_view {
|
||||
let effs = vec![Effect::FetchBilling {
|
||||
agent_id: id,
|
||||
silent: true,
|
||||
}];
|
||||
if process_effects(effs, &mut tasks, &mut app, &progress_tx) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if app.billing_poll_wanted {
|
||||
billing_poll_at = Some(Instant::now() + BILLING_POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
_ = gate_poll => {
|
||||
gate_poll_at = None;
|
||||
let effs = vec![Effect::RefreshGate];
|
||||
if process_effects(effs, &mut tasks, &mut app, &progress_tx) {
|
||||
break;
|
||||
}
|
||||
if !app.has_access() {
|
||||
gate_poll_at = Some(Instant::now() + GATE_POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
_ = subscription_watch => {
|
||||
subscription_watch_at = None;
|
||||
let effs = app.fire_subscription_check("watch");
|
||||
if process_effects(effs, &mut tasks, &mut app, &progress_tx) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_ = roster_poll => {
|
||||
roster_poll_at = None;
|
||||
// Only poll while the dashboard is open. When it is not active
|
||||
@@ -2519,12 +2375,6 @@ async fn drain_and_process(
|
||||
{
|
||||
crate::clipboard::prewarm_image_probe();
|
||||
}
|
||||
// The user may have just subscribed in the browser and
|
||||
// tabbed back.
|
||||
let effs = app.fire_subscription_check("focus");
|
||||
if process_effects(effs, tasks, app, progress_tx) {
|
||||
return true;
|
||||
}
|
||||
// Restore Prompt on refocus: needs-input overlay always, else idle non-vim.
|
||||
match app.active_view {
|
||||
ActiveView::Agent(id) => {
|
||||
|
||||
Reference in New Issue
Block a user