Skip to content

Commit 97ab01e

Browse files
committed
Fix: Windows terminal encoding set to UTF-8
1 parent f77a684 commit 97ab01e

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

src/agents/bash-tools.exec-runtime.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ import {
3333
readEnvInt,
3434
} from "./bash-tools.shared.js";
3535
import { buildCursorPositionResponse, stripDsrRequests } from "./pty-dsr.js";
36-
import { getShellConfig, sanitizeBinaryOutput } from "./shell-utils.js";
36+
import {
37+
getShellConfig,
38+
sanitizeBinaryOutput,
39+
wrapCommandWithWindowsPowerShellUtf8,
40+
} from "./shell-utils.js";
3741

3842
// Sanitize inherited host env before merge so dangerous variables from process.env
3943
// are not propagated into non-sandboxed executions.
@@ -417,11 +421,12 @@ export async function runExecProcess(opts: {
417421
};
418422
}
419423
const { shell, args: shellArgs } = getShellConfig();
420-
const childArgv = [shell, ...shellArgs, execCommand];
424+
const runtimeCommand = wrapCommandWithWindowsPowerShellUtf8(execCommand, shell);
425+
const childArgv = [shell, ...shellArgs, runtimeCommand];
421426
if (opts.usePty) {
422427
return {
423428
mode: "pty" as const,
424-
ptyCommand: execCommand,
429+
ptyCommand: runtimeCommand,
425430
childFallbackArgv: childArgv,
426431
env: shellRuntimeEnv,
427432
stdinMode: "pipe-open" as const,

src/agents/shell-utils.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import os from "node:os";
33
import path from "node:path";
44
import { afterEach, beforeEach, describe, expect, it } from "vitest";
55
import { captureEnv } from "../test-utils/env.js";
6-
import { getShellConfig, resolvePowerShellPath, resolveShellFromPath } from "./shell-utils.js";
6+
import {
7+
getShellConfig,
8+
resolvePowerShellPath,
9+
resolveShellFromPath,
10+
wrapCommandWithWindowsPowerShellUtf8,
11+
} from "./shell-utils.js";
712

813
const isWin = process.platform === "win32";
914

@@ -207,3 +212,26 @@ describe("resolvePowerShellPath", () => {
207212
expect(resolvePowerShellPath()).toBe(ps51Path);
208213
});
209214
});
215+
216+
describe("wrapCommandWithWindowsPowerShellUtf8", () => {
217+
it("wraps command with UTF-8 initialization on Windows PowerShell", () => {
218+
const shell = process.platform === "win32" ? "powershell.exe" : "pwsh";
219+
const wrapped = wrapCommandWithWindowsPowerShellUtf8("Write-Output 'ok'", shell);
220+
const shouldWrap = process.platform === "win32";
221+
222+
if (shouldWrap) {
223+
expect(wrapped).toContain("[Console]::OutputEncoding = $__openclawUtf8");
224+
expect(wrapped).toContain("$OutputEncoding = $__openclawUtf8");
225+
expect(wrapped).toContain("chcp 65001 > $null");
226+
expect(wrapped).toContain("Write-Output 'ok'");
227+
return;
228+
}
229+
230+
expect(wrapped).toBe("Write-Output 'ok'");
231+
});
232+
233+
it("does not wrap non-powershell shells", () => {
234+
const wrapped = wrapCommandWithWindowsPowerShellUtf8("echo ok", "bash");
235+
expect(wrapped).toBe("echo ok");
236+
});
237+
});

src/agents/shell-utils.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,34 @@ export function getShellConfig(): { shell: string; args: string[] } {
6969
return { shell, args: ["-c"] };
7070
}
7171

72+
function isWindowsPowerShellShell(shell: string): boolean {
73+
if (process.platform !== "win32") {
74+
return false;
75+
}
76+
const name = path
77+
.basename(shell)
78+
.replace(/\.(exe|cmd|bat)$/i, "")
79+
.toLowerCase();
80+
return name === "powershell" || name === "pwsh";
81+
}
82+
83+
export function wrapCommandWithWindowsPowerShellUtf8(command: string, shell: string): string {
84+
if (!isWindowsPowerShellShell(shell)) {
85+
return command;
86+
}
87+
88+
// Keep command semantics while forcing UTF-8 for both PowerShell text streams
89+
// and external console apps (via code page 65001 when available).
90+
return [
91+
"$__openclawUtf8 = [System.Text.UTF8Encoding]::new($false)",
92+
"[Console]::InputEncoding = $__openclawUtf8",
93+
"[Console]::OutputEncoding = $__openclawUtf8",
94+
"$OutputEncoding = $__openclawUtf8",
95+
"try { chcp 65001 > $null } catch {}",
96+
command,
97+
].join("; ");
98+
}
99+
72100
export function resolveShellFromPath(name: string): string | undefined {
73101
const envPath = process.env.PATH ?? "";
74102
if (!envPath) {

0 commit comments

Comments
 (0)