diff options
| author | Michelle Tilley <michelle@michelletilley.net> | 2026-04-21 13:07:27 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-21 13:07:27 -0700 |
| commit | 2f702ad446fcd6a261a3bea0ab2807d70eca43e2 (patch) | |
| tree | 4cfa6276257cefbe73f7fa46a74026170aaf8435 /crates/atuin-ai/src/fsm/events.rs | |
| parent | docs: document show_numeric_shortcuts (#3433) (diff) | |
| download | atuin-2f702ad446fcd6a261a3bea0ab2807d70eca43e2.zip | |
refactor: Replace ad-hoc dispatch with FSM + driver architecture (#3434)
Replaces the tangled dispatch handler system (`tui/dispatch.rs`,
`tui/state.rs`) with a pure finite state machine + driver architecture.
The FSM handles all state transitions as explicit `(State, Event) →
(NewState, Effects)` mappings. The driver executes IO effects and
bridges the TUI to the FSM.
Diffstat (limited to 'crates/atuin-ai/src/fsm/events.rs')
| -rw-r--r-- | crates/atuin-ai/src/fsm/events.rs | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/crates/atuin-ai/src/fsm/events.rs b/crates/atuin-ai/src/fsm/events.rs new file mode 100644 index 00000000..62a624bf --- /dev/null +++ b/crates/atuin-ai/src/fsm/events.rs @@ -0,0 +1,121 @@ +//! Events (inputs) to the agent FSM. + +use serde_json::Value; + +use crate::tools::ToolOutcome; + +/// Events that drive state transitions in the agent FSM. +#[derive(Debug, Clone)] +pub(crate) enum Event { + // ─── User actions ─────────────────────────────────────────── + /// User submitted a message from the input box. + UserSubmit(String), + /// User pressed Esc or equivalent cancel action. + Cancel, + /// User pressed Enter to execute the suggested command. + ExecuteCommand, + /// User pressed Tab to insert the suggested command. + InsertCommand, + /// User chose to retry after an error. + Retry, + /// User interrupted executing tools (Ctrl+C / Esc during shell execution). + InterruptTools, + + // ─── Stream lifecycle ─────────────────────────────────────── + /// Stream connection established, first frame received. + StreamStarted, + /// Received a chunk of streamed text content. + StreamChunk(String), + /// Stream delivered a client-side tool call. + StreamToolCall { + id: String, + name: String, + input: Value, + }, + /// Stream delivered a server-side tool result (executed remotely). + StreamServerToolResult { + tool_use_id: String, + content: String, + is_error: bool, + remote: bool, + content_length: Option<usize>, + }, + /// Stream status changed (e.g. "thinking", "searching"). + StreamStatusChanged(String), + /// Stream ended normally. + StreamDone { session_id: String }, + /// Stream encountered an error. + StreamError(String), + + // ─── Suggest command (terminal tool call) ─────────────────── + /// The suggest_command tool call acts as a stream terminal event. + /// This is the server signaling "turn complete, here's the command." + SuggestCommand { id: String, input: Value }, + + // ─── Tool lifecycle ───────────────────────────────────────── + /// Permission resolver completed for a tool. + PermissionResolved { + tool_id: String, + response: PermissionResponse, + }, + /// User made a permission choice via the dialog. + PermissionUserChoice { + tool_id: String, + choice: PermissionChoice, + }, + /// Tool execution completed. + ToolExecutionDone { + tool_id: String, + outcome: ToolOutcome, + /// Preview data computed by the driver (diff, content preview, final shell state). + preview: Option<super::tools::ToolPreviewData>, + }, + /// Live preview update for an executing shell command. + ToolPreviewUpdate { + tool_id: String, + lines: Vec<String>, + exit_code: Option<i32>, + }, + + // ─── Timers ───────────────────────────────────────────────── + /// Confirmation timeout expired. + ConfirmationTimeout { timeout_id: u64 }, + + // ─── Session management ───────────────────────────────────── + /// User ran /new to start a fresh session. + NewSession, + + // ─── Slash commands ───────────────────────────────────────── + /// User submitted a slash command (other than /new). + /// The driver resolves known commands (like /help) and passes the + /// rendered content; the FSM just pushes an OOB event. + SlashCommand { command: String, content: String }, +} + +/// Result of the permission resolver check. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum PermissionResponse { + /// Rule allows this tool call — execute immediately. + Allowed, + /// Rule denies this tool call — reject with error. + Denied, + /// No matching rule — ask the user. + Ask, + /// Session-scoped grant exists — execute immediately (bypass resolver). + SessionGranted, +} + +/// User's choice from the permission dialog. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum PermissionChoice { + /// Allow this one time. + Allow, + /// Allow this file for the remainder of the session. + AllowForSession, + /// Always allow in this project (writes to project permissions file). + AlwaysAllowInProject, + /// Always allow globally (writes to global permissions file, scoped to file). + AlwaysAllow, + /// Deny this tool call. + Deny, +} |
