From 2f702ad446fcd6a261a3bea0ab2807d70eca43e2 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Tue, 21 Apr 2026 13:07:27 -0700 Subject: refactor: Replace ad-hoc dispatch with FSM + driver architecture (#3434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/atuin-ai/src/fsm/effects.rs | 81 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 crates/atuin-ai/src/fsm/effects.rs (limited to 'crates/atuin-ai/src/fsm/effects.rs') diff --git a/crates/atuin-ai/src/fsm/effects.rs b/crates/atuin-ai/src/fsm/effects.rs new file mode 100644 index 00000000..ede72a42 --- /dev/null +++ b/crates/atuin-ai/src/fsm/effects.rs @@ -0,0 +1,81 @@ +//! Effects (outputs) from the agent FSM. +//! +//! The FSM returns these as data; the driver is responsible for executing them. + +use std::path::PathBuf; +use std::time::Duration; + +use serde_json::Value; + +use crate::permissions::rule::Rule; +use crate::permissions::writer::RuleDisposition; +use crate::tools::ClientToolCall; + +/// Where to write a permission rule. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum PermissionTarget { + /// Project-level: `/.atuin/permissions.ai.toml` + Project, + /// Global: `~/.config/atuin/permissions.ai.toml` + Global, +} + +/// Side effects the driver should execute after a state transition. +#[derive(Debug, Clone)] +pub(crate) enum Effect { + // ─── Network ──────────────────────────────────────────────── + /// Start a new streaming request to the server. + StartStream { + messages: Vec, + session_id: Option, + }, + /// Abort the active stream connection. + AbortStream, + + // ─── Tool orchestration ───────────────────────────────────── + /// Run the permission resolver for a tool call. + CheckPermission { + tool_id: String, + tool: ClientToolCall, + }, + /// Execute a tool (file read, edit, write, shell, history search). + ExecuteTool { + tool_id: String, + tool: ClientToolCall, + }, + /// Kill a running tool (send interrupt to shell command). + AbortTool { tool_id: String }, + + // ─── Persistence ──────────────────────────────────────────── + /// Persist current conversation state to disk. + Persist, + /// Write a permanent permission rule to disk. + WritePermissionRule { + target: PermissionTarget, + rule: Rule, + disposition: RuleDisposition, + }, + /// Cache a session-scoped file permission grant. + CacheSessionGrant { path: PathBuf }, + /// Archive current session and start fresh (IO only — state already updated by FSM). + ArchiveSession, + + // ─── Timers ───────────────────────────────────────────────── + /// Schedule a timer that will fire ConfirmationTimeout after delay. + ScheduleTimeout { timeout_id: u64, duration: Duration }, + + // ─── Exit ─────────────────────────────────────────────────── + /// Exit the application with the given action. + ExitApp(ExitAction), +} + +/// What to do when exiting the TUI. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum ExitAction { + /// Run the suggested command. + Execute(String), + /// Insert the command into the shell without running. + Insert(String), + /// Exit without action. + Cancel, +} -- cgit v1.3.1