Give each workflow run a parent task with no agent
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"g.fc.run/ai/byre/internal/api"
|
||||
"g.fc.run/ai/byre/internal/git"
|
||||
@@ -68,24 +69,31 @@ func (m *Manager) TaskWorkflow(ctx context.Context, taskID string) (api.TaskWork
|
||||
}
|
||||
|
||||
func (m *Manager) WorkflowRun(ctx context.Context, taskID string) (api.WorkflowRun, error) {
|
||||
frozen, err := m.store.TaskWorkflow(ctx, taskID)
|
||||
if err != nil {
|
||||
return api.WorkflowRun{}, err
|
||||
}
|
||||
task, err := m.store.GetTask(ctx, taskID)
|
||||
if err != nil {
|
||||
return api.WorkflowRun{}, err
|
||||
}
|
||||
tasks, err := m.store.RunTasks(ctx, *task.WorkflowRun)
|
||||
if task.Workflow == nil {
|
||||
return api.WorkflowRun{}, fmt.Errorf("%w: task %s isn't a workflow run", store.ErrNotFound, taskID)
|
||||
}
|
||||
frozen, err := m.store.TaskWorkflow(ctx, taskID)
|
||||
if err != nil {
|
||||
return api.WorkflowRun{}, err
|
||||
}
|
||||
events, err := m.store.RunEvents(ctx, *task.WorkflowRun)
|
||||
tasks, err := m.store.RunTasks(ctx, taskID)
|
||||
if err != nil {
|
||||
return api.WorkflowRun{}, err
|
||||
}
|
||||
events, err := m.store.RunEvents(ctx, taskID)
|
||||
if err != nil {
|
||||
return api.WorkflowRun{}, err
|
||||
}
|
||||
finished, err := m.store.WorkflowFinished(ctx, taskID)
|
||||
if err != nil {
|
||||
return api.WorkflowRun{}, err
|
||||
}
|
||||
|
||||
run := api.WorkflowRun{Name: frozen.Name, Run: *task.WorkflowRun, Title: frozen.Title, Brief: frozen.Brief, Definition: frozen.Definition, Stages: []api.WorkflowRunStage{}}
|
||||
run := api.WorkflowRun{Name: frozen.Name, Title: frozen.Title, Brief: frozen.Brief, Definition: frozen.Definition, Finished: finished, Stages: []api.WorkflowRunStage{}}
|
||||
after := map[string][]string{}
|
||||
discardedBy := map[string]string{}
|
||||
for _, ev := range events {
|
||||
@@ -100,8 +108,6 @@ func (m *Manager) WorkflowRun(ctx context.Context, taskID string) (api.WorkflowR
|
||||
after[*ev.TaskId] = ids
|
||||
case "task.discarded":
|
||||
discardedBy[*ev.TaskId] = ev.Actor
|
||||
case "task.workflow_finished":
|
||||
run.Finished = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,33 +238,60 @@ func (m *Manager) StartWorkflow(ctx context.Context, project api.Project, creato
|
||||
frozen.Title = title
|
||||
frozen.Audit = audit
|
||||
|
||||
run := ids.New(ids.WorkflowRun)
|
||||
tasks := []api.Task{}
|
||||
run, err := m.createRun(ctx, project, creator, frozen, todoItem, actor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tasks := []api.Task{run}
|
||||
for _, stage := range workflow.Starts(frozen.Definition) {
|
||||
task, err := m.startStage(ctx, project, creator, frozen, run, stage, base, todoItem, nil, actor)
|
||||
task, err := m.startStage(ctx, project, creator, frozen, run, stage, base, nil, actor)
|
||||
if task.Id != "" {
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
if err != nil {
|
||||
return tasks, fmt.Errorf("workflow %q: stage %q did not start: %w", name, stage, err)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if len(tasks) == 1 {
|
||||
_, derr := m.store.DiscardTask(ctx, run.Id, actor)
|
||||
if derr != nil {
|
||||
slog.Error("discard workflow run with no stage tasks", "task", run.Id, "err", derr)
|
||||
}
|
||||
}
|
||||
return tasks, fmt.Errorf("workflow %q: stage %q did not start: %w", name, stage, err)
|
||||
}
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
func (m *Manager) startStage(ctx context.Context, project api.Project, creator api.User, frozen store.FrozenWorkflow, run, stage, base string, todoItem *string, parents []parentStage, actor string) (api.Task, error) {
|
||||
func (m *Manager) createRun(ctx context.Context, project api.Project, creator api.User, frozen store.FrozenWorkflow, todoItem *string, actor string) (api.Task, error) {
|
||||
t := api.Task{
|
||||
Title: fmt.Sprintf("%s: %s", frozen.Title, stage),
|
||||
MergeTitle: frozen.Title,
|
||||
CreatedBy: creator.Login,
|
||||
TodoItem: todoItem,
|
||||
WorkflowRun: &run,
|
||||
WorkflowStage: &stage,
|
||||
Id: ids.New(ids.Task),
|
||||
ProjectId: project.Id,
|
||||
Title: frozen.Title,
|
||||
MergeTitle: frozen.Title,
|
||||
Status: api.TaskStatusActive,
|
||||
CreatedBy: creator.Login,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
TodoItem: todoItem,
|
||||
}
|
||||
if frozen.Audit != "" {
|
||||
t.Audit = &frozen.Audit
|
||||
}
|
||||
task, err := m.createTask(ctx, project, t, &frozen, base, actor)
|
||||
err := m.store.CreateTask(ctx, t, &frozen, actor)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
return m.store.GetTask(ctx, t.Id)
|
||||
}
|
||||
|
||||
func (m *Manager) startStage(ctx context.Context, project api.Project, creator api.User, frozen store.FrozenWorkflow, run api.Task, stage, base string, parents []parentStage, actor string) (api.Task, error) {
|
||||
t := api.Task{
|
||||
Title: fmt.Sprintf("%s: %s", frozen.Title, stage),
|
||||
MergeTitle: frozen.Title,
|
||||
CreatedBy: creator.Login,
|
||||
WorkflowStage: &stage,
|
||||
ParentTaskId: &run.Id,
|
||||
}
|
||||
task, err := m.createTask(ctx, project, t, base, actor)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
@@ -280,7 +313,7 @@ func (m *Manager) beginStage(ctx context.Context, task api.Task, project api.Pro
|
||||
ProjectId: &task.ProjectId,
|
||||
TaskId: &task.Id,
|
||||
Actor: actor,
|
||||
Payload: map[string]any{"id": task.Id, "workflow": frozen.Name, "run": *task.WorkflowRun, "stage": stage, "after": after},
|
||||
Payload: map[string]any{"id": task.Id, "workflow": frozen.Name, "run": *task.ParentTaskId, "stage": stage, "after": after},
|
||||
})
|
||||
if err != nil {
|
||||
return api.AgentSession{}, err
|
||||
@@ -401,16 +434,20 @@ func (m *Manager) nextStage(ctx context.Context, sessionID string) ([]string, er
|
||||
m.workflowMu.Lock()
|
||||
defer m.workflowMu.Unlock()
|
||||
|
||||
run, err := m.store.GetTask(ctx, *task.ParentTaskId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
next := workflow.Next(frozen.Definition, *task.WorkflowStage)
|
||||
if len(next) == 0 {
|
||||
finished, err := m.store.WorkflowFinished(ctx, *task.WorkflowRun)
|
||||
finished, err := m.store.WorkflowFinished(ctx, run.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if finished {
|
||||
return nil, m.store.ClearStage(ctx, sess.Id)
|
||||
}
|
||||
tasks, err := m.store.RunTasks(ctx, *task.WorkflowRun)
|
||||
tasks, err := m.store.RunTasks(ctx, run.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -436,12 +473,12 @@ func (m *Manager) nextStage(ctx context.Context, sessionID string) ([]string, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, m.store.FinishWorkflow(ctx, task, sess.Id, frozen.Name, output, kept)
|
||||
return nil, m.store.FinishWorkflow(ctx, run, sess.Id, frozen.Name, output, kept)
|
||||
}
|
||||
|
||||
started := []string{}
|
||||
for _, n := range next {
|
||||
ids, err := m.advanceTo(ctx, task, frozen, n)
|
||||
ids, err := m.advanceTo(ctx, run, frozen, n)
|
||||
if err != nil {
|
||||
return started, err
|
||||
}
|
||||
@@ -450,8 +487,8 @@ func (m *Manager) nextStage(ctx context.Context, sessionID string) ([]string, er
|
||||
return started, m.store.ClearStage(ctx, sess.Id)
|
||||
}
|
||||
|
||||
func (m *Manager) advanceTo(ctx context.Context, from api.Task, frozen store.FrozenWorkflow, stage string) ([]string, error) {
|
||||
tasks, err := m.store.RunTasks(ctx, *from.WorkflowRun)
|
||||
func (m *Manager) advanceTo(ctx context.Context, run api.Task, frozen store.FrozenWorkflow, stage string) ([]string, error) {
|
||||
tasks, err := m.store.RunTasks(ctx, run.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -463,11 +500,11 @@ func (m *Manager) advanceTo(ctx context.Context, from api.Task, frozen store.Fro
|
||||
}
|
||||
parents = append(parents, p)
|
||||
}
|
||||
project, err := m.store.GetProject(ctx, from.ProjectId)
|
||||
project, err := m.store.GetProject(ctx, run.ProjectId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creator, err := m.store.GetUser(ctx, from.CreatedBy)
|
||||
creator, err := m.store.GetUser(ctx, run.CreatedBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -488,7 +525,7 @@ func (m *Manager) advanceTo(ctx context.Context, from api.Task, frozen store.Fro
|
||||
default:
|
||||
base = project.DefaultBranch
|
||||
}
|
||||
task, err := m.startStage(ctx, project, creator, frozen, *from.WorkflowRun, stage, base, from.TodoItem, parents, "byre")
|
||||
task, err := m.startStage(ctx, project, creator, frozen, run, stage, base, parents, "byre")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -604,21 +641,25 @@ func (m *Manager) discardStages(ctx context.Context, tasks []api.Task, keep map[
|
||||
}
|
||||
|
||||
func (m *Manager) workflowStatus(ctx context.Context, task api.Task) (string, error) {
|
||||
finished, err := m.store.WorkflowFinished(ctx, *task.WorkflowRun)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if task.Workflow != nil {
|
||||
finished, err := m.store.WorkflowFinished(ctx, task.Id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
status := fmt.Sprintf("run of workflow %q, with no agent, worktree or branch; its stage tasks are its subtasks", *task.Workflow)
|
||||
if finished {
|
||||
status += "; workflow finished"
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
output, _, err := m.store.StageResult(ctx, task.Id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
status := fmt.Sprintf("stage %q", *task.WorkflowStage)
|
||||
status := fmt.Sprintf("stage %q of workflow run %s", *task.WorkflowStage, *task.ParentTaskId)
|
||||
if output != "" {
|
||||
status += " (finished)"
|
||||
}
|
||||
if finished {
|
||||
status += ", workflow finished"
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
@@ -673,6 +714,10 @@ func (m *Manager) finishStageTool(l *live) mcp.Tool {
|
||||
func describeTasks(tasks []api.Task) string {
|
||||
parts := []string{}
|
||||
for _, t := range tasks {
|
||||
if t.Workflow != nil {
|
||||
parts = append(parts, fmt.Sprintf("run task %s %q", t.Id, t.Title))
|
||||
continue
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("task %s %q from %s", t.Id, t.Title, t.BaseRef))
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
|
||||
Reference in New Issue
Block a user