fix: hide backend subprocess windows on Windows (#2179)
This commit is contained in:
parent
a96143b502
commit
8bbc4c94fe
|
|
@ -4,10 +4,11 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||
)
|
||||
|
||||
// TokenSource yields a GitHub bearer token on demand. Production wires this
|
||||
|
|
@ -169,7 +170,7 @@ func (s *GHTokenSource) ttl() time.Duration {
|
|||
}
|
||||
|
||||
func ghAuthToken(ctx context.Context) (string, error) {
|
||||
out, err := exec.CommandContext(ctx, "gh", "auth", "token").Output()
|
||||
out, err := aoprocess.CommandContext(ctx, "gh", "auth", "token").Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
||||
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -752,7 +753,7 @@ func pathExistsNonEmpty(path string) (bool, error) {
|
|||
}
|
||||
|
||||
func runCommand(ctx context.Context, binary string, args ...string) ([]byte, error) {
|
||||
cmd := exec.CommandContext(ctx, binary, args...)
|
||||
cmd := aoprocess.CommandContext(ctx, binary, args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return out, commandError{args: append([]string{binary}, args...), output: string(out), err: err}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/daemon"
|
||||
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/processalive"
|
||||
)
|
||||
|
||||
|
|
@ -96,11 +97,11 @@ func DefaultDeps() Deps {
|
|||
}
|
||||
|
||||
func commandOutput(ctx context.Context, name string, args ...string) ([]byte, error) {
|
||||
return exec.CommandContext(ctx, name, args...).CombinedOutput()
|
||||
return aoprocess.CommandContext(ctx, name, args...).CombinedOutput()
|
||||
}
|
||||
|
||||
func commandOutputInDir(ctx context.Context, dir, name string, args ...string) ([]byte, error) {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
cmd := aoprocess.CommandContext(ctx, name, args...)
|
||||
cmd.Dir = dir
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||
)
|
||||
|
||||
// Store is the narrow slice of the rewrite's native storage layer the importer
|
||||
|
|
@ -235,7 +235,7 @@ func defaultRepoOriginURL(path string) string {
|
|||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
cmd := exec.Command("git", "-C", path, "remote", "get-url", "origin")
|
||||
cmd := aoprocess.Command("git", "-C", path, "remote", "get-url", "origin")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -20,6 +19,7 @@ import (
|
|||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/observe"
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
||||
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -1212,7 +1212,7 @@ func normalizePRState(draft, merged, closed bool) string {
|
|||
// The observer uses this to backfill projects that were registered before
|
||||
// project.Add resolved origin URLs at add time.
|
||||
func resolveGitOriginURL(path string) string {
|
||||
out, err := exec.Command("git", "-C", path, "remote", "get-url", "origin").Output()
|
||||
out, err := aoprocess.Command("git", "-C", path, "remote", "get-url", "origin").Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Command creates a non-interactive child process. On Windows it suppresses
|
||||
// transient console windows for CLI tools launched by the desktop daemon.
|
||||
func Command(name string, args ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, args...)
|
||||
configureHidden(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CommandContext is Command with cancellation support.
|
||||
func CommandContext(ctx context.Context, name string, args ...string) *exec.Cmd {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
configureHidden(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
//go:build !windows
|
||||
|
||||
package process
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func configureHidden(_ *exec.Cmd) {}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
//go:build windows
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func configureHidden(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
CreationFlags: windows.CREATE_NO_WINDOW,
|
||||
HideWindow: true,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
//go:build windows
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func TestCommandContextHidesConsoleWindow(t *testing.T) {
|
||||
cmd := CommandContext(context.Background(), "git", "--version")
|
||||
if cmd.SysProcAttr == nil {
|
||||
t.Fatal("SysProcAttr = nil, want hidden Windows process attributes")
|
||||
}
|
||||
if got := cmd.SysProcAttr.CreationFlags; got&windows.CREATE_NO_WINDOW == 0 {
|
||||
t.Fatalf("CreationFlags = %#x, want CREATE_NO_WINDOW", got)
|
||||
}
|
||||
if !cmd.SysProcAttr.HideWindow {
|
||||
t.Fatal("HideWindow = false, want true")
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package project
|
|||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
|
@ -14,6 +13,7 @@ import (
|
|||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apierr"
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
||||
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||
)
|
||||
|
||||
// Manager is the controller-facing contract for the /api/v1/projects surface.
|
||||
|
|
@ -294,7 +294,7 @@ func (m *Service) SetConfig(ctx context.Context, id domain.ProjectID, in SetConf
|
|||
// other git error returns an empty string — `project add` must not fail just
|
||||
// because no origin is configured (the SCM observer skips such projects).
|
||||
func resolveGitOriginURL(path string) string {
|
||||
out, err := exec.Command("git", "-C", path, "remote", "get-url", "origin").Output()
|
||||
out, err := aoprocess.Command("git", "-C", path, "remote", "get-url", "origin").Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -313,14 +313,14 @@ func resolveGitOriginURL(path string) string {
|
|||
// returns an empty string — `project add` must not fail just because the branch
|
||||
// can't be resolved (the caller falls back to DefaultBranchName).
|
||||
func resolveDefaultBranch(path string) string {
|
||||
if out, err := exec.Command(
|
||||
if out, err := aoprocess.Command(
|
||||
"git", "-C", path, "symbolic-ref", "--short", "refs/remotes/origin/HEAD",
|
||||
).Output(); err == nil {
|
||||
if ref := strings.TrimSpace(string(out)); ref != "" {
|
||||
return strings.TrimPrefix(ref, "origin/")
|
||||
}
|
||||
}
|
||||
out, err := exec.Command("git", "-C", path, "symbolic-ref", "--short", "HEAD").Output()
|
||||
out, err := aoprocess.Command("git", "-C", path, "symbolic-ref", "--short", "HEAD").Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -412,7 +412,7 @@ func normalizePath(raw string) (string, error) {
|
|||
}
|
||||
|
||||
func isGitRepo(path string) bool {
|
||||
cmd := exec.Command("git", "-C", path, "rev-parse", "--show-toplevel")
|
||||
cmd := aoprocess.Command("git", "-C", path, "rev-parse", "--show-toplevel")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
|
@ -14,6 +13,7 @@ import (
|
|||
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apierr"
|
||||
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||
)
|
||||
|
||||
var workspaceRootIgnoreDenylist = []string{
|
||||
|
|
@ -358,7 +358,7 @@ func workspaceReposFromRecords(records []domain.WorkspaceRepoRecord) []Workspace
|
|||
}
|
||||
|
||||
func gitOutput(ctx context.Context, dir string, args ...string) (string, error) {
|
||||
cmd := exec.CommandContext(ctx, "git", append([]string{"-C", dir}, args...)...)
|
||||
cmd := aoprocess.CommandContext(ctx, "git", append([]string{"-C", dir}, args...)...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("git -C %s %s: %w: %s", dir, strings.Join(args, " "), err, strings.TrimSpace(string(out)))
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
||||
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||
)
|
||||
|
||||
// Sentinel errors returned by the Session Manager; callers match them with
|
||||
|
|
@ -1176,9 +1177,9 @@ func runPostCreate(ctx context.Context, workspacePath string, commands []string)
|
|||
}
|
||||
var cmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.CommandContext(ctx, "cmd", "/c", command)
|
||||
cmd = aoprocess.CommandContext(ctx, "cmd", "/c", command)
|
||||
} else {
|
||||
cmd = exec.CommandContext(ctx, "sh", "-c", command)
|
||||
cmd = aoprocess.CommandContext(ctx, "sh", "-c", command)
|
||||
}
|
||||
cmd.Dir = workspacePath
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue