aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-ai/src/tui/slash.rs
diff options
context:
space:
mode:
authorMichelle Tilley <michelle@michelletilley.net>2026-04-14 16:03:08 -0700
committerGitHub <noreply@github.com>2026-04-15 00:03:08 +0100
commitfd188da879d977ca847f10708c39dd4801a204c4 (patch)
tree592bfe2644f8bd9be3563f176eabf29e55fa9a9b /crates/atuin-ai/src/tui/slash.rs
parentfix: dependency fix (#3414) (diff)
downloadatuin-fd188da879d977ca847f10708c39dd4801a204c4.zip
feat: Allow resuming previous AI sessions (#3407)
This PR introduces session continuation to Atuin AI. * Conversations with Atuin AI are stored in a local SQLite database * Upon startup, Atuin AI tries to find a session to resume based on its directory/workspace and the time since the last event * If found, Atuin AI will show a note that the session has been resumed, and an event is added to help the LLM know where the invocation boundaries are * If not, Atuin AI will create a new conversation * The user can create a new conversation with `/new` * The new setting `ai.session_continue_minutes`, which defaults to `60`, controls how old the last event in a session can be before it's no longer considered for automatic resuming. <img width="1055" height="593" alt="image" src="https://github.com/user-attachments/assets/3f9ff01a-ef64-44a9-b0e2-3a4252c5746f" /> ## Architecture A new `SessionService` trait defines an API contract for a service that can manage session data. `LocalSessionService` implements this, with `DaemonSessionService` a possible future extension point. `SessionManager` owns a `dyn SessionService` and delegates as appropriate.
Diffstat (limited to 'crates/atuin-ai/src/tui/slash.rs')
-rw-r--r--crates/atuin-ai/src/tui/slash.rs79
1 files changed, 79 insertions, 0 deletions
diff --git a/crates/atuin-ai/src/tui/slash.rs b/crates/atuin-ai/src/tui/slash.rs
new file mode 100644
index 00000000..7d5e6fa8
--- /dev/null
+++ b/crates/atuin-ai/src/tui/slash.rs
@@ -0,0 +1,79 @@
+#[derive(Debug, Clone)]
+pub(crate) struct SlashCommand {
+ pub name: String,
+ pub description: String,
+}
+
+impl SlashCommand {
+ pub fn new(name: &str, description: &str) -> Self {
+ Self {
+ name: name.to_string(),
+ description: description.to_string(),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct SlashCommandRegistry {
+ commands: Vec<SlashCommand>,
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct SlashCommandSearchResult {
+ pub command: SlashCommand,
+ pub relevance: f32,
+ pub span: (usize, usize),
+}
+
+impl SlashCommandRegistry {
+ pub fn new() -> Self {
+ Self {
+ commands: Vec::new(),
+ }
+ }
+
+ pub fn register(&mut self, command: SlashCommand) {
+ self.commands.push(command);
+ }
+
+ pub fn get_commands(&self) -> &[SlashCommand] {
+ &self.commands
+ }
+
+ pub fn search_fuzzy(&self, query: &str) -> Vec<SlashCommandSearchResult> {
+ let query_lower = query.to_lowercase();
+
+ self.commands
+ .iter()
+ .filter_map(|command| {
+ let name_lower = command.name.to_lowercase();
+ if let Some(start) = name_lower.find(&query_lower as &str) {
+ let end = start + query_lower.len();
+ Some((command, start, end))
+ } else {
+ None
+ }
+ })
+ .map(|(command, start, end)| {
+ SlashCommandSearchResult {
+ command: command.clone(),
+ relevance: 1.0, // Simple relevance score for now
+ span: (start, end),
+ }
+ })
+ .collect()
+ }
+}
+
+impl Default for SlashCommandRegistry {
+ fn default() -> Self {
+ let mut registry = Self::new();
+ registry.register(SlashCommand::new("help", "Show help information"));
+ registry.register(SlashCommand::new(
+ "new",
+ "Start a new conversation, archiving the current one",
+ ));
+
+ registry
+ }
+}