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": [{
@@ -2,7 +2,10 @@ from __future__ import annotations
import unittest
from scripts.generate_flutter_license_triage import candidate_license
from scripts.generate_flutter_license_triage import (
candidate_license,
license_signatures,
)
class FlutterLicenseTriageTests(unittest.TestCase):
@@ -16,9 +19,26 @@ class FlutterLicenseTriageTests(unittest.TestCase):
self.assertEqual(candidate_license(bsd3)[0], "BSD-3-Clause")
def test_keeps_ambiguous_notice_unclassified(self) -> None:
self.assertIsNone(candidate_license("Copyright 2026 Example Authors. All rights reserved." )[0])
self.assertIsNone(
candidate_license("Copyright 2026 Example Authors. All rights reserved.")[0]
)
mixed = "Mozilla Public License Version 2.0. Apache License Version 2.0."
self.assertIsNone(candidate_license(mixed)[0])
self.assertEqual(
sorted(license_signatures(mixed)),
["Apache-2.0", "MPL-2.0"],
)
def test_detects_license_signatures_in_composite_notice(self) -> None:
text = """Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met. Neither the name of the project
may be used to endorse products. THIS SOFTWARE IS PROVIDED AS IS; DISCLAIMER.
Apache License
Version 2.0, January 2004
"""
signatures = license_signatures(text)
self.assertEqual(sorted(signatures), ["Apache-2.0", "BSD-3-Clause"])
self.assertIsNone(candidate_license(text)[0])
if __name__ == "__main__":