mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-08-27 10:51:26 -04:00
fix(code): simplify Auto fallback copy (#5670)
Auto fallback prompts now explain that review is needed without exposing internal denial and availability counters. --- Threshold reasons and counters remain available at `DEBUG`. The transcript and approval description use plain language while preserving the Switch-to-Manual flow and shell allow-list guard. Made by [Open SWE](https://openswe.vercel.app/agents/434260c2-4bed-5449-a0b4-95de592be3ec) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
@@ -9213,7 +9213,7 @@ class DeepAgentsApp(App):
|
||||
|
||||
is_auto_fallback = any(
|
||||
isinstance(request.get("description"), str)
|
||||
and request["description"].startswith("Auto human fallback ")
|
||||
and request["description"].startswith("Auto human fallback")
|
||||
for request in action_requests or []
|
||||
)
|
||||
if settings.shell_allow_list and action_requests and not is_auto_fallback:
|
||||
@@ -20718,11 +20718,18 @@ class DeepAgentsApp(App):
|
||||
text = f"Auto fell back to Manual: {reason}"
|
||||
self.notify(text, severity="warning", timeout=10, markup=False)
|
||||
else:
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug(
|
||||
"Auto human fallback reason=%s consecutive_denials=%s "
|
||||
"consecutive_unavailable=%s total_denials=%s",
|
||||
reason,
|
||||
event.get("consecutive_denials", 0),
|
||||
event.get("consecutive_unavailable", 0),
|
||||
event.get("total_denials", 0),
|
||||
)
|
||||
text = (
|
||||
"Auto fallback: human approval required "
|
||||
f"(denials {event.get('consecutive_denials', 0)}, "
|
||||
f"unavailable {event.get('consecutive_unavailable', 0)}, "
|
||||
f"total {event.get('total_denials', 0)})."
|
||||
"Auto couldn't confidently approve this action, so it needs your "
|
||||
"review. Auto will continue afterward."
|
||||
)
|
||||
else:
|
||||
text = f"Auto warning: {reason}"
|
||||
|
||||
@@ -2979,21 +2979,14 @@ class AutoModeHITLMiddleware(HumanInTheLoopMiddleware[AutoModeState, Any, Any]):
|
||||
runtime: object,
|
||||
*,
|
||||
fallback: bool,
|
||||
counters: AutoModeCounters | None,
|
||||
fallback_reason: str | None = None,
|
||||
) -> tuple[ActionRequest, ReviewConfig]:
|
||||
config = self.interrupt_on[tool_call["name"]]
|
||||
action, review = self._create_action_and_config(
|
||||
tool_call, config, state, cast("Any", runtime)
|
||||
)
|
||||
if fallback:
|
||||
counts = counters or _default_counters(ApprovalMode.AUTO)
|
||||
reason = f"reason: {fallback_reason}; " if fallback_reason else ""
|
||||
action["description"] = (
|
||||
"Auto human fallback "
|
||||
f"({reason}consecutive denials: {counts['consecutive_denials']}, "
|
||||
f"classifier unavailable: {counts['consecutive_unavailable']}, "
|
||||
f"total denials: {counts['total_denials']}).\n\n"
|
||||
"Auto human fallback: this action needs your review.\n\n"
|
||||
f"{action.get('description', '')}"
|
||||
)
|
||||
return action, review
|
||||
@@ -3023,8 +3016,6 @@ class AutoModeHITLMiddleware(HumanInTheLoopMiddleware[AutoModeState, Any, Any]):
|
||||
state,
|
||||
runtime,
|
||||
fallback=fallback,
|
||||
counters=counters,
|
||||
fallback_reason=fallback_reason,
|
||||
)
|
||||
action_requests.append(action)
|
||||
review_configs.append(review)
|
||||
@@ -3084,7 +3075,7 @@ class AutoModeHITLMiddleware(HumanInTheLoopMiddleware[AutoModeState, Any, Any]):
|
||||
manual_reviews: list[ReviewConfig] = []
|
||||
for call in manual_calls:
|
||||
action, review = self._action_and_config(
|
||||
call, state, runtime, fallback=False, counters=counters
|
||||
call, state, runtime, fallback=False
|
||||
)
|
||||
manual_actions.append(action)
|
||||
manual_reviews.append(review)
|
||||
|
||||
@@ -172,7 +172,7 @@ class ApprovalMenu(Container):
|
||||
self._tool_names = [r.get("name", "unknown") for r in self._action_requests]
|
||||
self._is_auto_fallback = any(
|
||||
isinstance(request.get("description"), str)
|
||||
and request["description"].startswith("Auto human fallback ")
|
||||
and request["description"].startswith("Auto human fallback")
|
||||
for request in self._action_requests
|
||||
)
|
||||
# Only offer the Auto option when it can actually be enabled. A live
|
||||
|
||||
@@ -17863,6 +17863,30 @@ class TestIsUserTyping:
|
||||
class TestRequestApprovalBranching:
|
||||
"""_request_approval should show a placeholder when the user is typing."""
|
||||
|
||||
async def test_auto_fallback_skips_shell_allow_list(self) -> None:
|
||||
from deepagents_code.config import settings
|
||||
|
||||
app = DeepAgentsApp(agent=MagicMock())
|
||||
app._last_typed_at = None
|
||||
action_requests = [
|
||||
{
|
||||
"name": "execute",
|
||||
"args": {"command": "echo hello"},
|
||||
"description": "Auto human fallback: this action needs your review.",
|
||||
}
|
||||
]
|
||||
|
||||
with (
|
||||
patch.object(settings, "shell_allow_list", ["echo"]),
|
||||
patch.object(app, "_mount_approval_widget", new=AsyncMock()) as mount,
|
||||
patch.object(app, "_reveal_pending_tool_calls"),
|
||||
patch.object(app, "_pause_loading_spinner_for_approval"),
|
||||
):
|
||||
result = await app._request_approval(action_requests, None)
|
||||
|
||||
assert not result.done()
|
||||
mount.assert_awaited_once()
|
||||
|
||||
async def test_placeholder_mounted_when_typing(self) -> None:
|
||||
"""If the user is typing, a Static placeholder is mounted instead of menu."""
|
||||
app = DeepAgentsApp(agent=MagicMock())
|
||||
@@ -30527,6 +30551,36 @@ class TestLiveApprovalModeWrites:
|
||||
warn.assert_called_once()
|
||||
assert "Could not open the Auto confirmation" in str(warn.call_args.args[0])
|
||||
|
||||
async def test_server_auto_fallback_hides_diagnostics(
|
||||
self, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
app = DeepAgentsApp()
|
||||
event = {
|
||||
"event": "fallback",
|
||||
"reason": "consecutive_policy_denials",
|
||||
"consecutive_denials": 4,
|
||||
"consecutive_unavailable": 0,
|
||||
"total_denials": 4,
|
||||
}
|
||||
caplog.set_level(logging.DEBUG, logger="deepagents_code.app")
|
||||
|
||||
with patch.object(app, "_mount_message", new=AsyncMock()) as mount:
|
||||
await app._on_auto_mode_event(event)
|
||||
|
||||
await_args = mount.await_args
|
||||
assert await_args is not None
|
||||
mounted = await_args.args[0]
|
||||
assert isinstance(mounted, AppMessage)
|
||||
assert mounted._content == (
|
||||
"Auto couldn't confidently approve this action, so it needs your review. "
|
||||
"Auto will continue afterward."
|
||||
)
|
||||
assert (
|
||||
"Auto human fallback reason=consecutive_policy_denials "
|
||||
"consecutive_denials=4 consecutive_unavailable=0 total_denials=4"
|
||||
in caplog.messages
|
||||
)
|
||||
|
||||
async def test_server_manual_fallback_updates_tui_mode_and_warns(self) -> None:
|
||||
from deepagents_code.approval_mode import ApprovalMode
|
||||
|
||||
|
||||
@@ -1917,7 +1917,13 @@ async def test_unavailable_auto_control_state_surfaces_manual_fallback(
|
||||
|
||||
hitl_request = review.call_args.args[0]
|
||||
description = hitl_request["action_requests"][0]["description"]
|
||||
assert description.startswith("Auto human fallback ")
|
||||
assert description.startswith(
|
||||
"Auto human fallback: this action needs your review.\n\n"
|
||||
)
|
||||
assert "consecutive denials" not in description
|
||||
assert "classifier unavailable" not in description
|
||||
assert "total denials" not in description
|
||||
assert "Auto control state was unavailable" not in description
|
||||
assert events == [
|
||||
{
|
||||
"type": "auto_mode",
|
||||
|
||||
@@ -359,7 +359,7 @@ class TestOptionOrdering:
|
||||
{
|
||||
"name": "delete",
|
||||
"args": {"file_path": "old.py"},
|
||||
"description": "Auto human fallback (consecutive denials: 3).",
|
||||
"description": "Auto human fallback: this action needs your review.",
|
||||
}
|
||||
)
|
||||
menu.set_future(future)
|
||||
@@ -539,7 +539,7 @@ class TestAutoOptionEligibility:
|
||||
{
|
||||
"name": "delete",
|
||||
"args": {"file_path": "old.py"},
|
||||
"description": "Auto human fallback (consecutive denials: 3).",
|
||||
"description": "Auto human fallback: this action needs your review.",
|
||||
},
|
||||
auto_mode_eligible=False,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user