diff --git a/src/cli_agent_orchestrator/providers/codex.py b/src/cli_agent_orchestrator/providers/codex.py index efe1aaa26..252c09691 100644 --- a/src/cli_agent_orchestrator/providers/codex.py +++ b/src/cli_agent_orchestrator/providers/codex.py @@ -75,8 +75,16 @@ # ASSISTANT_PREFIX_PATTERN and the TUI footer › matches idle prompt). TUI_PROGRESS_PATTERN = r"•.*\(\d+s\s*•\s*esc to interrupt\)" -# Workspace trust/approval prompt shown when Codex opens a new directory -TRUST_PROMPT_PATTERN = r"allow Codex to work in this folder" +# Workspace trust/approval prompt shown when Codex opens a new directory. +# codex-cli has reworded this dialog across versions, so match either phrasing: +# older: "… allow Codex to work in this folder without asking for approval" +# 0.144.1: "Do you trust the contents of this directory? … › 1. Yes, continue" +# A stale single-phrase pattern silently stops matching on a CLI update, which +# lets the worker hang on the unanswered prompt (it never auto-accepts). +TRUST_PROMPT_PATTERN = ( + r"allow Codex to work in this folder" + r"|Do you trust the contents of this (?:directory|folder)" +) # Codex welcome banner indicating normal startup (no trust prompt) CODEX_WELCOME_PATTERN = r"OpenAI Codex" diff --git a/test/providers/test_codex_provider_unit.py b/test/providers/test_codex_provider_unit.py index 360819127..8641d5363 100644 --- a/test/providers/test_codex_provider_unit.py +++ b/test/providers/test_codex_provider_unit.py @@ -1482,6 +1482,32 @@ async def test_handle_trust_prompt_detected_and_accepted(self, mock_tmux): "test-session", "window-0", "Enter" ) + @pytest.mark.asyncio + @patch("cli_agent_orchestrator.providers.codex.get_backend") + async def test_handle_trust_prompt_detected_and_accepted_new_wording(self, mock_tmux): + """codex-cli reworded the trust dialog (observed on 0.144.1): "Do you + trust the contents of this directory? … › 1. Yes, continue / 2. No, + quit". The handler must still detect it and auto-accept, otherwise the + worker hangs on the unanswered prompt until it times out. + """ + mock_tmux.return_value.get_history.return_value = ( + "> You are in /Users/test/project\n" + "\n" + " Do you trust the contents of this directory? Working with " + "untrusted contents comes with higher risk of prompt injection. " + "Trusting the directory allows project-local config, hooks, and " + "exec policies to load.\n" + "› 1. Yes, continue\n" + " 2. No, quit\n" + ) + + provider = CodexProvider("test1234", "test-session", "window-0") + await provider._handle_trust_prompt(timeout=2.0) + + mock_tmux.return_value.send_special_key.assert_called_once_with( + "test-session", "window-0", "Enter" + ) + @pytest.mark.asyncio @patch("cli_agent_orchestrator.providers.codex.get_backend") async def test_handle_trust_prompt_not_needed(self, mock_tmux): @@ -1507,6 +1533,20 @@ def test_get_status_trust_prompt_is_waiting_user_answer(self): # Should be WAITING_USER_ANSWER (not PROCESSING despite "running" in text) assert status == TerminalStatus.WAITING_USER_ANSWER + def test_get_status_trust_prompt_new_wording_is_waiting_user_answer(self): + """The reworded trust dialog (codex-cli 0.144.1) must also report + WAITING_USER_ANSWER so the terminal isn't mistaken for PROCESSING.""" + output = ( + "> You are in /Users/test/project\n" + "Do you trust the contents of this directory?\n" + "› 1. Yes, continue\n" + ) + + provider = CodexProvider("test1234", "test-session", "window-0") + status = provider.get_status(output) + + assert status == TerminalStatus.WAITING_USER_ANSWER + @pytest.mark.asyncio @patch("cli_agent_orchestrator.providers.codex.wait_until_status") @patch("cli_agent_orchestrator.providers.codex.wait_for_shell")