aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-ai/src/commands
diff options
context:
space:
mode:
Diffstat (limited to 'crates/atuin-ai/src/commands')
-rw-r--r--crates/atuin-ai/src/commands/inline.rs74
1 files changed, 69 insertions, 5 deletions
diff --git a/crates/atuin-ai/src/commands/inline.rs b/crates/atuin-ai/src/commands/inline.rs
index b37bb72f..2e6beca2 100644
--- a/crates/atuin-ai/src/commands/inline.rs
+++ b/crates/atuin-ai/src/commands/inline.rs
@@ -2,6 +2,7 @@ use std::path::PathBuf;
use std::sync::mpsc;
use crate::context::{AppContext, ClientContext};
+use crate::session::{LocalSessionService, SessionManager, SessionService};
use crate::tui::dispatch;
use crate::tui::events::AiTuiEvent;
use crate::tui::state::{ExitAction, Session};
@@ -83,7 +84,7 @@ pub(crate) async fn run(
capabilities: settings.ai.capabilities.clone(),
};
- let action = run_inline_tui(ctx, initial_command).await?;
+ let action = run_inline_tui(ctx, initial_command, settings).await?;
emit_shell_result(action, output_for_hook);
Ok(())
@@ -147,12 +148,74 @@ async fn ensure_hub_session(settings: &atuin_client::settings::Settings) -> Resu
// ───────────────────────────────────────────────────────────────────
-async fn run_inline_tui(ctx: AppContext, initial_prompt: Option<String>) -> Result<Action> {
+async fn run_inline_tui(
+ ctx: AppContext,
+ initial_prompt: Option<String>,
+ settings: &atuin_client::settings::Settings,
+) -> Result<Action> {
let client_ctx = ClientContext::detect();
- let (tx, rx) = mpsc::channel::<AiTuiEvent>();
+ // Open the session service and check for a resumable session
+ let service = LocalSessionService::open(&settings.ai.db_path, settings.local_timeout)
+ .await
+ .context("failed to open AI session database")?;
+
+ let cwd = std::env::current_dir()
+ .ok()
+ .map(|p| p.to_string_lossy().into_owned());
+ let git_root_str = ctx
+ .git_root
+ .as_ref()
+ .map(|p| p.to_string_lossy().into_owned());
+
+ let session_window_mins = settings.ai.session_continue_minutes.max(0); // treat negative values as 0 to avoid confusion
+ let max_age_secs: i64 = session_window_mins * 60;
+
+ let resumable = service
+ .find_resumable(cwd.as_deref(), git_root_str.as_deref(), max_age_secs)
+ .await?;
- let initial_state = Session::new(ctx.git_root.is_some());
+ let (session_mgr, initial_state) = if let Some(stored) = resumable {
+ debug!(session_id = %stored.id, "resuming AI session");
+ let (mgr, events, server_sid, last_event_ts, invocation_id) =
+ SessionManager::resume(Box::new(service), &stored).await?;
+
+ // Only treat this as a meaningful resume if there are API-visible events
+ // (not just OutOfBandOutput or SystemContext).
+ let has_api_content = events.iter().any(|e| e.is_api_content());
+
+ if has_api_content {
+ let mut session = Session::new(ctx.git_root.is_some(), Some(invocation_id));
+ session.conversation.events = events;
+ session.conversation.session_id = server_sid;
+ // Inject an invocation boundary so the LLM knows prior messages
+ // are from an earlier interaction.
+ session.conversation.events.push(
+ crate::tui::state::ConversationEvent::SystemContext {
+ content: "[Note: The user has started a new invocation of Atuin AI. Prior messages from this session are from an earlier invocation.]".to_string(),
+ },
+ );
+ session.view_start_index = session.conversation.events.len();
+ session.is_resumed = true;
+ session.last_event_time =
+ last_event_ts.and_then(|ts| chrono::DateTime::from_timestamp(ts, 0));
+ (mgr, session)
+ } else {
+ // No meaningful content — treat as a fresh session
+ debug!("resumable session has no API-visible content, starting fresh");
+ (
+ mgr,
+ Session::new(ctx.git_root.is_some(), Some(invocation_id)),
+ )
+ }
+ } else {
+ debug!("creating new AI session");
+ let mgr =
+ SessionManager::create_new(Box::new(service), cwd.as_deref(), git_root_str.as_deref());
+ (mgr, Session::new(ctx.git_root.is_some(), None))
+ };
+
+ let (tx, rx) = mpsc::channel::<AiTuiEvent>();
println!();
@@ -177,8 +240,9 @@ async fn run_inline_tui(ctx: AppContext, initial_prompt: Option<String>) -> Resu
tokio::task::spawn_blocking(move || {
let tx = tx.clone();
let client_ctx = client_ctx;
+ let mut session_mgr = session_mgr;
while let Ok(event) = rx.recv() {
- dispatch::dispatch(&h, event, &tx, &ctx, &client_ctx);
+ dispatch::dispatch(&h, event, &tx, &ctx, &client_ctx, &mut session_mgr);
}
});