feat(protocol): add Paste request type for bracketed-paste events
feat(engine): summarize agent completion progress in UI notifications feat(main): enable and disable bracketed paste support in terminal fix(app_config): store API key from file as fallback for Claude provider refactor(workflow): remove unnecessary card separator in workflow panel
This commit is contained in:
+22
-11
@@ -278,18 +278,29 @@ fn spawn_single_agent(
|
||||
|
||||
// Notify UI: agent completed or failed
|
||||
if let Some(f) = live {
|
||||
let summary_from = |text: &str| {
|
||||
text.lines()
|
||||
.next()
|
||||
.unwrap_or(text)
|
||||
.chars()
|
||||
.take(80)
|
||||
.collect::<String>()
|
||||
};
|
||||
match &result {
|
||||
Ok(_) => f(
|
||||
agent_id.to_string(),
|
||||
agent_name.to_string(),
|
||||
AgentStatus {
|
||||
state: AgentState::Completed,
|
||||
started_at: Some(started_at),
|
||||
completed_at: Some(completed_at),
|
||||
error: None,
|
||||
progress: None,
|
||||
},
|
||||
),
|
||||
Ok(text) => {
|
||||
let summary = summary_from(text);
|
||||
f(
|
||||
agent_id.to_string(),
|
||||
agent_name.to_string(),
|
||||
AgentStatus {
|
||||
state: AgentState::Completed,
|
||||
started_at: Some(started_at),
|
||||
completed_at: Some(completed_at),
|
||||
error: None,
|
||||
progress: Some(summary),
|
||||
},
|
||||
)
|
||||
}
|
||||
Err(e) => f(
|
||||
agent_id.to_string(),
|
||||
agent_name.to_string(),
|
||||
|
||||
@@ -45,6 +45,8 @@ pub enum ClientRequest {
|
||||
shift: bool,
|
||||
},
|
||||
Submit(String),
|
||||
/// Bulk-pasted text from a bracketed-paste event.
|
||||
Paste(String),
|
||||
Resize(u16, u16),
|
||||
Close,
|
||||
ScrollUp,
|
||||
|
||||
+29
-1
@@ -118,6 +118,7 @@ fn run_single_process() -> Result<()> {
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
execute!(stdout, crossterm::event::EnableBracketedPaste)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.clear()?;
|
||||
@@ -125,6 +126,7 @@ fn run_single_process() -> Result<()> {
|
||||
let run_result = run_loop(&mut state, &mut terminal);
|
||||
|
||||
let mut restore_stdout = io::stdout();
|
||||
let _ = execute!(restore_stdout, crossterm::event::DisableBracketedPaste);
|
||||
let _ = execute!(restore_stdout, LeaveAlternateScreen);
|
||||
let _ = disable_raw_mode();
|
||||
|
||||
@@ -408,6 +410,12 @@ fn run_daemon() -> Result<()> {
|
||||
}
|
||||
apply_action(&mut state, Action::Tick);
|
||||
}
|
||||
ClientRequest::Paste(text) => {
|
||||
state.input.buffer.insert_str(state.input.cursor, &text);
|
||||
state.input.cursor += text.len();
|
||||
state.dirty = true;
|
||||
apply_action(&mut state, Action::Tick);
|
||||
}
|
||||
ClientRequest::Resize(w, h) => {
|
||||
apply_action(&mut state, Action::Resize(w, h));
|
||||
apply_action(&mut state, Action::Tick);
|
||||
@@ -468,6 +476,7 @@ fn run_attach(session_id: &str) -> Result<()> {
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
execute!(stdout, crossterm::event::EnableBracketedPaste)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.clear()?;
|
||||
@@ -516,6 +525,9 @@ fn run_attach(session_id: &str) -> Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Paste(text) => {
|
||||
client.send(&ClientRequest::Paste(text))?;
|
||||
}
|
||||
Event::Resize(w, h) => {
|
||||
client.send(&ClientRequest::Resize(w, h))?;
|
||||
}
|
||||
@@ -555,6 +567,7 @@ fn run_attach(session_id: &str) -> Result<()> {
|
||||
})?;
|
||||
}
|
||||
|
||||
let _ = execute!(io::stdout(), crossterm::event::DisableBracketedPaste);
|
||||
let _ = execute!(io::stdout(), LeaveAlternateScreen);
|
||||
let _ = disable_raw_mode();
|
||||
|
||||
@@ -578,8 +591,9 @@ fn run_loop(
|
||||
let result = run_loop_inner(state, terminal);
|
||||
if let Err(ref _e) = result {
|
||||
let _ = terminal.clear();
|
||||
|
||||
|
||||
let _ = disable_raw_mode();
|
||||
let _ = execute!(io::stdout(), crossterm::event::DisableBracketedPaste);
|
||||
let _ = execute!(io::stdout(), LeaveAlternateScreen);
|
||||
}
|
||||
result
|
||||
@@ -625,6 +639,20 @@ fn run_loop_inner(
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Paste(text) => {
|
||||
// Insert pasted text as a single bulk operation instead of
|
||||
// character-by-character, avoiding O(n^2) String::insert()
|
||||
// and preventing stray newline/control-byte misinterpretation.
|
||||
if state.input.autocomplete_visible {
|
||||
state.input.close_autocomplete();
|
||||
}
|
||||
state.input.buffer.insert_str(state.input.cursor, &text);
|
||||
state.input.cursor += text.len();
|
||||
if state.input.buffer.starts_with('/') {
|
||||
state.input.open_autocomplete();
|
||||
}
|
||||
state.dirty = true;
|
||||
}
|
||||
Event::Resize(w, h) => {
|
||||
apply_action(state, Action::Resize(w, h));
|
||||
}
|
||||
|
||||
+11
-2
@@ -154,13 +154,22 @@ struct ClaudeSettings {
|
||||
/// than through its settings file, so reading only the file misses them.
|
||||
fn detect_claude_settings_provider() -> Option<ProviderConfig> {
|
||||
// Prefer the file, then fall back to env vars.
|
||||
let (base_url, _key) = claude_credentials_from_file()
|
||||
let (base_url, key) = claude_credentials_from_file()
|
||||
.or_else(claude_credentials_from_env)?;
|
||||
Some(ProviderConfig {
|
||||
api_base: base_url,
|
||||
// Keep the env-var name so runtime env overrides still work.
|
||||
api_key_env: Some("ANTHROPIC_API_KEY".to_string()),
|
||||
default_model: None,
|
||||
default_api_key: None,
|
||||
// Store the key read from the file as a direct fallback.
|
||||
// Without this, subagent/engine.rs resolve_provider_config() falls
|
||||
// through to std::env::var("ANTHROPIC_API_KEY") which is only
|
||||
// injected into the Claude Code process — not into zesdex. Workflow
|
||||
// nodes therefore got an empty key and failed with
|
||||
// "no API key configured for provider 'claude'", even though the
|
||||
// main agent succeeded (it has a DEFAULT_API_KEY fallback that
|
||||
// subagents intentionally do not have).
|
||||
default_api_key: Some(key),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -146,9 +146,6 @@ pub fn draw_workflow_panel(frame: &mut Frame, area: Rect, state: &crate::app::st
|
||||
Span::styled(prog.clone(), Style::default().fg(Theme::TEXT_DIM).add_modifier(Modifier::ITALIC)),
|
||||
]));
|
||||
}
|
||||
|
||||
// Card separator
|
||||
card_lines.push(Line::from(Span::raw("")));
|
||||
}
|
||||
|
||||
let list = Paragraph::new(card_lines);
|
||||
|
||||
Reference in New Issue
Block a user