From 5a28a6165cf1fb979970745cf14d1fc2adc8a5fb Mon Sep 17 00:00:00 2001 From: Zhou <32321321@qq.com> Date: Wed, 29 Jul 2026 21:47:01 +0800 Subject: [PATCH] fix(shell): preserve UTF-8 native input on PowerShell 5 (#5160) Windows PowerShell 5.1 defaults $OutputEncoding to US-ASCII, corrupting non-ASCII strings piped to native commands. Set it from the console encoding only on legacy versions so PowerShell 7 keeps its defaults. --- nanobot/agent/tools/shell.py | 1 + tests/tools/test_exec_platform.py | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/nanobot/agent/tools/shell.py b/nanobot/agent/tools/shell.py index be3d5c04b..6868a30d0 100644 --- a/nanobot/agent/tools/shell.py +++ b/nanobot/agent/tools/shell.py @@ -556,6 +556,7 @@ class ExecTool(Tool): command = ExecTool._normalize_powershell_command(command) command = ( "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)\n" + "if ($PSVersionTable.PSVersion.Major -lt 6) { $OutputEncoding = [Console]::OutputEncoding }\n" "$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'\n" f"{command}\n" "if ($LASTEXITCODE -ne $null) { exit $LASTEXITCODE }" diff --git a/tests/tools/test_exec_platform.py b/tests/tools/test_exec_platform.py index 9981e2200..cd759ad8e 100644 --- a/tests/tools/test_exec_platform.py +++ b/tests/tools/test_exec_platform.py @@ -230,8 +230,8 @@ class TestSpawnWindows: assert "if ($LASTEXITCODE -ne $null) { exit $LASTEXITCODE }" in command @pytest.mark.asyncio - async def test_powershell_configures_utf8_output(self): - """PowerShell should emit UTF-8 for captured output and redirections.""" + async def test_powershell_configures_utf8_io(self): + """PowerShell should use UTF-8 for captured output, native input, and redirections.""" env = {"PATH": ""} with ( patch("nanobot.agent.tools.shell._IS_WINDOWS", True), @@ -242,7 +242,10 @@ class TestSpawnWindows: command = mock_exec.call_args[0][-1] assert "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)" in command - assert "$OutputEncoding =" not in command + assert ( + "if ($PSVersionTable.PSVersion.Major -lt 6) { " + "$OutputEncoding = [Console]::OutputEncoding }" + ) in command assert "$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'" in command @pytest.mark.asyncio @@ -821,6 +824,20 @@ class TestWindowsRealExec: assert b"\x00" not in data assert data.decode("utf-8-sig").strip() == "file café λ 你好" + @pytest.mark.asyncio + async def test_windows_powershell_native_pipeline_input_is_utf8(self): + python = sys.executable.replace("'", "''") + result = await ExecTool(timeout=180).execute( + command=( + f"[string][char]0x4F1A | & '{python}' " + '-c "import sys; print(sys.stdin.buffer.read().hex())"' + ), + shell="powershell", + ) + + assert "e4bc9a0d0a" in result + assert "Exit code: 0" in result + @pytest.mark.asyncio async def test_windows_powershell_session_output_is_utf8(self): manager = ExecSessionManager()