fix: hide backend subprocess windows on Windows (#2179)
This commit is contained in:
parent
a96143b502
commit
8bbc4c94fe
|
|
@ -4,10 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TokenSource yields a GitHub bearer token on demand. Production wires this
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
||||||
|
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -752,7 +753,7 @@ func pathExistsNonEmpty(path string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCommand(ctx context.Context, binary string, args ...string) ([]byte, 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()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, commandError{args: append([]string{binary}, args...), output: string(out), err: err}
|
return out, commandError{args: append([]string{binary}, args...), output: string(out), err: err}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/daemon"
|
"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"
|
"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) {
|
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) {
|
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
|
cmd.Dir = dir
|
||||||
return cmd.CombinedOutput()
|
return cmd.CombinedOutput()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
"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
|
// 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 == "" {
|
if path == "" {
|
||||||
return ""
|
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()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -20,6 +19,7 @@ import (
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/observe"
|
"github.com/aoagents/agent-orchestrator/backend/internal/observe"
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
||||||
|
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -1212,7 +1212,7 @@ func normalizePRState(draft, merged, closed bool) string {
|
||||||
// The observer uses this to backfill projects that were registered before
|
// The observer uses this to backfill projects that were registered before
|
||||||
// project.Add resolved origin URLs at add time.
|
// project.Add resolved origin URLs at add time.
|
||||||
func resolveGitOriginURL(path string) string {
|
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 {
|
if err != nil {
|
||||||
return ""
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -14,6 +13,7 @@ import (
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apierr"
|
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apierr"
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
"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.
|
// 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
|
// other git error returns an empty string — `project add` must not fail just
|
||||||
// because no origin is configured (the SCM observer skips such projects).
|
// because no origin is configured (the SCM observer skips such projects).
|
||||||
func resolveGitOriginURL(path string) string {
|
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 {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -313,14 +313,14 @@ func resolveGitOriginURL(path string) string {
|
||||||
// returns an empty string — `project add` must not fail just because the branch
|
// returns an empty string — `project add` must not fail just because the branch
|
||||||
// can't be resolved (the caller falls back to DefaultBranchName).
|
// can't be resolved (the caller falls back to DefaultBranchName).
|
||||||
func resolveDefaultBranch(path string) string {
|
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",
|
"git", "-C", path, "symbolic-ref", "--short", "refs/remotes/origin/HEAD",
|
||||||
).Output(); err == nil {
|
).Output(); err == nil {
|
||||||
if ref := strings.TrimSpace(string(out)); ref != "" {
|
if ref := strings.TrimSpace(string(out)); ref != "" {
|
||||||
return strings.TrimPrefix(ref, "origin/")
|
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 {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -412,7 +412,7 @@ func normalizePath(raw string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isGitRepo(path string) bool {
|
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()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -14,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apierr"
|
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apierr"
|
||||||
|
aoprocess "github.com/aoagents/agent-orchestrator/backend/internal/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
var workspaceRootIgnoreDenylist = []string{
|
var workspaceRootIgnoreDenylist = []string{
|
||||||
|
|
@ -358,7 +358,7 @@ func workspaceReposFromRecords(records []domain.WorkspaceRepoRecord) []Workspace
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitOutput(ctx context.Context, dir string, args ...string) (string, error) {
|
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()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("git -C %s %s: %w: %s", dir, strings.Join(args, " "), err, strings.TrimSpace(string(out)))
|
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/domain"
|
||||||
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
"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
|
// 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
|
var cmd *exec.Cmd
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
cmd = exec.CommandContext(ctx, "cmd", "/c", command)
|
cmd = aoprocess.CommandContext(ctx, "cmd", "/c", command)
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.CommandContext(ctx, "sh", "-c", command)
|
cmd = aoprocess.CommandContext(ctx, "sh", "-c", command)
|
||||||
}
|
}
|
||||||
cmd.Dir = workspacePath
|
cmd.Dir = workspacePath
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue