From 4eb1111faadecac0e5e3c3ccab7d89a0b46090d7 Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 25 Mar 2026 10:10:17 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20clean=20up=20proc.go=20=E2=80=94=20?= =?UTF-8?q?ensureProcess=20bridge,=20processIsRunning/processKill=20helper?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- pkg/agentic/dispatch.go | 15 +--------- pkg/agentic/proc.go | 62 +++++++++++++++++++++++++++++++++++------ pkg/agentic/status.go | 7 +++-- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/pkg/agentic/dispatch.go b/pkg/agentic/dispatch.go index 3239bdc..38ae0ef 100644 --- a/pkg/agentic/dispatch.go +++ b/pkg/agentic/dispatch.go @@ -4,7 +4,6 @@ package agentic import ( "context" - "syscall" "time" "dappco.re/go/agent/pkg/messages" @@ -370,19 +369,7 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir string) (int, string, er s.startIssueTracking(wsDir) go func() { - ticker := time.NewTicker(5 * time.Second) - defer ticker.Stop() - for { - select { - case <-proc.Done(): - goto done - case <-ticker.C: - if err := syscall.Kill(pid, 0); err != nil { - goto done - } - } - } - done: + <-proc.Done() s.onAgentComplete(agent, wsDir, outputFile, proc.Info().ExitCode, string(proc.Info().Status), proc.Output()) }() diff --git a/pkg/agentic/proc.go b/pkg/agentic/proc.go index 7cd835c..03b75b4 100644 --- a/pkg/agentic/proc.go +++ b/pkg/agentic/proc.go @@ -2,12 +2,19 @@ // 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" @@ -15,23 +22,26 @@ import ( var procOnce sync.Once -// ensureProcess lazily initialises the default process service. +// 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 { - c := core.New() - svc, err := process.NewService(process.Options{})(c) - if err == nil { - if s, ok := svc.(*process.Service); ok { - process.SetDefault(s) - } + 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 for testability. +// 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) { @@ -88,3 +98,37 @@ func gitOutput(ctx context.Context, dir string, args ...string) string { } 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 +} diff --git a/pkg/agentic/status.go b/pkg/agentic/status.go index c2095e2..dbe518a 100644 --- a/pkg/agentic/status.go +++ b/pkg/agentic/status.go @@ -36,9 +36,10 @@ type WorkspaceStatus struct { Org string `json:"org,omitempty"` // forge org (e.g. "core") Task string `json:"task"` // task description Branch string `json:"branch,omitempty"` // git branch name - Issue int `json:"issue,omitempty"` // forge issue number - PID int `json:"pid,omitempty"` // process ID (if running) - StartedAt time.Time `json:"started_at"` // when dispatch started + Issue int `json:"issue,omitempty"` // forge issue number + PID int `json:"pid,omitempty"` // OS process ID (if running) + ProcessID string `json:"process_id,omitempty"` // go-process ID for managed lookup + StartedAt time.Time `json:"started_at"` // when dispatch started UpdatedAt time.Time `json:"updated_at"` // last status change Question string `json:"question,omitempty"` // from BLOCKED.md Runs int `json:"runs"` // how many times dispatched/resumed