feat: remove session hub and related commands from the application
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -76,9 +76,7 @@ pub struct InputState {
|
||||
const COMMANDS: &[&str] = &[
|
||||
"/help",
|
||||
"/quit",
|
||||
"/resume",
|
||||
"/clear",
|
||||
"/save",
|
||||
"/lesson",
|
||||
"/lesson ls",
|
||||
"/lesson export",
|
||||
|
||||
@@ -68,6 +68,7 @@ pub enum TurnEvent {
|
||||
tool_name: String,
|
||||
output: String,
|
||||
is_error: bool,
|
||||
path: Option<String>,
|
||||
},
|
||||
SystemNote {
|
||||
kind: String,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)]
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user