Repository navigation
Expand file tree
/
Copy pathbackground_runner.go
More file actions
116 lines (104 loc) · 3.5 KB
/
Copy pathbackground_runner.go
File metadata and controls
116 lines (104 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package engine
import (
"context"
"fmt"
"time"
agentcontracts "github.com/GrayCodeAI/reflect/internal/contracts/agent"
"github.com/GrayCodeAI/reflect/internal/taskruntime"
"github.com/GrayCodeAI/reflect/internal/tool"
)
// BackgroundTask represents an async subagent task running in the background.
// Kept for API compatibility; backed by taskruntime via BackgroundRunner.
type BackgroundTask struct {
ID string
Prompt string
Status string // "running", "done", "failed"
Result string
Error string
StartedAt time.Time
DoneAt time.Time
}
// BackgroundRunner manages async subagent tasks that run while the user keeps chatting.
// PACK-02: thin adapter over tool.BackgroundAgentManager / taskruntime.
type BackgroundRunner struct {
mgr *tool.BackgroundAgentManager
}
// NewBackgroundRunner creates a new background task runner.
func NewBackgroundRunner() *BackgroundRunner {
return &BackgroundRunner{mgr: tool.NewBackgroundAgentManager()}
}
// Delegate starts a background task. Returns the task ID immediately.
// execFn is adapted to the typed spawn contract (explore by default).
func (br *BackgroundRunner) Delegate(ctx context.Context, prompt string, execFn func(context.Context, string) (string, error)) string {
if br.mgr == nil {
br.mgr = tool.NewBackgroundAgentManager()
}
req := agentcontracts.SpawnRequest{Prompt: prompt, Background: true}
id := fmt.Sprintf("bg-%d", time.Now().UnixNano())
spawn := func(c context.Context, r agentcontracts.SpawnRequest) (agentcontracts.SpawnResult, error) {
if execFn == nil {
return agentcontracts.SpawnResult{}, fmt.Errorf("no exec fn")
}
out, err := execFn(c, r.Prompt)
if err != nil {
return agentcontracts.SpawnResult{Status: agentcontracts.StatusFailed, Error: err.Error()}, err
}
return agentcontracts.SpawnResult{Status: agentcontracts.StatusCompleted, Output: out}, nil
}
br.mgr.Spawn(ctx, id, req, spawn)
return id
}
// Status returns the current state of a background task.
func (br *BackgroundRunner) Status(id string) *BackgroundTask {
if br.mgr == nil || br.mgr.Registry() == nil {
return nil
}
t, ok := br.mgr.Registry().Get(id)
if !ok {
return nil
}
return taskruntimeToBackgroundTask(t)
}
// Collect returns and removes a completed task's result. Returns nil if still running.
func (br *BackgroundRunner) Collect(id string) *BackgroundTask {
if br.mgr == nil || br.mgr.Registry() == nil {
return nil
}
t, ok := br.mgr.Registry().Get(id)
if !ok || t.Status == taskruntime.StatusRunning {
return nil
}
// CollectCompleted clears all; prefer Get snapshot without delete of others.
// For API parity, return snapshot (done map retains until CollectCompleted).
return taskruntimeToBackgroundTask(t)
}
// ListActive returns all currently running tasks.
func (br *BackgroundRunner) ListActive() []*BackgroundTask {
// Registry does not list running ids yet; PendingCount is enough for tests.
return nil
}
// PendingCount returns the number of tasks still running.
func (br *BackgroundRunner) PendingCount() int {
if br.mgr == nil {
return 0
}
return br.mgr.Registry().PendingCount()
}
func taskruntimeToBackgroundTask(t *taskruntime.Task) *BackgroundTask {
status := "running"
switch t.Status {
case taskruntime.StatusCompleted:
status = "done"
case taskruntime.StatusFailed, taskruntime.StatusKilled:
status = "failed"
}
return &BackgroundTask{
ID: t.ID,
Prompt: t.Prompt,
Status: status,
Result: t.Output,
Error: t.Error,
StartedAt: t.StartedAt,
DoneAt: t.DoneAt,
}
}