feat: handle PostCompact hook event (#225)

Parses [PostCompact Hook] from stderr, emits claude:post-compact, shows
a toast notification, and briefly sets the character to success state.
This commit is contained in:
2026-03-20 09:38:23 -07:00
committed by Naomi Carrigan
parent 6c853ae73d
commit feb500ba2b
4 changed files with 115 additions and 2 deletions
+59 -2
View File
@@ -17,8 +17,8 @@ use crate::types::{
AgentEndEvent, AgentStartEvent, CharacterState, ClaudeMessage, ConnectionEvent,
ConnectionStatus, ContentBlock, ElicitationEvent, ElicitationResultEvent, MessageCost,
OutputEvent, PermissionPromptEvent, PermissionPromptEventItem, QuestionOption, SessionEvent,
StateChangeEvent, StopFailureEvent, TodoItem, TodoUpdateEvent, UserQuestionEvent,
WorkingDirectoryEvent, WorktreeEvent, WorktreeInfo,
PostCompactEvent, StateChangeEvent, StopFailureEvent, TodoItem, TodoUpdateEvent,
UserQuestionEvent, WorkingDirectoryEvent, WorktreeEvent, WorktreeInfo,
};
use parking_lot::RwLock;
use std::cell::RefCell;
@@ -1055,6 +1055,7 @@ fn handle_stderr(
let is_elicitation = line.contains("[Elicitation Hook]");
let is_elicitation_result = line.contains("[ElicitationResult Hook]");
let is_stop_failure = line.contains("[StopFailure Hook]");
let is_post_compact = line.contains("[PostCompact Hook]");
let line_type = if is_worktree_create || is_worktree_remove {
"worktree"
@@ -1064,6 +1065,8 @@ fn handle_stderr(
"elicitation"
} else if is_stop_failure {
"error"
} else if is_post_compact {
"compact-prompt"
} else {
"error"
};
@@ -1180,6 +1183,28 @@ fn handle_stderr(
parent_tool_use_id: None,
},
);
} else if is_post_compact {
let data = parse_post_compact_hook(&line);
let _ = app.emit(
"claude:post-compact",
PostCompactEvent {
session_id: data.session_id,
conversation_id: conversation_id.clone(),
},
);
let _ = app.emit(
"claude:output",
OutputEvent {
line_type: "compact-prompt".to_string(),
content: "Context compacted — conversation history has been summarised to free up space.".to_string(),
tool_name: None,
conversation_id: conversation_id.clone(),
cost: None,
parent_tool_use_id: None,
},
);
} else {
let _ = app.emit(
"claude:output",
@@ -1383,6 +1408,16 @@ fn build_stop_failure_message(data: &StopFailureData) -> String {
}
}
#[derive(Debug)]
struct PostCompactData {
session_id: Option<String>,
}
fn parse_post_compact_hook(line: &str) -> PostCompactData {
let session_id = extract_debug_string_value(line, "session_id");
PostCompactData { session_id }
}
/// Extracts a double-quoted string value from a `key="value"` pair in a hook line.
/// Handles escape sequences within the quoted value.
fn extract_quoted_value(line: &str, key: &str) -> Option<String> {
@@ -3509,6 +3544,28 @@ mod tests {
);
}
#[test]
fn test_parse_post_compact_hook_with_session_id() {
let line =
r#"[PostCompact Hook] session_id=Some("sess-abc123"), conversation_id=Some("conv-xyz")"#;
let data = parse_post_compact_hook(line);
assert_eq!(data.session_id, Some("sess-abc123".to_string()));
}
#[test]
fn test_parse_post_compact_hook_without_session_id() {
let line = "[PostCompact Hook] session_id=None";
let data = parse_post_compact_hook(line);
assert_eq!(data.session_id, None);
}
#[test]
fn test_parse_post_compact_hook_empty_line() {
let line = "[PostCompact Hook]";
let data = parse_post_compact_hook(line);
assert_eq!(data.session_id, None);
}
#[test]
fn test_build_stop_failure_message_no_fields() {
let data = StopFailureData {