Refactor scrolling methods in ScrollState to accept an amount parameter

- Updated `scroll_up` and `scroll_down` methods to take an `amount` parameter for more flexible scrolling.
- Removed the `AgentMode` enum and related methods from the types module to simplify state management.
- Modified `AppStateRest` to remove the `mode` field and adjusted related logic.
- Enhanced `run_subagent` to build tool definitions and handle API key resolution from configuration.
- Updated command parsing to reflect changes in login handling.
- Removed onboarding overlays and related logic from input handling and rendering.
- Improved status bar to reflect connection status and agent readiness.
- Adjusted workflow panel rendering to simplify phase status display.
- Refactored edit log initialization to load from disk if available.
- Updated settings structure to use a HashMap for API keys.
- Enhanced error handling in LlmClient for authentication issues.
This commit is contained in:
asepharyana
2026-07-12 03:14:52 +07:00
parent 71d3494372
commit 36573e7e3b
28 changed files with 435 additions and 482 deletions
+61 -38
View File
@@ -6,7 +6,7 @@ use crate::app::mode::ModeKind;
use crate::app::review::{should_trigger_review, trigger_review};
use crate::app::state::rest::{AppStateRest, ChatMessageDisplay};
use crate::app::state::runtime::TurnEvent;
use crate::app::state::types::{AgentMode, Origin, Overlay, Toast, ToastKind};
use crate::app::state::types::{Origin, Overlay, Toast, ToastKind};
use crate::dto::chat::message::{ChatMessage, Role};
const MAX_TOOL_ONLY_TURNS: usize = 6;
@@ -60,6 +60,7 @@ pub enum Action {
command: String,
},
ModelList,
AbortTurn,
}
pub fn apply_action(state: &mut AppStateRest, action: Action) {
@@ -77,8 +78,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
ModeKind::Help => Overlay::Help,
ModeKind::Settings => Overlay::Settings,
ModeKind::QuitConfirm => Overlay::QuitConfirm,
ModeKind::Onboard => Overlay::Onboard,
ModeKind::OnboardProvider => Overlay::OnboardProvider,
ModeKind::KeyInput => Overlay::KeyInput,
ModeKind::Editor => Overlay::Editor,
ModeKind::Effort => Overlay::Effort,
@@ -132,12 +132,11 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
state.dirty = true;
}
Action::ScrollUp => {
state.scroll.scroll_up();
state.scroll.scroll_up(5);
state.dirty = true;
}
Action::ScrollDown => {
let total = state.transcript_cache.messages.len();
state.scroll.scroll_down(total);
state.scroll.scroll_down(5);
state.dirty = true;
}
Action::OpenOverlay(overlay) => {
@@ -419,6 +418,10 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
state.dirty = true;
}
}
Action::AbortTurn => {
state.abort_flag.store(true, std::sync::atomic::Ordering::SeqCst);
state.push_toast(Toast::new(ToastKind::Warning, "Aborting generation...".to_string()));
}
Action::LessonAccept { name } => {
if let Some(ref rt) = state.session_runtime {
let _ = crate::app::review::resolve_pending_lesson(
@@ -459,7 +462,7 @@ fn spawn_turn(state: &AppStateRest) {
if messages.is_empty() {
return;
}
let mut api_key = state.settings.api_key.clone().unwrap_or_default();
let mut api_key = state.settings.api_keys.get(&state.settings.provider).cloned().unwrap_or_default();
let model = state.settings.model.clone();
let base_url = state.app_config.providers.get(&state.settings.provider)
.map(|p| p.api_base.clone());
@@ -482,12 +485,14 @@ fn spawn_turn(state: &AppStateRest) {
tools.extend(state.mcp_manager.as_tools());
let tool_defs = crate::tool::tool_defs(&tools);
let ctx = state.tool_ctx();
let mode = state.mode;
let edit_session_dir = state.session_dir.clone();
let session_id = state.session_id.clone();
let turn_events = state.turn_events.clone();
let in_flight_flag = state.turn_in_flight.clone();
let workspace_roots: Vec<std::path::PathBuf> = ctx.workspaces.clone();
let abort_flag = state.abort_flag.clone();
abort_flag.store(false, std::sync::atomic::Ordering::SeqCst);
*in_flight_flag.lock().unwrap() = true;
@@ -502,13 +507,14 @@ fn spawn_turn(state: &AppStateRest) {
tdefs: tool_defs,
tools,
ctx,
mode,
workspace_roots,
edit_log_session_dir: edit_session_dir,
session_id,
db,
temperature,
max_tokens,
abort_flag,
};
let result = run_agent_turn(tc, &messages, &events_q);
if let Err(e) = result {
@@ -527,13 +533,14 @@ struct TurnCtx {
tdefs: Vec<crate::dto::provider::request::ToolDef>,
tools: Vec<Box<dyn crate::tool::Tool>>,
ctx: crate::tool::ToolCtx,
mode: AgentMode,
workspace_roots: Vec<std::path::PathBuf>,
edit_log_session_dir: std::path::PathBuf,
session_id: String,
db: Option<std::sync::Arc<std::sync::Mutex<rusqlite::Connection>>>,
temperature: f32,
max_tokens: u32,
abort_flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
fn archive_message(db: &Option<std::sync::Arc<std::sync::Mutex<rusqlite::Connection>>>, session_id: &str, msg: &ChatMessage) {
@@ -589,47 +596,57 @@ fn run_agent_turn(
};
let mut stream_started = false;
let (response, usage) = match tc.client.chat_with_tools_streaming(
let mut usage = None;
let result = tc.client.chat_with_tools_streaming(
&wire_msgs,
Some(tc.tdefs.clone()),
Some(tc.temperature),
Some(tc.max_tokens),
|event| match event {
crate::app::runtime::stream::StreamEvent::Token(tok) => {
if let Ok(mut q) = events_q.lock() {
if !stream_started {
q.push_back(TurnEvent::StreamStart);
stream_started = true;
|event| -> bool {
if tc.abort_flag.load(std::sync::atomic::Ordering::SeqCst) {
return false;
}
if let Ok(mut q) = events_q.lock() {
match event {
crate::app::runtime::stream::StreamEvent::Token(tok) => {
if !stream_started {
q.push_back(TurnEvent::StreamStart);
stream_started = true;
}
q.push_back(TurnEvent::StreamToken(tok.clone()));
}
q.push_back(TurnEvent::StreamToken(tok.clone()));
crate::app::runtime::stream::StreamEvent::Usage { prompt_tokens, completion_tokens, .. } => {
usage = Some((*prompt_tokens, *completion_tokens));
}
_ => {}
}
}
crate::app::runtime::stream::StreamEvent::Usage { prompt_tokens, completion_tokens, .. } => {
if let Ok(mut q) = events_q.lock() {
q.push_back(TurnEvent::Usage {
tokens_in: *prompt_tokens,
tokens_out: *completion_tokens,
});
}
}
_ => {}
true
},
) {
Ok(result) => result,
Err(_stream_err) => {
// Provider doesn't support streaming — fall back to non-streaming
);
let (response, final_usage) = match result {
Ok((msg, u)) => (msg, u.or(usage)),
Err(e) => {
if tc.abort_flag.load(std::sync::atomic::Ordering::SeqCst) || e.to_string().contains("aborted") {
if let Ok(mut q) = events_q.lock() {
q.push_back(TurnEvent::Error("Generation aborted by user".to_string()));
}
return Ok(());
}
let (msg, usage_fb) = tc.client.chat_with_tools_non_streaming(
&wire_msgs, Some(tc.tdefs.clone()),
)?;
if let Some((tok_in, tok_out)) = usage_fb {
if let Ok(mut q) = events_q.lock() {
q.push_back(TurnEvent::Usage { tokens_in: tok_in, tokens_out: tok_out });
}
}
(msg, usage_fb)
}
};
if let Some((tok_in, tok_out)) = final_usage {
if let Ok(mut q) = events_q.lock() {
q.push_back(TurnEvent::Usage { tokens_in: tok_in, tokens_out: tok_out });
}
}
let has_tool_calls = response.tool_calls.is_some()
&& response.tool_calls.as_ref().is_some_and(|tc| !tc.is_empty());
@@ -640,6 +657,12 @@ fn run_agent_turn(
archive_message(&tc.db, &tc.session_id, &response);
msgs.push(response);
for tool_call in tool_calls {
if tc.abort_flag.load(std::sync::atomic::Ordering::SeqCst) {
if let Ok(mut q) = events_q.lock() {
q.push_back(TurnEvent::Error("Turn aborted by user".to_string()));
}
return Ok(());
}
let tool_name = tool_call.function.name.clone();
let args = crate::dto::chat::tool::sanitize_tool_arguments(
&tool_call.function.arguments,
@@ -650,7 +673,7 @@ fn run_agent_turn(
let verdict = crate::app::harness::Harness::gate_tool_call(
&tool_name,
&args,
&tc.mode,
&ws_roots,
);
@@ -671,7 +694,7 @@ fn run_agent_turn(
},
Verdict::Block(reason) => (format!("Blocked: {}", reason), true, false),
Verdict::Escalate => (
"Tool requires approval. Switch to Auto mode or provide explicit approval."
"Tool requires approval. Provide explicit approval."
.to_string(),
true,
false,