feat(agentic): schedule pr management loop

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 03:46:09 +00:00
parent 5675d4cda6
commit f71066197d
3 changed files with 53 additions and 0 deletions

View file

@ -290,6 +290,9 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
if planRetentionDays(core.NewOptions()) > 0 {
go s.runPlanCleanupLoop(ctx, planRetentionScheduleInterval)
}
if s.forgeToken != "" {
go s.runPRManageLoop(ctx, prManageScheduleInterval)
}
c.RegisterQuery(s.handleWorkspaceQuery)

View file

@ -45,6 +45,8 @@ type RateLimitInfo struct {
var retryAfterPattern = compileRetryAfterPattern()
const prManageScheduleInterval = 5 * time.Minute
func compileRetryAfterPattern() *regexp.Regexp {
pattern, err := regexp.Compile(`(\d+)\s*minutes?\s*(?:and\s*)?(\d+)?\s*seconds?`)
if err != nil {
@ -114,6 +116,28 @@ func (s *PrepSubsystem) cmdPRManage(options core.Options) core.Result {
return s.cmdReviewQueue(options)
}
// ctx, cancel := context.WithCancel(context.Background())
// go s.runPRManageLoop(ctx, 5*time.Minute)
func (s *PrepSubsystem) runPRManageLoop(ctx context.Context, interval time.Duration) {
if ctx == nil || interval <= 0 {
return
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if result := s.cmdPRManage(core.NewOptions()); !result.OK {
core.Warn("pr-manage scheduled run failed", "error", result.Value)
}
}
}
}
func (s *PrepSubsystem) reviewQueue(ctx context.Context, _ *mcp.CallToolRequest, input ReviewQueueInput) (*mcp.CallToolResult, ReviewQueueOutput, error) {
limit := input.Limit
if limit <= 0 {

View file

@ -87,6 +87,32 @@ func TestReviewqueue_StoreReviewOutput_Good(t *testing.T) {
})
}
func TestReviewqueue_RunPRManageLoop_Good_StopsOnCancel(t *testing.T) {
s := &PrepSubsystem{
ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}),
backoff: make(map[string]time.Time),
failCount: make(map[string]int),
}
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
go func() {
s.runPRManageLoop(ctx, time.Hour)
close(done)
}()
cancel()
require.Eventually(t, func() bool {
select {
case <-done:
return true
default:
return false
}
}, time.Second, 5*time.Millisecond)
}
// --- reviewQueue ---
func TestReviewqueue_NoCandidates_Good(t *testing.T) {