2026-10-21 11:53:37 -07:00
|
|
|
package store
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
2026-10-21 15:15:20 -07:00
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"g.fc.run/ai/byre/internal/api"
|
2026-10-21 11:53:37 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Work struct {
|
|
|
|
|
AbandonedMerges []string
|
|
|
|
|
StartMerges []ClaimedMerge
|
|
|
|
|
FinalizeMerges []FinalizingMerge
|
|
|
|
|
StartDiscards []string
|
|
|
|
|
FinishDiscards []string
|
2026-10-21 12:54:34 -07:00
|
|
|
EndReviewers []EndedReviewer
|
|
|
|
|
StartReviews []SnapshotRef
|
|
|
|
|
StartStages []StageStart
|
|
|
|
|
SettleReviews []SnapshotRef
|
|
|
|
|
ReadyPrompts []ReadyPrompt
|
2026-10-22 16:46:57 -07:00
|
|
|
SelfCloses []SelfClose
|
2026-10-21 13:11:03 -07:00
|
|
|
AdvanceStages []string
|
2026-10-21 13:59:37 -07:00
|
|
|
LeadDeliveries []LeadDelivery
|
|
|
|
|
QueuedDelivery []QueuedDelivery
|
2026-10-21 15:15:20 -07:00
|
|
|
DueSchedules []api.Schedule
|
|
|
|
|
NextSchedule *time.Time
|
2026-10-21 11:53:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Store) Coordinate(ctx context.Context) (Work, error) {
|
|
|
|
|
work := Work{}
|
|
|
|
|
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
|
|
|
|
var err error
|
|
|
|
|
work.AbandonedMerges, err = abandonMerges(ctx, tx, emit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = claimHeldMerges(ctx, tx, emit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.StartMerges, err = startableMerges(ctx, tx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.FinalizeMerges, err = finalizingMerges(ctx, tx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
_, err = tx.ExecContext(ctx, `UPDATE tasks SET discard_state = 'closing'
|
|
|
|
|
WHERE status = 'active' AND discard_state = 'running'
|
|
|
|
|
AND NOT EXISTS (SELECT 1 FROM agent_sessions s WHERE s.id = tasks.discard_session AND s.state = 'running')`)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.StartDiscards, err = queryIDs(ctx, tx, `SELECT id FROM tasks WHERE status = 'active' AND discard_state = 'held' AND NOT `+implementerRunning)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.FinishDiscards, err = queryIDs(ctx, tx, `SELECT id FROM tasks WHERE status = 'active' AND discard_state = 'closing'`)
|
2026-10-21 12:54:34 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.EndReviewers, err = endReviews(ctx, tx, emit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.StartReviews, err = startableReviews(ctx, tx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.StartStages, err = pendingStages(ctx, tx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.SettleReviews, err = settleReviews(ctx, tx, work.StartStages)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.ReadyPrompts, err = readyPrompts(ctx, tx, emit)
|
2026-10-21 13:11:03 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-10-22 16:46:57 -07:00
|
|
|
work.SelfCloses, err = selfCloses(ctx, tx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-10-21 13:11:03 -07:00
|
|
|
work.AdvanceStages, err = finishedStages(ctx, tx)
|
2026-10-21 13:59:37 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.LeadDeliveries, err = leadDeliveries(ctx, tx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = endLandings(ctx, tx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.QueuedDelivery, err = queuedDeliveries(ctx, tx)
|
2026-10-21 15:15:20 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
work.DueSchedules, work.NextSchedule, err = dueSchedules(ctx, tx)
|
2026-10-21 11:53:37 -07:00
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
return work, err
|
|
|
|
|
}
|
2026-10-22 16:46:57 -07:00
|
|
|
|
|
|
|
|
type SelfClose struct {
|
|
|
|
|
TaskID string
|
|
|
|
|
Ready string
|
|
|
|
|
By string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func selfCloses(ctx context.Context, tx *sql.Tx) ([]SelfClose, error) {
|
|
|
|
|
rows, err := tx.QueryContext(ctx, `
|
|
|
|
|
SELECT tasks.id, tasks.ready, r.created_by
|
|
|
|
|
FROM tasks JOIN tasks r ON r.id = tasks.parent_task_id JOIN workflow_definitions d ON d.id = r.workflow_definition_id
|
|
|
|
|
WHERE tasks.status = 'active' AND tasks.workflow_stage != '' AND tasks.discard_state = ''
|
|
|
|
|
AND tasks.merging_by = '' AND tasks.finalizing = '' AND tasks.merge_queued_by = '' AND tasks.parent_merge_by = '' AND tasks.landing_session = ''
|
|
|
|
|
AND ((tasks.ready IN ('complete', 'continue') AND d.auto_merge) OR (tasks.ready = 'discard' AND d.auto_discard))
|
|
|
|
|
AND NOT `+implementerRunning+`
|
|
|
|
|
ORDER BY tasks.created_at`)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer rows.Close()
|
|
|
|
|
closes := []SelfClose{}
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
c := SelfClose{}
|
|
|
|
|
err = rows.Scan(&c.TaskID, &c.Ready, &c.By)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
closes = append(closes, c)
|
|
|
|
|
}
|
|
|
|
|
return closes, rows.Err()
|
|
|
|
|
}
|