agent/pkg/agentic/dispatch_sync.go

98 lines
2.3 KiB
Go
Raw Normal View History

// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"context"
"syscall"
"time"
core "dappco.re/go/core"
)
// DispatchSyncInput is the input for a synchronous (blocking) task run.
//
// input := agentic.DispatchSyncInput{Repo: "go-crypt", Agent: "codex:gpt-5.3-codex-spark", Task: "fix it", Issue: 7}
type DispatchSyncInput struct {
Org string
Repo string
Agent string
Task string
Issue int
}
// DispatchSyncResult is the output of a synchronous task run.
//
// if result.OK { fmt.Println("done:", result.Status) }
type DispatchSyncResult struct {
OK bool
Status string
Error string
PRURL string
}
// DispatchSync preps a workspace, spawns the agent directly (no queue, no concurrency check),
// and blocks until the agent completes.
//
// result := prep.DispatchSync(ctx, input)
func (s *PrepSubsystem) DispatchSync(ctx context.Context, input DispatchSyncInput) DispatchSyncResult {
// Prep workspace
prepInput := PrepInput{
Org: input.Org,
Repo: input.Repo,
Task: input.Task,
Agent: input.Agent,
Issue: input.Issue,
}
prepCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
_, prepOut, err := s.prepWorkspace(prepCtx, nil, prepInput)
if err != nil {
return DispatchSyncResult{Error: err.Error()}
}
if !prepOut.Success {
return DispatchSyncResult{Error: "prep failed"}
}
wsDir := prepOut.WorkspaceDir
prompt := prepOut.Prompt
core.Print(nil, " workspace: %s", wsDir)
core.Print(nil, " branch: %s", prepOut.Branch)
// Spawn agent directly — no queue, no concurrency check
pid, _, err := s.spawnAgent(input.Agent, prompt, wsDir)
if err != nil {
return DispatchSyncResult{Error: err.Error()}
}
core.Print(nil, " pid: %d", pid)
core.Print(nil, " waiting for completion...")
// Poll for process exit
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return DispatchSyncResult{Error: "cancelled"}
case <-ticker.C:
if pid > 0 && syscall.Kill(pid, 0) != nil {
// Process exited — read final status
st, err := readStatus(wsDir)
if err != nil {
return DispatchSyncResult{Error: "can't read final status"}
}
return DispatchSyncResult{
OK: st.Status == "completed",
Status: st.Status,
PRURL: st.PRURL,
}
}
}
}
}