diff options
| author | Erik Krogen <erikkrogen@gmail.com> | 2026-08-12 16:38:43 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-13 02:38:43 +0300 |
| commit | 8b635c918066dd184bacc29b4c06c40a3efea3f5 (patch) | |
| tree | dc378f378f5184f963963acdf9f037c31d3378d4 /src/app | |
| parent | 49e333ae87a57952fc82ba479a55c35b975ff3cc (diff) | |
fix(agent): focus Copilot before prompting (#2734)
* fix(agent): focus Copilot before prompting
refs #1698
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(agent): cover stale focus-reporting state
refs #1698
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/api/agents.rs | 54 |
1 files changed, 54 insertions, 0 deletions
diff --git a/src/app/api/agents.rs b/src/app/api/agents.rs index 8bc29462..e5e5525f 100644 --- a/src/app/api/agents.rs +++ b/src/app/api/agents.rs @@ -98,6 +98,16 @@ impl App { ), ); } + if expected_agent == crate::detect::Agent::GithubCopilot { + // Copilot ignores synthetic Enter after focus loss until it receives focus gained. + let focus = match crate::ghostty::encode_focus(crate::ghostty::FocusEvent::Gained) { + Ok(focus) => focus, + Err(err) => return encode_error(id, "agent_prompt_failed", err.to_string()), + }; + if let Err(err) = runtime.try_send_bytes(Bytes::from(focus)) { + return encode_error(id, "agent_prompt_failed", err.to_string()); + } + } let (text, enter) = crate::app::api_helpers::encode_api_submission_parts(runtime, ¶ms.text); if let Err(err) = runtime.try_send_bytes(Bytes::from(text)) { @@ -397,6 +407,50 @@ mod tests { } #[tokio::test] + async fn agent_prompt_focuses_copilot_before_submitting() { + let mut app = app_with_agent(); + let pane_id = app.state.workspaces[0].tabs[0].root_pane; + let terminal_id = app.state.workspaces[0].tabs[0].panes[&pane_id] + .attached_terminal_id + .clone(); + let terminal = app.state.terminals.get_mut(&terminal_id).unwrap(); + terminal.set_agent_name("reviewer".into()); + terminal.set_detected_state(Some(Agent::GithubCopilot), AgentState::Idle); + let (runtime, mut rx) = + crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes( + 80, 24, 0, b"", 3, + ); + runtime.test_process_pty_bytes(b"\x1b[?2004h"); + app.state.insert_test_runtime(pane_id, runtime); + + let response = app.handle_agent_prompt( + "req".into(), + AgentPromptParams { + target: "reviewer".into(), + text: "A != B".into(), + wait: None, + }, + ); + let success: SuccessResponse = serde_json::from_str(&response).unwrap(); + assert!(matches!( + success.result, + ResponseResult::AgentPrompted { .. } + )); + assert_eq!(rx.try_recv().unwrap(), Bytes::from_static(b"\x1b[I")); + assert_eq!( + rx.try_recv().unwrap(), + Bytes::from_static(b"\x1b[200~A != B\x1b[201~") + ); + assert_eq!( + tokio::time::timeout(Duration::from_secs(1), rx.recv()) + .await + .unwrap() + .unwrap(), + Bytes::from_static(b"\r") + ); + } + + #[tokio::test] async fn agent_send_keys_validates_every_key_before_writing() { let mut app = app_with_agent(); let pane_id = app.state.workspaces[0].tabs[0].root_pane; |
