Files
zero/internal/tui/command_bash.go
anandh8x fcc6c72c34 fix(windows): prefer PowerShell for command execution
Detect PowerShell 7 or Windows PowerShell before falling back to cmd.exe, and share the selected host shell across agent tools and the interactive shell escape.

Configure UTF-8 output, preserve restricted-sandbox MSYS safeguards, and add Windows coverage for pipelines, workspace writes, and interactive stdin sessions.
2026-07-24 19:35:42 +05:30

65 lines
1.8 KiB
Go

package tui
import (
"context"
"strings"
"time"
tea "charm.land/bubbletea/v2"
"github.com/Gitlawb/zero/internal/tools"
)
// bashEscapeTimeout bounds a "!cmd" shell escape so a hung command can't freeze
// the agent indefinitely.
const bashEscapeTimeout = 30 * time.Second
// bashResultMsg carries the combined output of a "!cmd" shell escape back to the
// model for display in the transcript.
type bashResultMsg struct {
command string
output string
}
// runBashEscape runs a user-typed "!cmd" in the workspace and returns its
// combined output as a message. The user typed it explicitly, so it runs
// directly (a deliberate shell escape, outside the agent sandbox), bounded by a
// timeout and executed asynchronously so the UI stays responsive.
func runBashEscape(cwd, command string) tea.Cmd {
return func() tea.Msg {
ctx, cancel := context.WithTimeout(context.Background(), bashEscapeTimeout)
defer cancel()
cmd := tools.NewHostShellCommandContext(ctx, command)
if strings.TrimSpace(cwd) != "" {
cmd.Dir = cwd
}
out, err := cmd.CombinedOutput()
text := strings.TrimRight(string(out), "\n")
switch {
case ctx.Err() == context.DeadlineExceeded:
text = appendNote(text, "[timed out after "+bashEscapeTimeout.String()+"]")
case err != nil:
text = appendNote(text, "[exit error: "+err.Error()+"]")
}
if strings.TrimSpace(text) == "" {
text = "(no output)"
}
return bashResultMsg{command: command, output: text}
}
}
// escapeShell returns the platform shell and arguments for tests and display.
// Execution itself uses tools.NewHostShellCommandContext so the cmd.exe
// fallback receives its required raw Windows command line.
func escapeShell(command string) (string, []string) {
return tools.HostShellCommand(command)
}
func appendNote(text, note string) string {
if text == "" {
return note
}
return text + "\n" + note
}