@@ -129,3 +129,119 @@ async def test_turn_completion_marks_non_mutating_response_done( |
| 129 | 129 | event.type == "dod_status" and event.dod_status == "done" |
| 130 | 130 | for event in events |
| 131 | 131 | ) |
| 132 | + |
| 133 | + |
| 134 | +@pytest.mark.asyncio |
| 135 | +async def test_turn_completion_handles_fake_tool_narration_without_reroute( |
| 136 | + temp_dir: Path, |
| 137 | +) -> None: |
| 138 | + backend = ScriptedBackend() |
| 139 | + config = non_streaming_config() |
| 140 | + config.reasoning.completion_check = False |
| 141 | + agent = Agent( |
| 142 | + backend=backend, |
| 143 | + config=config, |
| 144 | + project_root=temp_dir, |
| 145 | + ) |
| 146 | + runtime = ConversationRuntime(agent) |
| 147 | + events = [] |
| 148 | + |
| 149 | + async def capture(event) -> None: |
| 150 | + events.append(event) |
| 151 | + |
| 152 | + prepared = await runtime.turn_preparation.prepare( |
| 153 | + task="Summarize the current test status.", |
| 154 | + emit=capture, |
| 155 | + requested_mode="execute", |
| 156 | + original_task=None, |
| 157 | + on_user_question=None, |
| 158 | + ) |
| 159 | + await runtime.phase_tracker.enter( |
| 160 | + TurnPhase.ASSISTANT, |
| 161 | + capture, |
| 162 | + detail="Requesting assistant response", |
| 163 | + reason_code="request_assistant_response", |
| 164 | + ) |
| 165 | + |
| 166 | + narrated = "Used bash tool with command `pytest -q` and everything passed." |
| 167 | + decision = await runtime.turn_completion.handle_text_response( |
| 168 | + content=narrated, |
| 169 | + response_content=narrated, |
| 170 | + task=prepared.task, |
| 171 | + effective_task=prepared.effective_task, |
| 172 | + iterations=1, |
| 173 | + max_iterations=agent.config.max_iterations, |
| 174 | + actions_taken=[], |
| 175 | + continuation_count=0, |
| 176 | + dod=prepared.definition_of_done, |
| 177 | + emit=capture, |
| 178 | + summary=prepared.summary, |
| 179 | + executor=prepared.executor, |
| 180 | + rollback_plan=prepared.rollback_plan, |
| 181 | + ) |
| 182 | + |
| 183 | + assert decision.action == TurnCompletionAction.COMPLETE |
| 184 | + assert prepared.summary.final_response == narrated |
| 185 | + assert not any( |
| 186 | + "PRETENDING to use tools" in message.content |
| 187 | + for message in agent.session.messages |
| 188 | + ) |
| 189 | + assert any(event.type == "response" and event.content == narrated for event in events) |
| 190 | + |
| 191 | + |
| 192 | +@pytest.mark.asyncio |
| 193 | +async def test_turn_completion_handles_deflection_text_without_repair_prompt( |
| 194 | + temp_dir: Path, |
| 195 | +) -> None: |
| 196 | + backend = ScriptedBackend() |
| 197 | + config = non_streaming_config() |
| 198 | + config.reasoning.completion_check = False |
| 199 | + agent = Agent( |
| 200 | + backend=backend, |
| 201 | + config=config, |
| 202 | + project_root=temp_dir, |
| 203 | + ) |
| 204 | + runtime = ConversationRuntime(agent) |
| 205 | + events = [] |
| 206 | + |
| 207 | + async def capture(event) -> None: |
| 208 | + events.append(event) |
| 209 | + |
| 210 | + prepared = await runtime.turn_preparation.prepare( |
| 211 | + task="What should I verify next?", |
| 212 | + emit=capture, |
| 213 | + requested_mode="execute", |
| 214 | + original_task=None, |
| 215 | + on_user_question=None, |
| 216 | + ) |
| 217 | + await runtime.phase_tracker.enter( |
| 218 | + TurnPhase.ASSISTANT, |
| 219 | + capture, |
| 220 | + detail="Requesting assistant response", |
| 221 | + reason_code="request_assistant_response", |
| 222 | + ) |
| 223 | + |
| 224 | + deflection = "You can run pytest -q to verify the current state." |
| 225 | + decision = await runtime.turn_completion.handle_text_response( |
| 226 | + content=deflection, |
| 227 | + response_content=deflection, |
| 228 | + task=prepared.task, |
| 229 | + effective_task=prepared.effective_task, |
| 230 | + iterations=1, |
| 231 | + max_iterations=agent.config.max_iterations, |
| 232 | + actions_taken=[], |
| 233 | + continuation_count=0, |
| 234 | + dod=prepared.definition_of_done, |
| 235 | + emit=capture, |
| 236 | + summary=prepared.summary, |
| 237 | + executor=prepared.executor, |
| 238 | + rollback_plan=prepared.rollback_plan, |
| 239 | + ) |
| 240 | + |
| 241 | + assert decision.action == TurnCompletionAction.COMPLETE |
| 242 | + assert prepared.summary.final_response == deflection |
| 243 | + assert not any( |
| 244 | + "Please use your tools to execute the task" in message.content |
| 245 | + for message in agent.session.messages |
| 246 | + ) |
| 247 | + assert any(event.type == "response" and event.content == deflection for event in events) |