package agents import ( "context" "fmt" "log/slog" "g.fc.run/misc/byre/internal/api" "g.fc.run/misc/byre/internal/git" ) func mergeInPrompt(target string) string { return fmt.Sprintf("Merge %[1]s into this branch: run `git merge --no-ff --no-commit %[1]s`. "+ "Resolve any conflicts keeping both sides' intent; regenerate generated files instead of hand-merging them. "+ "Check that the build and tests pass. Leave the merge uncommitted: byre concludes it when the turn ends.", target) } func (m *Manager) MergeIn(ctx context.Context, task api.Task, user api.User, target string) (api.AgentSession, error) { sessions, err := m.store.ListSessions(ctx, task.Id) if err != nil { return api.AgentSession{}, err } for _, sess := range sessions { if sess.State != api.AgentSessionStateCompleted { return m.Send(ctx, sess.Id, "byre", mergeInPrompt(target), "") } } return m.create(ctx, task, user, mergeInPrompt(target), "", "byre", nil) } func (m *Manager) Merge(ctx context.Context, task api.Task, project api.Project, user api.User) (api.AgentSession, error) { if user.GitName == "" || user.GitEmail == "" { return api.AgentSession{}, ErrNoGitIdentity } m.mu.Lock() m.merges[task.Id] = user.Login m.mu.Unlock() sess, err := m.MergeIn(ctx, task, user, project.DefaultBranch) if err != nil { m.mu.Lock() delete(m.merges, task.Id) m.mu.Unlock() } return sess, err } func (m *Manager) takeMerge(taskID string) (string, bool) { m.mu.Lock() defer m.mu.Unlock() login, ok := m.merges[taskID] delete(m.merges, taskID) return login, ok } func (m *Manager) finishMerge(l *live, login string) { ctx := context.Background() err := m.squashAndClose(ctx, l.taskID, login) if err == nil { return } slog.Error("finish merge", "task", l.taskID, "err", err) m.fileError(l, "The agent merged the default branch in, but squashing onto it failed: "+err.Error()) err = m.Deliver(ctx, l.id) if err != nil { slog.Error("deliver inbox responses", "session", l.id, "err", err) } } func (m *Manager) squashAndClose(ctx context.Context, taskID, login string) error { task, err := m.store.GetTask(ctx, taskID) if err != nil { return err } project, err := m.store.GetProject(ctx, task.ProjectId) if err != nil { return err } user, err := m.store.GetUser(ctx, login) if err != nil { return err } commit, err := git.SquashMerge(project.RepoPath, task.WorktreePath, project.DefaultBranch, task.Title, []string{ "GIT_AUTHOR_NAME=" + user.GitName, "GIT_AUTHOR_EMAIL=" + user.GitEmail, "GIT_COMMITTER_NAME=" + user.GitName, "GIT_COMMITTER_EMAIL=" + user.GitEmail, }) if err != nil { return err } err = m.RemoveTask(ctx, task, project, login) if err != nil { return err } _, err = m.store.MergeTask(ctx, taskID, commit, login) return err } func (m *Manager) RemoveTask(ctx context.Context, task api.Task, project api.Project, actor string) error { err := m.StopTask(ctx, task.Id, actor) if err != nil { return err } return git.RemoveWorktree(project.RepoPath, task.WorktreePath, task.Branch) }