fix: render pwsh and powershell tools correctly in UI

This fixes regressions from splitting the shell tools where powershell commands were missing their native exit codes and their correct UI rendering.
This commit is contained in:
LukeParkerDev 2026-04-03 14:01:13 +10:00
parent 95577c75a3
commit 6ad6358eb1
4 changed files with 51 additions and 7 deletions

View file

@ -0,0 +1,31 @@
{
"name": ".opencode",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@opencode-ai/plugin": "*"
}
},
"node_modules/@opencode-ai/plugin": {
"version": "1.2.24",
"license": "MIT",
"dependencies": {
"@opencode-ai/sdk": "1.2.24",
"zod": "4.1.8"
}
},
"node_modules/@opencode-ai/sdk": {
"version": "1.2.24",
"license": "MIT"
},
"node_modules/zod": {
"version": "4.1.8",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View file

@ -11,6 +11,11 @@ export function preview(text: string) {
}
export namespace ShellRunner {
function wrap(name: string, command: string) {
if (name !== "powershell" && name !== "pwsh") return command
return `${command}; if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE }; if ($?) { exit 0 }; exit 1`
}
export async function shellEnv(ctx: Tool.Context, cwd: string) {
const extra = await Plugin.trigger("shell.env", { cwd, sessionID: ctx.sessionID, callID: ctx.callID }, { env: {} })
return {
@ -21,7 +26,7 @@ export namespace ShellRunner {
export function launch(shell: string, name: string, command: string, cwd: string, env: NodeJS.ProcessEnv) {
if (process.platform === "win32" && (name === "powershell" || name === "pwsh")) {
return spawn(shell, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", command], {
return spawn(shell, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", wrap(name, command)], {
cwd,
env,
stdio: ["ignore", "pipe", "pipe"],
@ -54,6 +59,7 @@ export namespace ShellRunner {
) {
const proc = launch(input.shell, input.name, input.command, input.cwd, input.env)
let output = ""
let code: number | null = null
ctx.metadata({
metadata: {
@ -103,12 +109,14 @@ export namespace ShellRunner {
ctx.abort.removeEventListener("abort", abort)
}
proc.once("exit", () => {
proc.once("exit", (next) => {
exited = true
code = next
})
proc.once("close", () => {
proc.once("close", (next) => {
exited = true
code = next
cleanup()
resolve()
})
@ -131,7 +139,7 @@ export namespace ShellRunner {
title: input.description,
metadata: {
output: preview(output),
exit: proc.exitCode,
exit: code,
description: input.description,
},
output,

View file

@ -269,6 +269,8 @@ export type ToolInfo = {
subtitle?: string
}
const SHELL = new Set(["bash", "pwsh", "powershell"])
function agentTitle(i18n: UiI18n, type?: string) {
if (!type) return i18n.t("ui.tool.agent.default")
return i18n.t("ui.tool.agent", { type })
@ -331,6 +333,8 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
}
}
case "bash":
case "pwsh":
case "powershell":
return {
icon: "console",
title: i18n.t("ui.tool.shell"),
@ -518,7 +522,7 @@ function renderable(part: PartType, showReasoningSummaries = true) {
}
function toolDefaultOpen(tool: string, shell = false, edit = false) {
if (tool === "bash") return shell
if (SHELL.has(tool)) return shell
if (tool === "edit" || tool === "write" || tool === "apply_patch") return edit
}

View file

@ -33,6 +33,7 @@ import type { Diagnostic } from "vscode-languageserver-types"
import styles from "./part.module.css"
const MIN_DURATION = 2000
const SHELL = new Set(["bash", "pwsh", "powershell"])
export interface PartProps {
index: number
@ -90,7 +91,7 @@ export function Part(props: PartProps) {
<Match when={props.part.type === "tool" && props.part.tool === "todowrite"}>
<IconQueueList width={18} height={18} />
</Match>
<Match when={props.part.type === "tool" && props.part.tool === "bash"}>
<Match when={props.part.type === "tool" && SHELL.has(props.part.tool)}>
<IconCommandLine width={18} height={18} />
</Match>
<Match when={props.part.type === "tool" && props.part.tool === "edit"}>
@ -240,7 +241,7 @@ export function Part(props: PartProps) {
state={props.part.state}
/>
</Match>
<Match when={props.part.tool === "bash"}>
<Match when={SHELL.has(props.part.tool)}>
<BashTool
id={props.part.id}
tool={props.part.tool}