fix: stabilize agent file change previews

This commit is contained in:
Hamza Ayed
2026-10-07 21:10:36 +03:00
parent fc36f54f15
commit 95c9bc231b
9 changed files with 823 additions and 224 deletions
@@ -16,6 +16,7 @@ if "SOVEREIGNAI_DATA_DIR" not in os.environ:
from app.main import (
AgentRequest,
_execute_agent,
_explicit_file_change_requested,
_explicit_workspace_search_queries,
_knowledge_context_for_model,
_is_knowledge_answer_insufficient,
@@ -235,6 +236,130 @@ class AgentSkillTests(unittest.TestCase):
self.assertEqual(result["skill"], "code_review")
self.assertNotIn("proposal", result)
def test_explicit_file_change_retries_with_forced_preview_tool(self) -> None:
completions = [
{"choices": [{"message": {"content": "سأفحص الطلب."}}]},
{
"choices": [
{
"message": {
"tool_calls": [
{
"id": "proposal-retry",
"function": {
"name": "propose_file_change",
"arguments": json.dumps(
{
"path": "README.md",
"operation": "update",
"content": "# Updated\n",
},
),
},
}
]
}
}
]
},
{"choices": [{"message": {"content": "تم إعداد المعاينة فقط."}}]},
]
proposal = {
"token": "preview-token",
"path": "README.md",
"operation": "update",
"diff": "--- a/README.md\n+++ b/README.md\n+updated\n",
"expires_in_seconds": 600,
}
with (
patch(
"app.main.workspace.create_change_preview",
return_value=proposal,
) as create_preview,
patch(
"app.main.get_completion",
new=AsyncMock(side_effect=completions),
) as model,
):
result = asyncio.run(
_execute_agent(
AgentRequest(
task="نفّذ تعديلًا صريحًا: حدّث README.md وأعد معاينة diff فقط.",
workspace_path=self.workspace,
workspace_files=["README.md"],
skill_id="code_review",
)
)
)
self.assertEqual(model.await_count, 3)
forced_payload = model.await_args_list[1].args[0]
self.assertEqual(
forced_payload["tool_choice"],
{"type": "function", "function": {"name": "propose_file_change"}},
)
self.assertEqual(
[tool["function"]["name"] for tool in forced_payload["tools"]],
["propose_file_change"],
)
self.assertEqual(len(forced_payload["messages"]), 2)
self.assertIn("أعد نداء أداة propose_file_change الآن", forced_payload["messages"][0]["content"])
create_preview.assert_called_once()
self.assertEqual(result["proposal"]["diff"], proposal["diff"])
self.assertEqual(result["tool"], "propose_file_change")
def test_explicit_file_change_fails_clearly_if_retry_has_no_tool_call(self) -> None:
completions = [
{"choices": [{"message": {"content": "سأعمل على التعديل."}}]},
{"choices": [{"message": {"content": "تعذر إنشاء معاينة."}}]},
]
with (
patch(
"app.main.get_completion",
new=AsyncMock(side_effect=completions),
) as model,
patch("app.main.workspace.create_change_preview") as create_preview,
):
with self.assertRaises(HTTPException) as error:
asyncio.run(
_execute_agent(
AgentRequest(
task="نفّذ تعديلًا صريحًا: حدّث README.md وأعد معاينة diff فقط.",
workspace_path=self.workspace,
workspace_files=["README.md"],
skill_id="code_review",
)
)
)
self.assertEqual(error.exception.status_code, 502)
self.assertIn("لم يُكتب أي ملف", error.exception.detail)
self.assertEqual(model.await_count, 2)
create_preview.assert_not_called()
def test_file_change_retry_requires_positive_intent_and_file_target(self) -> None:
self.assertTrue(
_explicit_file_change_requested(
"نفّذ تعديلًا صريحًا على README.md", selected_files=False
)
)
self.assertTrue(
_explicit_file_change_requested("أضف شرحًا", selected_files=True)
)
self.assertFalse(
_explicit_file_change_requested(
"راجع الملفات دون تعديل", selected_files=True
)
)
self.assertFalse(
_explicit_file_change_requested(
"كيف أعدّل README.md؟", selected_files=False
)
)
self.assertFalse(
_explicit_file_change_requested("أنشئ خطة اختبار", selected_files=False)
)
def test_model_cannot_call_a_tool_after_file_proposal_ends_tool_access(self) -> None:
completions = [
{"choices": [{"message": {"tool_calls": [{