diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/api/agents.rs | 45 | ||||
| -rw-r--r-- | src/cli/spec.rs | 2 |
2 files changed, 46 insertions, 1 deletions
diff --git a/src/app/api/agents.rs b/src/app/api/agents.rs index e5e5525f..1ed13efc 100644 --- a/src/app/api/agents.rs +++ b/src/app/api/agents.rs @@ -79,6 +79,16 @@ impl App { let Some(terminal) = self.state.terminals.get(&terminal_id) else { return agent_not_found(id, ¶ms.target); }; + if terminal.state == crate::detect::AgentState::Blocked { + return encode_error( + id, + "agent_blocked", + format!( + "agent {} is blocked and requires interactive input", + params.target + ), + ); + } let Some(expected_agent) = terminal.effective_known_agent() else { return agent_not_ready(id, ¶ms.target); }; @@ -407,6 +417,41 @@ mod tests { } #[tokio::test] + async fn agent_prompt_rejects_blocked_agent_without_writing() { + 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::Blocked); + let (runtime, mut rx) = crate::terminal::TerminalRuntime::test_with_channel(80, 24); + app.state.insert_test_runtime(pane_id, runtime); + + let response = app.handle_agent_prompt( + "req".into(), + AgentPromptParams { + target: "reviewer".into(), + text: "unrelated prompt".into(), + wait: None, + }, + ); + + let error: crate::api::schema::ErrorResponse = serde_json::from_str(&response).unwrap(); + assert_eq!(error.error.code, "agent_blocked"); + assert!( + tokio::time::timeout( + AGENT_PROMPT_SUBMIT_DELAY + Duration::from_millis(100), + rx.recv() + ) + .await + .is_err(), + "blocked prompt wrote or scheduled terminal input" + ); + } + + #[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; diff --git a/src/cli/spec.rs b/src/cli/spec.rs index a1efdac4..ba454bfc 100644 --- a/src/cli/spec.rs +++ b/src/cli/spec.rs @@ -354,7 +354,7 @@ fn agent_command() -> Command { .help("Fail after this many milliseconds"), ) .after_help( - "When submission starts from a non-working state, --wait first requires an observed state change within 5000ms; otherwise it returns agent_prompt_stalled. A shorter --timeout returns timeout instead. It then matches idle, done, or blocked by default, or any exact --until state. It does not track turns: if the agent is already working, that active turn's completion may match. Without --timeout, the settled-state wait is indefinite.", + "If the agent is already blocked, submission is rejected with agent_blocked before any input is sent. When an accepted submission starts from another non-working state, --wait first requires an observed state change within 5000ms; otherwise it returns agent_prompt_stalled. A shorter --timeout returns timeout instead. It then matches idle, done, or blocked by default, or any exact --until state. It does not track turns: if the agent is already working, that active turn's completion may match. Without --timeout, the settled-state wait is indefinite.", ), ) .subcommand( |
