agent/pkg/agentic/proc.go
Snider 4eb1111faa refactor: clean up proc.go — ensureProcess bridge, processIsRunning/processKill helpers
- 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>
2026-03-25 10:10:17 +00:00

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
}