aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-ai/src/fsm/events.rs
blob: 62a624bfd3d0069ae32b052e1c4968fc451b3334 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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,
}