2026-10-14 19:35:34 -07:00
package agents
import (
"context"
"errors"
"fmt"
2026-10-16 10:36:38 -07:00
"log/slog"
2026-10-14 19:35:34 -07:00
"path/filepath"
"time"
2026-10-18 15:32:07 -07:00
"g.fc.run/ai/byre/internal/api"
"g.fc.run/ai/byre/internal/git"
"g.fc.run/ai/byre/internal/store"
"g.fc.run/ai/byre/internal/todo"
2026-10-14 19:35:34 -07:00
)
2026-10-15 22:53:54 -07:00
func ( m * Manager ) CreateTask ( ctx context . Context , project api . Project , title , createdBy , base , actor string ) ( api . Task , error ) {
return m . createTask ( ctx , project , api . Task { Title : title , CreatedBy : createdBy }, nil , base , actor )
2026-10-15 07:23:17 -07:00
}
2026-10-17 08:10:55 -07:00
func ( m * Manager ) StartTask ( ctx context . Context , project api . Project , creator api . User , title , prompt , model string ) ( api . Task , error ) {
if creator . GitName == "" || creator . GitEmail == "" {
return api . Task {}, ErrNoGitIdentity
}
task , err := m . CreateTask ( ctx , project , title , creator . Login , project . DefaultBranch , creator . Login )
if err != nil {
return api . Task {}, err
}
2026-10-18 08:48:57 -07:00
_ , err = m . create ( ctx , task , creator , "" , prompt , model , creator . Login , nil )
2026-10-17 08:10:55 -07:00
if err != nil {
return api . Task {}, fmt . Errorf ( "task %s created, but its agent did not start: %w" , task . Id , err )
}
return task , nil
}
2026-10-16 08:53:02 -07:00
func ( m * Manager ) StartTodo ( ctx context . Context , project api . Project , creator api . User , title , model , actor , workflowName string , discuss bool ) ([] api . Task , error ) {
2026-10-15 07:23:17 -07:00
if creator . GitName == "" || creator . GitEmail == "" {
2026-10-15 22:53:54 -07:00
return nil , ErrNoGitIdentity
2026-10-15 07:23:17 -07:00
}
src , found , err := git . ReadFile ( project . RepoPath , project . DefaultBranch , todo . File )
if err != nil {
2026-10-15 22:53:54 -07:00
return nil , err
2026-10-15 07:23:17 -07:00
}
if ! found {
2026-10-15 22:53:54 -07:00
return nil , fmt . Errorf ( "%w: %s has no %s" , store . ErrNotFound , project . DefaultBranch , todo . File )
2026-10-15 07:23:17 -07:00
}
item , found := todo . Find ( todo . Parse ( src ), title )
if ! found {
2026-10-15 22:53:54 -07:00
return nil , fmt . Errorf ( "%w: %s on %s has no item %q" , store . ErrNotFound , todo . File , project . DefaultBranch , title )
2026-10-15 07:23:17 -07:00
}
tasks , err := m . store . ListTasks ( ctx , project . Id )
if err != nil {
2026-10-15 22:53:54 -07:00
return nil , err
2026-10-15 07:23:17 -07:00
}
for _ , t := range tasks {
if t . Status == api . TaskStatusActive && t . TodoItem != nil && * t . TodoItem == title {
2026-10-15 22:53:54 -07:00
return nil , fmt . Errorf ( "%w: task %s is already working on %q" , ErrConflict , t . Id , title )
2026-10-15 07:23:17 -07:00
}
}
2026-10-15 22:53:54 -07:00
started := [] api . Task {}
if workflowName != "" {
2026-10-16 08:53:02 -07:00
started , err = m . StartWorkflow ( ctx , project , creator , item . Title , project . DefaultBranch , actor , workflowName , todoPrompt ( item , discuss ), & item . Title )
2026-10-15 22:53:54 -07:00
if err != nil {
return nil , err
}
2026-10-15 22:11:25 -07:00
} else {
2026-10-15 22:53:54 -07:00
task , err := m . createTask ( ctx , project , api . Task { Title : item . Title , CreatedBy : creator . Login , TodoItem : & item . Title }, nil , project . DefaultBranch , actor )
if err != nil {
return nil , err
}
2026-10-18 08:48:57 -07:00
_ , err = m . create ( ctx , task , creator , "" , todoPrompt ( item , discuss ), model , actor , nil )
2026-10-15 22:11:25 -07:00
if err != nil {
2026-10-15 22:53:54 -07:00
return nil , fmt . Errorf ( "task %s created, but its agent did not start: %w" , task . Id , err )
2026-10-15 22:11:25 -07:00
}
2026-10-15 22:53:54 -07:00
started = append ( started , task )
2026-10-15 07:23:17 -07:00
}
2026-10-15 19:40:44 -07:00
if actor == creator . Login {
m . setStartedTodo ( creator . Login , true )
}
2026-10-15 22:53:54 -07:00
return started , nil
2026-10-15 07:23:17 -07:00
}
2026-10-16 08:53:02 -07:00
func todoPrompt ( item api . TodoItem , discuss bool ) string {
p := fmt . Sprintf ( "Work on this item from %[1]s at the repo root:\n\n%[2]s\n\nWhen the work is done, remove this item from %[1]s in this worktree, so the removal lands in the same merge as the work." ,
2026-10-15 07:23:17 -07:00
todo . File , todo . Markdown ( item ))
2026-10-16 08:53:02 -07:00
if ! discuss {
return p
}
2026-10-18 11:59:22 -07:00
return p + "\n\n" + discussPrompt
2026-10-15 07:23:17 -07:00
}
2026-10-19 08:40:05 -07:00
const discussPrompt = "The human wants to discuss this item with you before any of it is built, to judge whether it should be built at all and in what shape. Zoom out before going into detail. Read what you need to understand it, then give your view: what problem it solves and whether that is worth solving, whether we should do it, what the alternatives are (another approach, a smaller change, or nothing), and how you would approach it, with the trade-offs and doubts you see. Talk it over in your final messages, where the human replies in the chat, and use ask_human as often as it helps. Each question belongs to that zoomed-out discussion, such as whether to do it, which alternative, or what shape, rather than the narrow details of getting it built, and later questions can build on earlier answers. Keep the conversation going turn by turn, and change nothing until they tell you to build it."
2026-10-18 11:59:22 -07:00
2026-10-15 21:17:58 -07:00
func ( m * Manager ) createTask ( ctx context . Context , project api . Project , t api . Task , frozen * store . FrozenWorkflow , base , actor string ) ( api . Task , error ) {
2026-10-15 11:36:51 -07:00
t . Id = store . NewID ()
t . ProjectId = project . Id
if t . MergeTitle == "" {
t . MergeTitle = t . Title
2026-10-14 19:35:34 -07:00
}
2026-10-15 11:36:51 -07:00
t . Branch = "byre/" + t . Id
t . WorktreePath = filepath . Join ( m . worktreeRoot , project . Id , t . Id )
t . Status = api . TaskStatusActive
t . CreatedAt = time . Now (). UTC ()
2026-10-14 19:35:34 -07:00
var err error
t . BaseRef , err = git . AddWorktree ( project . RepoPath , t . WorktreePath , t . Branch , base )
if err != nil {
return api . Task {}, err
}
2026-10-15 21:17:58 -07:00
err = m . store . CreateTask ( ctx , t , frozen , actor )
2026-10-14 19:35:34 -07:00
if err != nil {
2026-10-16 10:36:38 -07:00
rerr := git . RemoveWorktree ( project . RepoPath , t . WorktreePath , t . Branch )
if rerr != nil {
slog . Error ( "remove worktree of task not created" , "task" , t . Id , "err" , rerr )
}
if errors . Is ( err , store . ErrParentNotOpen ) {
return api . Task {}, fmt . Errorf ( "%w: %w" , ErrConflict , err )
}
2026-10-14 19:35:34 -07:00
return api . Task {}, err
}
2026-10-15 13:15:49 -07:00
return m . store . GetTask ( ctx , t . Id )
2026-10-14 19:35:34 -07:00
}