aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorErik Krogen <erikkrogen@gmail.com>2026-08-12 16:38:43 -0700
committerGitHub <noreply@github.com>2026-08-13 02:38:43 +0300
commit8b635c918066dd184bacc29b4c06c40a3efea3f5 (patch)
treedc378f378f5184f963963acdf9f037c31d3378d4 /src
parent49e333ae87a57952fc82ba479a55c35b975ff3cc (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')
-rw-r--r--src/app/api/agents.rs54
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, &params.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;