feat: remove session hub and related commands from the application

This commit is contained in:
asepharyana
2026-07-11 22:40:45 +07:00
parent 1f182021fb
commit 93d1bbb7c1
11 changed files with 44 additions and 140 deletions
+1 -4
View File
@@ -9,7 +9,6 @@ Keybindings:
Ctrl+P Settings
Ctrl+A Toggle yolo arm
Ctrl+B Bash panel
Ctrl+S Session hub
Ctrl+T Todo panel
Ctrl+W Workflow panel
Ctrl+K Key input
@@ -21,14 +20,12 @@ Keybindings:
Slash commands:
/help Show this help
/quit Quit session
/resume Resume from overlay
/mode <name> Switch mode (chat, agents, bash, workflow)
/lesson <text> Create a lesson
/lesson list List lessons
/lesson export Export lessons
/lesson import Import lessons
/clear Clear transcript
/save Save session";
/clear Clear transcript";
pub fn handle_help_action(action: &Action) -> Action {
match action {
-8
View File
@@ -19,14 +19,10 @@ pub mod mcp;
pub mod onboard;
#[expect(dead_code)]
pub mod onboard_provider;
#[expect(dead_code)]
pub mod picker;
pub mod quit_confirm;
#[expect(dead_code)]
pub mod rewind;
pub mod security;
#[expect(dead_code)]
pub mod session_hub;
pub mod settings;
pub mod todo;
#[expect(dead_code)]
@@ -40,11 +36,9 @@ pub enum ModeKind {
Workflow,
Help,
Settings,
SessionHub,
QuitConfirm,
Onboard,
OnboardProvider,
Picker,
KeyInput,
Editor,
Effort,
@@ -64,11 +58,9 @@ impl ModeKind {
ModeKind::Workflow => "Workflow",
ModeKind::Help => "Help",
ModeKind::Settings => "Settings",
ModeKind::SessionHub => "SessionHub",
ModeKind::QuitConfirm => "QuitConfirm",
ModeKind::Onboard => "Onboard",
ModeKind::OnboardProvider => "OnboardProvider",
ModeKind::Picker => "Picker",
ModeKind::KeyInput => "KeyInput",
ModeKind::Editor => "Editor",
ModeKind::Effort => "Effort",
+41 -55
View File
@@ -9,6 +9,8 @@ use crate::app::state::runtime::TurnEvent;
use crate::app::state::types::{AgentMode, Origin, Overlay, Toast, ToastKind};
use crate::dto::chat::message::{ChatMessage, Role};
const MAX_TOOL_ONLY_TURNS: usize = 6;
const MAX_AGENT_STEPS: usize = 40;
#[derive(Debug, Clone)]
@@ -58,9 +60,6 @@ pub enum Action {
LessonReject {
name: String,
},
SaveSession,
ResumeSession,
RefreshSessions,
}
pub fn apply_action(state: &mut AppStateRest, action: Action) {
@@ -82,11 +81,9 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
| ModeKind::Workflow => Overlay::None,
ModeKind::Help => Overlay::Help,
ModeKind::Settings => Overlay::Settings,
ModeKind::SessionHub => Overlay::SessionHub,
ModeKind::QuitConfirm => Overlay::QuitConfirm,
ModeKind::Onboard => Overlay::Onboard,
ModeKind::OnboardProvider => Overlay::OnboardProvider,
ModeKind::Picker => Overlay::Picker,
ModeKind::KeyInput => Overlay::KeyInput,
ModeKind::Editor => Overlay::Editor,
ModeKind::Effort => Overlay::Effort,
@@ -110,6 +107,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
rt.push_message(ChatMessage::user(text));
}
spawn_turn(state);
state.push_transcript(ChatMessageDisplay::new(Role::Assistant, "Thinking...".to_string()));
state.dirty = true;
}
Action::InsertChar(c) => {
@@ -273,53 +271,6 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
}
state.dirty = true;
}
Action::SaveSession => {
save_current_session(state);
state.push_toast(Toast::new(ToastKind::Success, "session saved".to_string()));
state.dirty = true;
}
Action::ResumeSession => {
let base = state.store_base_dir();
let sessions = crate::model::session::Session::list(&base);
let target = sessions.into_iter()
.filter(|s| s.id != state.session_id)
.max_by_key(|s| s.updated_at);
if let Some(session) = target {
let conv_path = session.conversation_path(&base);
let loaded_msgs: Vec<crate::dto::chat::message::ChatMessage> =
std::fs::read_to_string(&conv_path)
.ok()
.and_then(|data| serde_json::from_str(&data).ok())
.unwrap_or_default();
state.session_id = session.id.clone();
state.session_dir = session.session_dir(&base);
state.session_runtime = Some(crate::app::state::runtime::SessionRuntime::new(
state.session_dir.clone(),
));
state.transcript_cache.messages.clear();
if let Some(ref mut rt) = state.session_runtime {
for msg in loaded_msgs {
let display = ChatMessageDisplay::new(
msg.role.clone(),
msg.content.clone().unwrap_or_default(),
);
state.transcript_cache.messages.push(display);
rt.push_message(msg);
}
}
state.push_toast(Toast::new(ToastKind::Success,
format!("resumed session: {}", session.title)));
} else {
state.push_toast(Toast::new(ToastKind::Info,
"no other sessions to resume".to_string()));
}
state.dirty = true;
}
Action::RefreshSessions => {
let base = state.store_base_dir();
state.sessions = crate::model::session::Session::list(&base);
state.dirty = true;
}
Action::Tick => {
let now_ms = chrono::Utc::now().timestamp_millis();
state.misc.drain_expired_toasts(now_ms);
@@ -341,16 +292,39 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
TurnEvent::AssistantMessage(msg) => {
let display_content = msg.content.clone().unwrap_or_default();
if !display_content.is_empty() {
state.push_transcript(ChatMessageDisplay::new(Role::Assistant, display_content));
let replaced = if let Some(last) = state.transcript_cache.messages.last_mut() {
if last.role == Role::Assistant && last.content == "Thinking..." {
last.content = display_content.clone();
true
} else {
false
}
} else {
false
};
if !replaced {
state.push_transcript(ChatMessageDisplay::new(Role::Assistant, display_content));
}
}
if let Some(ref mut rt) = state.session_runtime {
rt.push_message(msg);
}
}
TurnEvent::ToolResult { tool_call_id, tool_name, output, is_error } => {
TurnEvent::ToolResult { tool_call_id, tool_name, output, is_error, path } => {
let display_path = path.unwrap_or_default();
let display = if tool_name == "read" {
let line_count = output.lines().count();
if !display_path.is_empty() {
format!("read: {} ({} lines)", display_path, line_count)
} else {
format!("read: {} line(s)", line_count)
}
} else {
format!("{}: {}", tool_name, output)
};
state.push_transcript(ChatMessageDisplay::new(
Role::Tool,
format!("{}: {}", tool_name, output),
display,
));
if let Some(ref mut rt) = state.session_runtime {
rt.push_message(ChatMessage::tool_result(tool_call_id.clone(), output.clone()));
@@ -520,6 +494,7 @@ fn run_agent_turn(
) -> anyhow::Result<()> {
let mut msgs = messages.to_vec();
let mut edits_this_turn = 0u32;
let mut tool_only_rounds = 0usize;
let system_text = format!(
"{}\n\n{}",
@@ -531,6 +506,13 @@ fn run_agent_turn(
}
for _step in 0..MAX_AGENT_STEPS {
if tool_only_rounds >= MAX_TOOL_ONLY_TURNS {
msgs.push(ChatMessage::user(
"Stop calling tools. Respond naturally now.".to_string(),
));
tool_only_rounds = 0;
}
let response = tc
.client
.chat_with_tools(&msgs, Some(tc.tdefs.clone()))?;
@@ -540,6 +522,7 @@ fn run_agent_turn(
let content = response.content.clone().unwrap_or_default();
if has_tool_calls {
tool_only_rounds += 1;
let tool_calls = response.tool_calls.clone().unwrap_or_default();
msgs.push(response);
for tool_call in tool_calls {
@@ -583,6 +566,8 @@ fn run_agent_turn(
edits_this_turn += 1;
}
let tool_path = args.get("path").and_then(|v| v.as_str()).map(|s| s.to_string());
{
if let Ok(mut q) = events_q.lock() {
q.push_back(TurnEvent::ToolResult {
@@ -590,6 +575,7 @@ fn run_agent_turn(
tool_name: tool_name.clone(),
output: output.clone(),
is_error,
path: tool_path,
});
}
}
-6
View File
@@ -10,9 +10,6 @@ pub fn apply_command(command: Command) -> Vec<Action> {
Command::Quit => {
vec![Action::QuitConfirm]
}
Command::Resume => {
vec![Action::SaveSession, Action::ResumeSession, Action::CloseOverlay]
}
Command::LessonCreate(text) => {
vec![Action::SystemNote {
kind: "lesson".to_string(),
@@ -43,9 +40,6 @@ pub fn apply_command(command: Command) -> Vec<Action> {
message: "transcript cleared".to_string(),
}]
}
Command::Save => {
vec![Action::SaveSession]
}
Command::Login { provider } => {
vec![Action::SystemNote {
kind: "oauth".to_string(),
-2
View File
@@ -76,9 +76,7 @@ pub struct InputState {
const COMMANDS: &[&str] = &[
"/help",
"/quit",
"/resume",
"/clear",
"/save",
"/lesson",
"/lesson ls",
"/lesson export",
+1
View File
@@ -68,6 +68,7 @@ pub enum TurnEvent {
tool_name: String,
output: String,
is_error: bool,
path: Option<String>,
},
SystemNote {
kind: String,
-4
View File
@@ -39,7 +39,6 @@ pub enum PanelKind {
Bash,
Workflow,
Help,
SessionHub,
}
impl PanelKind {
@@ -50,7 +49,6 @@ impl PanelKind {
PanelKind::Bash => "Bash",
PanelKind::Workflow => "Workflow",
PanelKind::Help => "Help",
PanelKind::SessionHub => "Sessions",
}
}
}
@@ -95,11 +93,9 @@ pub enum Overlay {
Agents,
Bash,
QuitConfirm,
SessionHub,
Workflow,
Onboard,
OnboardProvider,
Picker,
KeyInput,
Editor,
Effort,
+1 -5
View File
@@ -4,16 +4,14 @@ use crate::app::mode::ModeKind;
pub enum Command {
Help,
Quit,
Resume,
LessonCreate(String),
LessonExport(String),
LessonImport(String),
LessonAccept(String),
LessonReject(String),
LessonList,
Mode(ModeKind),
Clear,
Save,
LessonList,
Login { provider: String },
Unknown(String),
}
@@ -30,9 +28,7 @@ pub fn parse_command(text: &str) -> Command {
match cmd {
"/help" => Command::Help,
"/quit" => Command::Quit,
"/resume" => Command::Resume,
"/clear" => Command::Clear,
"/save" => Command::Save,
"/mode" => {
let mode = match arg1 {
"chat" | "c" => ModeKind::Chat,
-5
View File
@@ -72,11 +72,6 @@ pub fn handle_key(key: KeyEvent, state: &mut AppStateRest) -> Vec<Action> {
KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::CONTROL) => {
vec![Action::OpenOverlay(Overlay::Bash)]
}
KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL)
&& !key.modifiers.contains(KeyModifiers::SHIFT) =>
{
vec![Action::RefreshSessions, Action::OpenOverlay(Overlay::SessionHub)]
}
KeyCode::Char('S') if key.modifiers.contains(KeyModifiers::CONTROL) => {
vec![Action::OpenOverlay(Overlay::Security)]
}
-2
View File
@@ -211,11 +211,9 @@ fn apply_client_update(
Some("Agents") => Overlay::Agents,
Some("Bash") => Overlay::Bash,
Some("QuitConfirm") => Overlay::QuitConfirm,
Some("SessionHub") => Overlay::SessionHub,
Some("Workflow") => Overlay::Workflow,
Some("Onboard") => Overlay::Onboard,
Some("OnboardProvider") => Overlay::OnboardProvider,
Some("Picker") => Overlay::Picker,
Some("KeyInput") => Overlay::KeyInput,
Some("Editor") => Overlay::Editor,
Some("Effort") => Overlay::Effort,
-49
View File
@@ -156,24 +156,6 @@ fn render_overlay(frame: &mut Frame, area: Rect, overlay: crate::app::state::typ
let paragraph = Paragraph::new(lines).block(block);
frame.render_widget(paragraph, overlay_area);
}
crate::app::state::types::Overlay::SessionHub => {
let block = block.title(" Sessions ");
let lines: Vec<Line> = state.sessions.iter().map(|s| {
Line::from(Span::styled(
format!("{} | {}", s.title, s.model),
Style::default().fg(Theme::TEXT),
))
}).collect();
let paragraph = if lines.is_empty() {
Paragraph::new(Line::from(Span::styled(
"No sessions",
Style::default().fg(Theme::DIM),
))).block(block)
} else {
Paragraph::new(lines).block(block)
};
frame.render_widget(paragraph, overlay_area);
}
crate::app::state::types::Overlay::Workflow => {
workflow::draw_workflow_panel(frame, overlay_area, state);
}
@@ -228,37 +210,6 @@ fn render_overlay(frame: &mut Frame, area: Rect, overlay: crate::app::state::typ
let paragraph = Paragraph::new(lines).block(block);
frame.render_widget(paragraph, overlay_area);
}
crate::app::state::types::Overlay::Picker => {
let block = block.title(" Select ");
let lines = vec![
Line::from(Span::styled(
"Selection Picker",
Style::default().fg(Theme::TEXT).add_modifier(Modifier::BOLD),
)),
Line::from(Span::styled(
"",
Style::default(),
)),
Line::from(Span::styled(
format!("Filter: {}", state.input.buffer),
Style::default().fg(Theme::DIM),
)),
Line::from(Span::styled(
"",
Style::default(),
)),
Line::from(Span::styled(
"Press Enter to select, Esc to cancel.",
Style::default().fg(Theme::DIM),
)),
Line::from(Span::styled(
"Type to filter available options.",
Style::default().fg(Theme::DIM),
)),
];
let paragraph = Paragraph::new(lines).block(block);
frame.render_widget(paragraph, overlay_area);
}
crate::app::state::types::Overlay::KeyInput => {
let block = block.title(" Input ");
let input_text = &state.input.buffer;