Add TUI kanban board, review workflow, and new task statuses
Replace task statuses (pending/running/succeeded/failed/cancelled) with
a kanban workflow: todo → in_progress → in_review → done/wontdo.
When a non-review agent task completes, the API auto-creates a child
review task and moves the parent to in_review. Only humans can move
tasks from in_review to done/wontdo via the TUI.
New components:
- cmd/tui: bubbletea kanban board with $EDITOR integration
- POST /api/v1/tasks/{id}/complete: agent completion callback
- Operator --api-url flag for completion callbacks
- ProviderQueue sets tasks to in_progress on pickup
- AgentTask reconciler calls /complete on job finish
This commit is contained in:
@@ -2,6 +2,7 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -24,7 +25,7 @@ func (db *DB) CreateTask(ctx context.Context, req models.CreateTaskRequest) (*mo
|
||||
ExtraTools: req.ExtraTools,
|
||||
ParentTaskID: req.ParentTaskID,
|
||||
PoolRef: req.PoolRef,
|
||||
Status: models.StatusPending,
|
||||
Status: models.StatusTodo,
|
||||
}
|
||||
if task.ExtraTools == nil {
|
||||
task.ExtraTools = []string{}
|
||||
@@ -79,7 +80,7 @@ func (db *DB) GetTask(ctx context.Context, id string) (*models.Task, error) {
|
||||
}
|
||||
|
||||
func (db *DB) ListPendingTasks(ctx context.Context) ([]models.Task, error) {
|
||||
return db.listTasksByStatus(ctx, string(models.StatusPending))
|
||||
return db.listTasksByStatus(ctx, string(models.StatusTodo))
|
||||
}
|
||||
|
||||
func (db *DB) listTasksByStatus(ctx context.Context, status string) ([]models.Task, error) {
|
||||
@@ -130,13 +131,13 @@ func (db *DB) ListTasks(ctx context.Context, status string, repository string) (
|
||||
}
|
||||
|
||||
func (db *DB) UpdateTaskStatus(ctx context.Context, id string, req models.UpdateTaskRequest) error {
|
||||
if req.Status == models.StatusRunning {
|
||||
if req.Status == models.StatusInProgress {
|
||||
_, err := db.Pool.Exec(ctx, `
|
||||
UPDATE tasks SET status = $2, job_name = COALESCE(NULLIF($3, ''), job_name), started_at = NOW()
|
||||
WHERE id = $1`, id, req.Status, req.JobName)
|
||||
return err
|
||||
}
|
||||
if req.Status == models.StatusSucceeded || req.Status == models.StatusFailed {
|
||||
if req.Status == models.StatusDone || req.Status == models.StatusWontdo {
|
||||
_, err := db.Pool.Exec(ctx, `
|
||||
UPDATE tasks SET status = $2, result = COALESCE(NULLIF($3, ''), result),
|
||||
error_message = COALESCE(NULLIF($4, ''), error_message), completed_at = NOW()
|
||||
@@ -147,6 +148,70 @@ func (db *DB) UpdateTaskStatus(ctx context.Context, id string, req models.Update
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) CompleteTask(ctx context.Context, id string, req models.CompleteTaskRequest) (*models.Task, error) {
|
||||
task, err := db.GetTask(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.ErrorMessage != "" {
|
||||
_, err := db.Pool.Exec(ctx, `
|
||||
UPDATE tasks SET status = 'todo', error_message = $2, completed_at = NOW()
|
||||
WHERE id = $1`, id, req.ErrorMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task.Status = models.StatusTodo
|
||||
task.ErrorMessage = req.ErrorMessage
|
||||
return task, nil
|
||||
}
|
||||
|
||||
if req.Result != "" {
|
||||
_, err := db.Pool.Exec(ctx, `
|
||||
UPDATE tasks SET result = $2 WHERE id = $1`, id, req.Result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task.Result = req.Result
|
||||
}
|
||||
|
||||
if task.Command != "review" {
|
||||
_, err := db.Pool.Exec(ctx, `
|
||||
UPDATE tasks SET status = 'in_review', completed_at = NOW()
|
||||
WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task.Status = models.StatusInReview
|
||||
|
||||
reviewTask := models.CreateTaskRequest{
|
||||
Command: "review",
|
||||
Repository: task.Repository,
|
||||
Ref: task.Ref,
|
||||
IssueNumber: task.IssueNumber,
|
||||
PRNumber: task.PRNumber,
|
||||
Body: task.Body,
|
||||
Author: task.Author,
|
||||
ParentTaskID: task.ID,
|
||||
PoolRef: task.PoolRef,
|
||||
}
|
||||
if _, err := db.CreateTask(ctx, reviewTask); err != nil {
|
||||
return nil, fmt.Errorf("create review task: %w", err)
|
||||
}
|
||||
|
||||
return task, nil
|
||||
}
|
||||
|
||||
_, err = db.Pool.Exec(ctx, `
|
||||
UPDATE tasks SET status = 'in_review', completed_at = NOW()
|
||||
WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task.Status = models.StatusInReview
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func scanTasks(rows pgx.Rows) ([]models.Task, error) {
|
||||
var tasks []models.Task
|
||||
for rows.Next() {
|
||||
|
||||
Reference in New Issue
Block a user