mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-05 17:08:33 +00:00
Fix Windows exec UTF-16 output decoding
This commit is contained in:
parent
3b14d59dcd
commit
9cdf17f5d5
@ -65,6 +65,17 @@ def _reap_pid(pid: int) -> None:
|
|||||||
logger.debug("_reap_pid({}): {}", pid, exc)
|
logger.debug("_reap_pid({}): {}", pid, exc)
|
||||||
|
|
||||||
|
|
||||||
|
def _decode_process_output(data: bytes) -> str:
|
||||||
|
if not data:
|
||||||
|
return ""
|
||||||
|
if _IS_WINDOWS and b"\x00" in data[:200]:
|
||||||
|
try:
|
||||||
|
return data.decode("utf-16")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
pass
|
||||||
|
return data.decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
|
||||||
# Policy note appended to recoverable workspace-boundary guard errors.
|
# Policy note appended to recoverable workspace-boundary guard errors.
|
||||||
_WORKSPACE_BOUNDARY_NOTE = (
|
_WORKSPACE_BOUNDARY_NOTE = (
|
||||||
"\n\nNote: this is a hard policy boundary, not a transient failure. "
|
"\n\nNote: this is a hard policy boundary, not a transient failure. "
|
||||||
@ -337,10 +348,10 @@ class ExecTool(Tool):
|
|||||||
output_parts = []
|
output_parts = []
|
||||||
|
|
||||||
if stdout:
|
if stdout:
|
||||||
output_parts.append(stdout.decode("utf-8", errors="replace"))
|
output_parts.append(_decode_process_output(stdout))
|
||||||
|
|
||||||
if stderr:
|
if stderr:
|
||||||
stderr_text = stderr.decode("utf-8", errors="replace")
|
stderr_text = _decode_process_output(stderr)
|
||||||
if stderr_text.strip():
|
if stderr_text.strip():
|
||||||
output_parts.append(f"STDERR:\n{stderr_text}")
|
output_parts.append(f"STDERR:\n{stderr_text}")
|
||||||
|
|
||||||
|
|||||||
@ -465,6 +465,29 @@ class TestExecuteEndToEnd:
|
|||||||
assert "hello world" in result
|
assert "hello world" in result
|
||||||
assert "Exit code: 0" in result
|
assert "Exit code: 0" in result
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_windows_decodes_utf16_output(self):
|
||||||
|
"""PowerShell output may arrive as UTF-16LE on Windows."""
|
||||||
|
mock_proc = AsyncMock()
|
||||||
|
mock_proc.communicate.return_value = (
|
||||||
|
"ok café\r\n".encode("utf-16-le"),
|
||||||
|
"warn λ\r\n".encode("utf-16-le"),
|
||||||
|
)
|
||||||
|
mock_proc.returncode = 0
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("nanobot.agent.tools.shell._IS_WINDOWS", True),
|
||||||
|
patch.object(ExecTool, "_spawn", return_value=mock_proc),
|
||||||
|
patch.object(ExecTool, "_guard_command", return_value=None),
|
||||||
|
):
|
||||||
|
tool = ExecTool()
|
||||||
|
result = await tool.execute(command="echo ok")
|
||||||
|
|
||||||
|
assert "ok café" in result
|
||||||
|
assert "warn λ" in result
|
||||||
|
assert "\x00" not in result
|
||||||
|
assert "Exit code: 0" in result
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_unix_full_path(self):
|
async def test_unix_full_path(self):
|
||||||
"""Full execute() flow on Unix: env, spawn, output formatting."""
|
"""Full execute() flow on Unix: env, spawn, output formatting."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user