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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
//! 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 },
/// Shell tool execution timeout expired.
ToolExecutionTimeout { timeout_id: u64, tool_id: String },
// ─── 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 },
// ─── Skills ────────────────────────────────────────────────
/// User invoked a skill via /skill-name. FSM emits a LoadSkill
/// effect; the driver loads the content asynchronously and sends
/// SkillLoaded when ready.
RequestSkillLoad {
name: String,
arguments: Option<String>,
},
/// A skill's content has been loaded and interpolated.
/// Pushes skill content as OOB context and starts a turn so the
/// LLM sees the skill and acts on it.
SkillLoaded {
name: String,
arguments: Option<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,
}
|