Publish harness and TUI open-source

initial sync from the monorepo
This commit is contained in:
grokkybara[bot] 2026-07-16 06:46:02 +01:00
commit c68e39f604
2734 changed files with 1437016 additions and 0 deletions

View file

@ -0,0 +1,45 @@
//! Bridge the shell's `AuthManager` onto the voice crate's bearer provider.
//!
//! voice-api accepts both API keys and OAuth2 tokens directly at `api.x.ai`
//! and attributes per-user billing for OAuth, so the voice channel just reuses
//! the same bearer the agent uses for chat — no separate env var.
//!
//! Resolved per request: the agent's refreshing manager in direct-spawn mode,
//! or a non-refreshing one that adopts the agent's rotated `auth.json` token
//! under the file lock in leader mode (see [`crate::acp`]).
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use xai_grok_tools::types::SharedApiKeyProvider;
use xai_grok_voice::{SharedVoiceAuth, VoiceAuthProvider};
/// Adapts the shell's `ApiKeyProvider` onto [`VoiceAuthProvider`].
///
/// Resolves a token per request (never a static snapshot) so a long session
/// follows the underlying `AuthManager` instead of pinning a token that 401s.
struct AuthManagerVoiceAuth(SharedApiKeyProvider);
impl std::fmt::Debug for AuthManagerVoiceAuth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("AuthManagerVoiceAuth")
}
}
impl VoiceAuthProvider for AuthManagerVoiceAuth {
fn bearer(&self) -> Pin<Box<dyn Future<Output = Option<String>> + Send + '_>> {
let provider = self.0.clone();
Box::pin(async move { provider.current_api_key_async().await })
}
}
/// Build the voice bearer provider from the connection's `AuthManager`.
///
/// Works for every auth method: OAuth / grok.com / OIDC session tokens and
/// `XAI_API_KEY` / per-model BYOK keys.
pub fn build_voice_auth(auth_manager: Arc<xai_grok_shell::auth::AuthManager>) -> SharedVoiceAuth {
Arc::new(AuthManagerVoiceAuth(
xai_grok_shell::auth::shared_api_key_provider(auth_manager),
))
}

View file

@ -0,0 +1,89 @@
//! Map pipeline [`VoiceEvent`]s onto prompt-box dictation state.
use xai_grok_voice::VoiceEvent;
use crate::app::app_view::{AppView, VoiceTarget};
/// Append finalized text to whichever prompt started capture
/// (`voice_recording_target`) — the agent prompt or the dashboard dispatch input
/// — not necessarily the active view, so a late final after a view switch still
/// lands in the right place. Inserts a single separating space unless the prompt
/// is empty or already ends in whitespace (preserves trailing newlines).
fn append_voice_text_to_prompt(app: &mut AppView, text: &str) {
let combine = |existing: &str| -> String {
if existing.trim().is_empty() {
text.to_string()
} else if existing.ends_with(char::is_whitespace) {
format!("{existing}{text}")
} else {
format!("{existing} {text}")
}
};
match app.voice_recording_target() {
Some(VoiceTarget::Agent(id)) => {
let Some(agent) = app.agents.get_mut(&id) else {
return;
};
let combined = combine(agent.prompt.text());
agent.prompt.set_text(&combined);
agent.prompt.set_cursor(combined.len());
}
Some(target @ (VoiceTarget::DashboardDispatch | VoiceTarget::DashboardPeekReply(_))) => {
let Some(dashboard) = app.dashboard.as_mut() else {
return;
};
// Route to the box bound at capture start. The dispatch box is stable,
// but the peek reply widget is *shared* across rows and reassigned when
// the peeked row changes. While listening `enforce_voice_session_bound`
// stops capture on a row change, but after an explicit stop the target
// is kept for the trailing final and that guard no longer runs — so
// re-check the bound row here, or a final would land in (and send from)
// another agent's reply.
let prompt = match target {
VoiceTarget::DashboardPeekReply(rec) => {
let peeked = match dashboard.peek.as_ref().map(|p| &p.row) {
Some(crate::views::dashboard::DashboardRowId::TopLevel(id)) => Some(*id),
_ => None,
};
if peeked != Some(rec) {
return;
}
&mut dashboard.peek_reply
}
_ => &mut dashboard.dispatch,
};
let combined = combine(prompt.text());
prompt.set_text(&combined);
prompt.set_cursor(combined.len());
}
None => {}
}
}
/// Apply a voice event to app state. Returns whether the frame should redraw.
pub fn handle_voice_event(app: &mut AppView, event: VoiceEvent) -> bool {
match event {
VoiceEvent::InterimTranscript { text } => {
// No-op unless recording, so a late interim after a stop can't
// repopulate the overlay.
app.voice_set_interim(text)
}
VoiceEvent::UtteranceFinal { text } => {
app.voice_clear_interim();
// Keep the mic open across pauses; user stops explicitly, then Enter to send.
// The bound target survives a stop (`Stopping`), so a trailing final
// after an explicit stop still lands.
if !text.trim().is_empty() {
append_voice_text_to_prompt(app, text.trim());
}
true
}
VoiceEvent::Error { message } => {
// Tear down the session; `voice_reset` releases the mic when recording
// (the pipeline reader usually has already on error).
app.voice_reset();
app.show_toast(&format!("Voice: {message}"));
true
}
}
}

View file

@ -0,0 +1,27 @@
//! Voice input: STT pipeline integration and prompt-box dictation.
//!
//! Layering (pager-owned):
//! - **Voice gate** — GA default **on**. Remote `voice_mode_enabled: false` is
//! a kill switch (every voice surface unavailable and silent — no toast).
//! Absent remote falls through to on. `GROK_VOICE_MODE` overrides for local
//! dev (env > remote > default on). Free/X Basic still get SuperGrok upsell
//! via tier gates (not this flag).
//! - **Session mode** (`voice_ui_active`) — this CLI run only; shows the mic.
//! - **Capture chord** — `/voice` or `Ctrl+Space` start dictation (Esc/Enter
//! stop). `Ctrl+Space` decodes identically on every terminal, so the
//! cheatsheet shows it whenever voice is enabled.
//! - **Hold-to-talk** — `Ctrl+Space`: hold to record, release to stop, on
//! terminals that report key releases (Kitty protocol); elsewhere the same
//! chord toggles instead (press starts, press again stops). Handled in
//! `app::event_loop`.
//!
//! Finals append to the recording target's prompt — the agent prompt or the
//! dashboard's dispatch (new-agent) input, captured at start via
//! [`crate::app::app_view::VoiceTarget`] — while capture stays open across
//! speech pauses. The user always submits with Enter; nothing is auto-sent.
mod auth;
mod handle;
pub use auth::build_voice_auth;
pub use handle::handle_voice_event;