aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/api/agents.rs45
-rw-r--r--src/cli/spec.rs2
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, &params.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, &params.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(