mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-04 16:38:49 +00:00
fix(agent): stop on workspace violations from tool errors
Treat workspace and safety guard failures as fatal regardless of whether they arrive from tool preparation, returned tool output, or raised exceptions. Made-with: Cursor
This commit is contained in:
parent
f19d767b0f
commit
48f3cc6390
@ -764,6 +764,15 @@ class AgentRunner:
|
||||
"status": "error",
|
||||
"detail": prep_error.split(": ", 1)[-1][:120],
|
||||
}
|
||||
if self._is_workspace_violation(prep_error):
|
||||
logger.warning(
|
||||
"Tool {} blocked by workspace/safety guard during preparation; aborting turn: {}",
|
||||
tool_call.name,
|
||||
prep_error.replace("\n", " ").strip()[:200],
|
||||
)
|
||||
event["detail"] = ("workspace_violation: "
|
||||
+ prep_error.replace("\n", " ").strip())[:160]
|
||||
return prep_error, event, RuntimeError(prep_error)
|
||||
return prep_error + hint, event, RuntimeError(prep_error) if spec.fail_on_tool_error else None
|
||||
try:
|
||||
if tool is not None:
|
||||
@ -781,6 +790,15 @@ class AgentRunner:
|
||||
if isinstance(exc, AskUserInterrupt):
|
||||
event["status"] = "waiting"
|
||||
return "", event, exc
|
||||
if self._is_workspace_violation(str(exc)):
|
||||
logger.warning(
|
||||
"Tool {} blocked by workspace/safety guard; aborting turn: {}",
|
||||
tool_call.name,
|
||||
str(exc).replace("\n", " ").strip()[:200],
|
||||
)
|
||||
event["detail"] = ("workspace_violation: "
|
||||
+ str(exc).replace("\n", " ").strip())[:160]
|
||||
return f"Error: {type(exc).__name__}: {exc}", event, exc
|
||||
if spec.fail_on_tool_error:
|
||||
return f"Error: {type(exc).__name__}: {exc}", event, exc
|
||||
return f"Error: {type(exc).__name__}: {exc}", event, None
|
||||
|
||||
@ -312,6 +312,46 @@ async def test_runner_returns_structured_tool_error():
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runner_stops_on_workspace_violation_without_fail_on_tool_error():
|
||||
from nanobot.agent.runner import AgentRunSpec, AgentRunner
|
||||
|
||||
provider = MagicMock()
|
||||
provider.chat_with_retry = AsyncMock(side_effect=[
|
||||
LLMResponse(
|
||||
content="working",
|
||||
tool_calls=[ToolCallRequest(id="call_1", name="read_file", arguments={"path": "/tmp/outside.md"})],
|
||||
),
|
||||
LLMResponse(content="should not continue", tool_calls=[]),
|
||||
])
|
||||
tools = MagicMock()
|
||||
tools.get_definitions.return_value = []
|
||||
tools.execute = AsyncMock(
|
||||
side_effect=PermissionError("Path /tmp/outside.md is outside allowed directory /workspace")
|
||||
)
|
||||
|
||||
runner = AgentRunner(provider)
|
||||
|
||||
result = await runner.run(AgentRunSpec(
|
||||
initial_messages=[],
|
||||
tools=tools,
|
||||
model="test-model",
|
||||
max_iterations=2,
|
||||
max_tool_result_chars=_MAX_TOOL_RESULT_CHARS,
|
||||
))
|
||||
|
||||
assert provider.chat_with_retry.await_count == 1
|
||||
assert result.stop_reason == "tool_error"
|
||||
assert "outside allowed directory" in (result.error or "")
|
||||
assert result.tool_events == [
|
||||
{
|
||||
"name": "read_file",
|
||||
"status": "error",
|
||||
"detail": "workspace_violation: Path /tmp/outside.md is outside allowed directory /workspace",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runner_persists_large_tool_results_for_follow_up_calls(tmp_path):
|
||||
from nanobot.agent.runner import AgentRunSpec, AgentRunner
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user