Start a new task from a closed task
This commit is contained in:
+64
-2
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -19,6 +20,13 @@ func (m *Manager) Fork(ctx context.Context, src api.Task, project api.Project, n
|
||||
return t, err
|
||||
}
|
||||
|
||||
func forkOrigin(t api.Task, from string) string {
|
||||
if t.ForkedAt == nil {
|
||||
return "continued from " + from
|
||||
}
|
||||
return fmt.Sprintf("forked from %s at snapshot %d", from, *t.ForkedAt)
|
||||
}
|
||||
|
||||
func forkTitle(src api.Task, n int) string {
|
||||
return fmt.Sprintf("%s (fork of #%d)", src.Title, n)
|
||||
}
|
||||
@@ -172,7 +180,7 @@ func (m *Manager) fork(ctx context.Context, src api.Task, project api.Project, n
|
||||
return api.Task{}, "", fmt.Errorf("%w: snapshot %d of task %s has no recorded agent message at or before it to fork the conversation from", ErrConflict, n, src.Id)
|
||||
}
|
||||
|
||||
t, err := m.createTask(ctx, project, api.Task{
|
||||
return m.forkSession(ctx, project, api.Task{
|
||||
Title: title,
|
||||
MergeTitle: src.MergeTitle,
|
||||
CreatedBy: creator.Login,
|
||||
@@ -181,7 +189,61 @@ func (m *Manager) fork(ctx context.Context, src api.Task, project api.Project, n
|
||||
ForkedAt: &n,
|
||||
WorkflowStage: src.WorkflowStage,
|
||||
ParentTaskId: src.ParentTaskId,
|
||||
}, snap.CommitSha, creator.Login)
|
||||
}, snap.CommitSha, creator, srcSess, at)
|
||||
}
|
||||
|
||||
func (m *Manager) Continue(ctx context.Context, srcSess api.AgentSession, src api.Task, project api.Project, creator api.User, text, model, sendID, client string, images ...string) (api.Task, error) {
|
||||
if creator.GitName == "" || creator.GitEmail == "" {
|
||||
return api.Task{}, ErrNoGitIdentity
|
||||
}
|
||||
if src.Status == api.TaskStatusActive {
|
||||
return api.Task{}, fmt.Errorf("%w: task %s is still active", ErrConflict, src.Id)
|
||||
}
|
||||
if srcSess.Role != api.Implementer {
|
||||
return api.Task{}, fmt.Errorf("%w: session %s is a %s, not a task's agent", ErrConflict, srcSess.Id, srcSess.Role)
|
||||
}
|
||||
if srcSess.Vendor == api.Antigravity {
|
||||
return api.Task{}, fmt.Errorf("%w: session %s ran on Antigravity, and agy can't fork a conversation", ErrConflict, srcSess.Id)
|
||||
}
|
||||
|
||||
unlock := m.lockDelivery("continue:" + srcSess.Id)
|
||||
defer unlock()
|
||||
ctx = context.WithoutCancel(ctx)
|
||||
prior, sent, err := m.priorSend(ctx, sendID)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
if sent {
|
||||
return m.store.GetTask(ctx, prior.TaskId)
|
||||
}
|
||||
|
||||
events, err := m.store.TaskEvents(ctx, src.Id, 0, -1)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
at := forkPoint(events, srcSess.Id, math.MaxInt64)
|
||||
if at == "" {
|
||||
return api.Task{}, fmt.Errorf("%w: session %s has no recorded agent message to continue the conversation from", ErrConflict, srcSess.Id)
|
||||
}
|
||||
|
||||
t, sessionID, err := m.forkSession(ctx, project, api.Task{
|
||||
Title: src.Title + " (continued)",
|
||||
MergeTitle: src.MergeTitle,
|
||||
CreatedBy: creator.Login,
|
||||
ForkedFrom: &src.Id,
|
||||
}, project.DefaultBranch, creator, srcSess, at)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
_, _, err = m.Message(ctx, sessionID, creator.Login, text, model, sendID, client, images...)
|
||||
if err != nil {
|
||||
return api.Task{}, fmt.Errorf("task %s created, but its agent did not start: %w", t.Id, err)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (m *Manager) forkSession(ctx context.Context, project api.Project, task api.Task, base string, creator api.User, srcSess api.AgentSession, at string) (api.Task, string, error) {
|
||||
t, err := m.createTask(ctx, project, task, base, creator.Login)
|
||||
if err != nil {
|
||||
return api.Task{}, "", err
|
||||
}
|
||||
|
||||
@@ -142,6 +142,18 @@ func subtaskGuidance(parent api.Task) string {
|
||||
}
|
||||
|
||||
func forkGuidance(task, src api.Task) string {
|
||||
if task.ForkedAt == nil {
|
||||
outcome := "was discarded, so none of its changes are in this worktree"
|
||||
switch {
|
||||
case src.MergeCommit != nil:
|
||||
outcome = fmt.Sprintf("was merged as commit %s, so its work is already on the default branch", *src.MergeCommit)
|
||||
case src.Status == api.TaskStatusMerged:
|
||||
outcome = "was merged with no changes"
|
||||
}
|
||||
return fmt.Sprintf("\n- This conversation continues byre task %q, which %s. It goes on in a new task of its own: branch %s, worktree %s, started from the default branch. "+
|
||||
"Everything before this point happened in %s, which no longer exists: from now on read and change files only in %s, and use paths under it.",
|
||||
src.Title, outcome, task.Branch, task.WorktreePath, src.WorktreePath, task.WorktreePath)
|
||||
}
|
||||
return fmt.Sprintf("\n- This conversation was forked from byre task %q at its snapshot %d, into a task of its own: branch %s, worktree %s, which held that snapshot's files when the fork was made. "+
|
||||
"Everything before the fork happened in %s, which belongs to the other task: from now on read and change files only in %s, never in the other worktree, and use paths under it.",
|
||||
src.Title, *task.ForkedAt, task.Branch, task.WorktreePath, src.WorktreePath, task.WorktreePath)
|
||||
|
||||
@@ -588,7 +588,7 @@ func (m *Manager) signalText(ctx context.Context, ev api.Event) (string, error)
|
||||
return "", err
|
||||
}
|
||||
if task.ForkedFrom != nil {
|
||||
return fmt.Sprintf("%s was forked by %s from %s at snapshot %d.", m.taskLabel(ctx, task.Id), ev.Actor, m.taskLabel(ctx, *task.ForkedFrom), *task.ForkedAt), nil
|
||||
return fmt.Sprintf("%s was %s by %s.", m.taskLabel(ctx, task.Id), forkOrigin(task, m.taskLabel(ctx, *task.ForkedFrom)), ev.Actor), nil
|
||||
}
|
||||
if task.WorkflowStage != nil {
|
||||
return fmt.Sprintf("%s was created to run stage %q of %s.", m.taskLabel(ctx, task.Id), *task.WorkflowStage, m.taskLabel(ctx, *task.ParentTaskId)), nil
|
||||
|
||||
@@ -894,7 +894,7 @@ func (m *Manager) listTasksText(ctx context.Context) (string, error) {
|
||||
fmt.Fprintf(b, "; from TODO item %q", *t.TodoItem)
|
||||
}
|
||||
if t.ForkedFrom != nil {
|
||||
fmt.Fprintf(b, "; forked from %s at snapshot %d", *t.ForkedFrom, *t.ForkedAt)
|
||||
fmt.Fprintf(b, "; %s", forkOrigin(t, *t.ForkedFrom))
|
||||
}
|
||||
if t.ParentTaskId != nil && t.WorkflowStage == nil {
|
||||
fmt.Fprintf(b, "; subtask of %s", *t.ParentTaskId)
|
||||
|
||||
@@ -34,8 +34,8 @@ func (m *Manager) mergeNote(ctx context.Context, task api.Task, login string) (s
|
||||
if task.TodoItem != nil {
|
||||
fmt.Fprintf(b, "Started from the TODO item %q.\n", *task.TodoItem)
|
||||
}
|
||||
if task.ForkedFrom != nil && task.ForkedAt != nil {
|
||||
fmt.Fprintf(b, "Forked from task %s at snapshot %d.\n", *task.ForkedFrom, *task.ForkedAt)
|
||||
if task.ForkedFrom != nil {
|
||||
fmt.Fprintf(b, "This task was %s.\n", forkOrigin(task, "task "+*task.ForkedFrom))
|
||||
}
|
||||
fmt.Fprintf(b, "Full transcripts: the events of task %s in byre (GET /api/events?task_id=%s).\n", task.Id, task.Id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user