From e95384de069e6473a0fa8d78b0174d4e2fbd41d6 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 22 Jun 2026 09:51:22 +0200 Subject: [PATCH] fix(cli): preserve PowerShell prologues --- packages/opencode/src/kilocode/shell/shell.ts | 78 ++++++++++++++++++- .../test/kilocode/shell/shell.test.ts | 15 ++++ 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/kilocode/shell/shell.ts b/packages/opencode/src/kilocode/shell/shell.ts index d69be3b143..be562526b7 100644 --- a/packages/opencode/src/kilocode/shell/shell.ts +++ b/packages/opencode/src/kilocode/shell/shell.ts @@ -2,9 +2,81 @@ export function args(command: string) { return ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script(command)] } -function script(command: string) { - return `[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false); +const setup = `[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false); [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); $OutputEncoding = [Console]::OutputEncoding; -${command}` +` + +function script(command: string) { + const pos = prologue(command) + const head = command.slice(0, pos) + const body = command.slice(pos) + const gap = head && !/[;\r\n]\s*$/.test(head) ? "\n" : "" + return `${head}${gap}${setup}${body}` +} + +function prologue(command: string) { + const pos = scan(command, 0) + const body = command.slice(pos) + const match = /^\s*param\s*\(/i.exec(body) + if (!match) return pos + + const start = match[0].lastIndexOf("(") + const end = block(body, start) + if (end === undefined) return pos + return pos + end +} + +function scan(command: string, start: number) { + let pos = start + while (pos < command.length) { + const end = line(command, pos) + const value = command.slice(pos, end) + const text = value.trimStart() + if (text.trim() === "" || text.startsWith("#") || /^using\s+(?:assembly|module|namespace|type)\b/i.test(text)) { + pos = end + continue + } + return pos + } + return pos +} + +function line(command: string, start: number) { + const index = command.indexOf("\n", start) + if (index === -1) return command.length + return index + 1 +} + +function block(command: string, start: number) { + let depth = 0 + let quote: string | undefined + for (let pos = start; pos < command.length; pos++) { + const char = command[pos] + if (quote) { + if (quote === "'" && char === "'" && command[pos + 1] === "'") { + pos++ + continue + } + if (quote === '"' && char === "`") { + pos++ + continue + } + if (char === quote) quote = undefined + continue + } + if (char === "'" || char === '"') { + quote = char + continue + } + if (char === "#") { + pos = line(command, pos) - 1 + continue + } + if (char === "(") depth++ + if (char === ")") { + depth-- + if (depth === 0) return pos + 1 + } + } } diff --git a/packages/opencode/test/kilocode/shell/shell.test.ts b/packages/opencode/test/kilocode/shell/shell.test.ts index 20c007f185..5ede0443ec 100644 --- a/packages/opencode/test/kilocode/shell/shell.test.ts +++ b/packages/opencode/test/kilocode/shell/shell.test.ts @@ -23,4 +23,19 @@ describe("PowerShell arguments", () => { test.each(["powershell", "pwsh"])("routes %s through the Kilo argument builder", (shell) => { expect(Shell.args(shell, command, "/tmp")).toEqual(PowerShell.args(command)) }) + + test("keeps script-level prologue before the UTF-8 console setup", () => { + const input = `#requires -Version 5.1 +using namespace System.Text +param( + [string]$Name +) +Write-Output $Name` + const value = PowerShell.args(input)[4] + const setup = value.indexOf("[Console]::InputEncoding") + + expect(value.startsWith("#requires -Version 5.1")).toBe(true) + expect(setup).toBeGreaterThan(value.indexOf(")")) + expect(setup).toBeLessThan(value.indexOf("Write-Output")) + }) })