- proc.go: ensureProcess() as temporary bridge until go-process gets v0.7.0 update - processIsRunning/processKill: use go-process ProcessID when available, fall back to PID - WorkspaceStatus: add ProcessID field for go-process managed lookup - dispatch.go: simplified spawnAgent goroutine — uses proc.Done() instead of syscall poll - Removed syscall import from dispatch.go Next: update go-process to v0.7.0 Core contract, then replace syscall.Kill calls in queue.go, shutdown.go, status.go, dispatch_sync.go with processIsRunning/processKill. Coverage: 78.1%, 802 tests Co-Authored-By: Virgil <virgil@lethean.io>
134 lines
3.7 KiB
Go
134 lines
3.7 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
// Process execution helpers — wraps go-process for testable command execution.
|
|
// All external command execution in the agentic package goes through these helpers.
|
|
//
|
|
// Requires go-process to be registered with Core via:
|
|
//
|
|
// core.New(core.WithService(agentic.ProcessRegister))
|
|
//
|
|
// If process service is not initialised (e.g. in tests), helpers will error.
|
|
|
|
package agentic
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"syscall"
|
|
|
|
core "dappco.re/go/core"
|
|
"dappco.re/go/core/process"
|
|
)
|
|
|
|
var procOnce sync.Once
|
|
|
|
// ensureProcess lazily initialises go-process default service for tests
|
|
// and standalone usage. In production, main.go registers ProcessRegister
|
|
// with Core which calls SetDefault properly.
|
|
func ensureProcess() {
|
|
procOnce.Do(func() {
|
|
if process.Default() != nil {
|
|
return
|
|
}
|
|
c := core.New()
|
|
svc, err := process.NewService(process.Options{})(c)
|
|
if err == nil {
|
|
if s, ok := svc.(*process.Service); ok {
|
|
_ = process.SetDefault(s)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// runCmd executes a command in a directory and returns (output, error).
|
|
// Uses go-process RunWithOptions — requires process service to be registered.
|
|
//
|
|
// out, err := runCmd(ctx, repoDir, "git", "log", "--oneline", "-20")
|
|
func runCmd(ctx context.Context, dir string, command string, args ...string) (string, error) {
|
|
ensureProcess()
|
|
return process.RunWithOptions(ctx, process.RunOptions{
|
|
Command: command,
|
|
Args: args,
|
|
Dir: dir,
|
|
})
|
|
}
|
|
|
|
// runCmdEnv executes a command with additional environment variables.
|
|
//
|
|
// out, err := runCmdEnv(ctx, repoDir, []string{"GOWORK=off"}, "go", "test", "./...")
|
|
func runCmdEnv(ctx context.Context, dir string, env []string, command string, args ...string) (string, error) {
|
|
ensureProcess()
|
|
return process.RunWithOptions(ctx, process.RunOptions{
|
|
Command: command,
|
|
Args: args,
|
|
Dir: dir,
|
|
Env: env,
|
|
})
|
|
}
|
|
|
|
// runCmdOK executes a command and returns true if it exits 0.
|
|
//
|
|
// if runCmdOK(ctx, repoDir, "go", "build", "./...") { ... }
|
|
func runCmdOK(ctx context.Context, dir string, command string, args ...string) bool {
|
|
_, err := runCmd(ctx, dir, command, args...)
|
|
return err == nil
|
|
}
|
|
|
|
// gitCmd runs a git command in the given directory.
|
|
//
|
|
// out, err := gitCmd(ctx, repoDir, "log", "--oneline", "-20")
|
|
func gitCmd(ctx context.Context, dir string, args ...string) (string, error) {
|
|
return runCmd(ctx, dir, "git", args...)
|
|
}
|
|
|
|
// gitCmdOK runs a git command and returns true if it exits 0.
|
|
//
|
|
// if gitCmdOK(ctx, repoDir, "fetch", "origin", "main") { ... }
|
|
func gitCmdOK(ctx context.Context, dir string, args ...string) bool {
|
|
return runCmdOK(ctx, dir, "git", args...)
|
|
}
|
|
|
|
// gitOutput runs a git command and returns trimmed stdout.
|
|
//
|
|
// branch := gitOutput(ctx, repoDir, "rev-parse", "--abbrev-ref", "HEAD")
|
|
func gitOutput(ctx context.Context, dir string, args ...string) string {
|
|
out, err := gitCmd(ctx, dir, args...)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return core.Trim(out)
|
|
}
|
|
|
|
// --- Process lifecycle helpers ---
|
|
|
|
// processIsRunning checks if a process is still alive.
|
|
// Uses go-process ProcessID if available, falls back to PID signal check.
|
|
//
|
|
// if processIsRunning(st.ProcessID, st.PID) { ... }
|
|
func processIsRunning(processID string, pid int) bool {
|
|
if processID != "" {
|
|
if proc, err := process.Get(processID); err == nil {
|
|
return proc.IsRunning()
|
|
}
|
|
}
|
|
if pid > 0 {
|
|
return syscall.Kill(pid, 0) == nil
|
|
}
|
|
return false
|
|
}
|
|
|
|
// processKill terminates a process.
|
|
// Uses go-process Kill if ProcessID available, falls back to SIGTERM.
|
|
//
|
|
// processKill(st.ProcessID, st.PID)
|
|
func processKill(processID string, pid int) bool {
|
|
if processID != "" {
|
|
if proc, err := process.Get(processID); err == nil {
|
|
return proc.Kill() == nil
|
|
}
|
|
}
|
|
if pid > 0 {
|
|
return syscall.Kill(pid, syscall.SIGTERM) == nil
|
|
}
|
|
return false
|
|
}
|