fix(core): force UTF-8 for Windows shells

Force cmd, PowerShell, and piped Python subprocesses to emit UTF-8 so V2 shell output is decoded without CJK mojibake.

Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
This commit is contained in:
Dax Raad
2026-07-13 03:48:24 +00:00
parent 3d545f960b
commit 34cfa8601f
5 changed files with 43 additions and 4 deletions
+1
View File
@@ -173,6 +173,7 @@ export const layer = Layer.effect(
const file = path.join(outputDir, `${id}.out`)
const env = {
...process.env,
...ShellSelect.env(),
TERM: "xterm-256color",
OPENCODE_TERMINAL: "1",
} as Record<string, string>
+23 -2
View File
@@ -163,12 +163,33 @@ function info(file: string): Item {
}
}
export function env(platform = process.platform) {
if (platform === "win32") return { PYTHONIOENCODING: "utf-8" }
return {}
}
export function args(file: string, command: string) {
const n = name(file)
if (n === "nu" || n === "fish") return ["-c", command]
if (n === "zsh" || n === "bash") return ["-c", command]
if (n === "cmd") return ["/c", command]
if (ps(file)) return ["-NoProfile", "-Command", command]
if (n === "cmd") return ["/d", "/c", `chcp 65001 >nul 2>nul & ${command}`]
if (ps(file)) {
const payload = Buffer.from(command, "utf8").toString("base64")
const script = [
"$utf8 = [System.Text.UTF8Encoding]::new($false)",
"[Console]::InputEncoding = $utf8",
"[Console]::OutputEncoding = $utf8",
"$OutputEncoding = $utf8",
`& ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${payload}'))))`,
].join("; ")
return [
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-EncodedCommand",
Buffer.from(script, "utf16le").toString("base64"),
]
}
return ["-c", command]
}
+1 -1
View File
@@ -70,7 +70,7 @@ const modelOutput = (output: Output): string | undefined => {
// TODO: Port tree-sitter bash / PowerShell parser-based approval reduction.
// TODO: Port BashArity reusable command-prefix approvals.
// TODO: Replace token-based command-argument external-directory advisories with parser-based detection.
// TODO: Restore PowerShell and cmd-specific invocation/path handling on Windows.
// TODO: Restore Windows shell-specific path handling.
// TODO: Add plugin shell.env environment augmentation once V2 plugin hooks exist.
// TODO: Add durable/live progress metadata streaming for long-running commands once V2 tool invocation progress context is wired.
// TODO: Persist job status and define restart recovery before exposing remote observation.
+17
View File
@@ -61,6 +61,23 @@ describe("shell", () => {
expect(ShellSelect.args("/bin/bash", "echo hi")).toEqual(["-c", "echo hi"])
})
test("switches cmd output to UTF-8", () => {
expect(ShellSelect.args("cmd", 'echo "你好"')).toEqual(["/d", "/c", 'chcp 65001 >nul 2>nul & echo "你好"'])
})
test("transports PowerShell commands independently of the active code page", () => {
const args = ShellSelect.args("pwsh", 'Write-Output "你好"')
expect(args.slice(0, 4)).toEqual(["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand"])
expect(Buffer.from(args[4], "base64").toString("utf16le")).toBe(
"$utf8 = [System.Text.UTF8Encoding]::new($false); [Console]::InputEncoding = $utf8; [Console]::OutputEncoding = $utf8; $OutputEncoding = $utf8; & ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('V3JpdGUtT3V0cHV0ICLkvaDlpb0i'))))",
)
})
test("forces piped Python output to UTF-8 on Windows", () => {
expect(ShellSelect.env("win32")).toEqual({ PYTHONIOENCODING: "utf-8" })
expect(ShellSelect.env("linux")).toEqual({})
})
if (process.platform === "win32") {
test("rejects blacklisted shells case-insensitively", async () => {
await withShell("NU.EXE", async () => {
+1 -1
View File
@@ -570,7 +570,7 @@ test("keeps locked deferred parity TODOs visible", async () => {
"Port tree-sitter bash / PowerShell parser-based approval reduction.",
"Port BashArity reusable command-prefix approvals.",
"Replace token-based command-argument external-directory advisories with parser-based detection.",
"Restore PowerShell and cmd-specific invocation/path handling on Windows.",
"Restore Windows shell-specific path handling.",
"Add plugin shell.env environment augmentation once V2 plugin hooks exist.",
"Add durable/live progress metadata streaming for long-running commands once V2 tool invocation progress context is wired.",
"Persist job status and define restart recovery before exposing remote observation.",