104 lines
3.3 KiB
Rust
104 lines
3.3 KiB
Rust
//! Stop command: cancel the in-flight LLM turn for the current room's chat
|
|
//! session.
|
|
//!
|
|
//! `{bot_name} stop` (or `halt`/`abort`) cancels the actively-running Claude
|
|
//! Code turn for this room via [`crate::chat::dispatcher::ChatDispatcher`]
|
|
//! and clears any coalesced/pending messages. Unlike [`super::reset`] and
|
|
//! [`crate::chat::compact`], which match on the first word and ignore
|
|
//! trailing text, this requires an EXACT match (after mention-stripping) —
|
|
//! an instruction like "stop X and do Y" must not be misread as an abort.
|
|
|
|
use crate::chat::util::strip_bot_mention;
|
|
|
|
/// A parsed stop/halt/abort command.
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct StopCommand;
|
|
|
|
/// Parse a stop command from a raw message body.
|
|
///
|
|
/// Strips the bot mention prefix and requires the *entire* remaining,
|
|
/// trimmed message to equal `stop`, `halt`, or `abort` (case-insensitive).
|
|
/// Returns `None` for anything else, including a recognised word followed by
|
|
/// trailing text (e.g. "stop the deployment").
|
|
pub fn extract_stop_command(
|
|
message: &str,
|
|
bot_name: &str,
|
|
bot_user_id: &str,
|
|
) -> Option<StopCommand> {
|
|
let stripped = strip_bot_mention(message, bot_name, bot_user_id);
|
|
let trimmed = stripped.trim();
|
|
|
|
if trimmed.eq_ignore_ascii_case("stop")
|
|
|| trimmed.eq_ignore_ascii_case("halt")
|
|
|| trimmed.eq_ignore_ascii_case("abort")
|
|
{
|
|
Some(StopCommand)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn extract_bare_stop() {
|
|
let cmd = extract_stop_command("stop", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, Some(StopCommand));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_with_display_name() {
|
|
let cmd = extract_stop_command("Timmy stop", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, Some(StopCommand));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_with_full_user_id() {
|
|
let cmd = extract_stop_command("@timmy:home.local stop", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, Some(StopCommand));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_halt_synonym() {
|
|
let cmd = extract_stop_command("Timmy halt", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, Some(StopCommand));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_abort_synonym() {
|
|
let cmd = extract_stop_command("abort", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, Some(StopCommand));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_case_insensitive() {
|
|
let cmd = extract_stop_command("Timmy STOP", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, Some(StopCommand));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_non_stop_returns_none() {
|
|
let cmd = extract_stop_command("Timmy help", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, None);
|
|
}
|
|
|
|
#[test]
|
|
fn extract_with_trailing_text_returns_none() {
|
|
// "stop X and do Y" is an instruction, not an abort request.
|
|
let cmd = extract_stop_command("Timmy stop X and do Y", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, None);
|
|
}
|
|
|
|
#[test]
|
|
fn extract_stop_followed_by_words_returns_none() {
|
|
let cmd = extract_stop_command("stop the deployment please", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, None);
|
|
}
|
|
}
|