Run workflow stages with finish_stage handoffs and lead signals
This commit is contained in:
@@ -156,15 +156,16 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
|
||||
tool("create_task",
|
||||
"Create a task with its own branch and worktree, and start an agent on it with prompt as its first message. "+
|
||||
"The new agent sees nothing else, so the prompt must be a complete brief. It starts from the project's default branch, "+
|
||||
"or from from_task_id's committed HEAD.",
|
||||
"or from from_task_id's committed HEAD. With workflow, the task runs that workflow's stages in order instead, each in a fresh session on the stage's model; "+
|
||||
"prompt is then optional and is added to every stage's first message as its brief, and model can't be given.",
|
||||
`{"type": "object", "properties": {
|
||||
"project_id": {"type": "string"},
|
||||
"title": {"type": "string"},
|
||||
"prompt": {"type": "string"},
|
||||
"from_task_id": {"type": "string"},
|
||||
"model": {"type": "string"},
|
||||
"workflow": {"type": "string", "description": "The name of a workflow for the task to freeze a copy of."}
|
||||
}, "required": ["project_id", "title", "prompt"]}`,
|
||||
"workflow": {"type": "string", "description": "The name of a workflow for the task to freeze a copy of and run."}
|
||||
}, "required": ["project_id", "title"]}`,
|
||||
func(ctx context.Context, user api.User, raw json.RawMessage) (string, error) {
|
||||
args := struct {
|
||||
ProjectID string `json:"project_id"`
|
||||
@@ -178,8 +179,14 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.TrimSpace(args.Title) == "" || strings.TrimSpace(args.Prompt) == "" {
|
||||
return "", errors.New("title and prompt are required")
|
||||
if strings.TrimSpace(args.Title) == "" {
|
||||
return "", errors.New("title is required")
|
||||
}
|
||||
if args.Workflow == "" && strings.TrimSpace(args.Prompt) == "" {
|
||||
return "", errors.New("prompt is required without a workflow")
|
||||
}
|
||||
if args.Workflow != "" && args.Model != "" {
|
||||
return "", errModelWithWorkflow
|
||||
}
|
||||
if user.GitName == "" || user.GitEmail == "" {
|
||||
return "", ErrNoGitIdentity
|
||||
@@ -199,10 +206,17 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
task, err := m.CreateTask(ctx, project, args.Title, user.Login, base, leadActor(user.Login), args.Workflow)
|
||||
task, err := m.CreateTask(ctx, project, args.Title, user.Login, base, leadActor(user.Login), args.Workflow, args.Prompt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if args.Workflow != "" {
|
||||
err = m.startWorkflow(ctx, task, user, leadActor(user.Login))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("Task %s %q created from %s and its workflow %q started.", task.Id, task.Title, task.BaseRef, args.Workflow), nil
|
||||
}
|
||||
_, err = m.create(ctx, task, user, args.Prompt, m.modelOr(args.Model, l), leadActor(user.Login), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err)
|
||||
@@ -211,12 +225,13 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
|
||||
}),
|
||||
|
||||
tool("start_todo",
|
||||
"Start a task on an item of a project's TODO.md (its title, exactly as list_tasks' project or the file on the default branch has it).",
|
||||
"Start a task on an item of a project's TODO.md (its title, exactly as list_tasks' project or the file on the default branch has it). "+
|
||||
"With workflow, the task runs that workflow's stages with the item as their brief, and model can't be given.",
|
||||
`{"type": "object", "properties": {
|
||||
"project_id": {"type": "string"},
|
||||
"title": {"type": "string"},
|
||||
"model": {"type": "string"},
|
||||
"workflow": {"type": "string", "description": "The name of a workflow for the task to freeze a copy of."}
|
||||
"workflow": {"type": "string", "description": "The name of a workflow for the task to freeze a copy of and run."}
|
||||
}, "required": ["project_id", "title"]}`,
|
||||
func(ctx context.Context, user api.User, raw json.RawMessage) (string, error) {
|
||||
args := struct {
|
||||
@@ -229,6 +244,9 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if args.Workflow != "" && args.Model != "" {
|
||||
return "", errModelWithWorkflow
|
||||
}
|
||||
project, err := m.store.GetProject(ctx, args.ProjectID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -562,6 +580,13 @@ func (m *Manager) listTasksText(ctx context.Context) (string, error) {
|
||||
if t.ForkedFrom != nil {
|
||||
fmt.Fprintf(b, "; forked from %s at snapshot %d", *t.ForkedFrom, *t.ForkedAt)
|
||||
}
|
||||
if t.Workflow != nil {
|
||||
stage, err := m.workflowStatus(ctx, t.Id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(b, "; workflow %q, %s", *t.Workflow, stage)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
if !listed {
|
||||
@@ -674,6 +699,10 @@ func transcriptLine(ev api.Event, root string) string {
|
||||
}
|
||||
case "task.merged", "task.discarded":
|
||||
return fmt.Sprintf("%s by %s", ev.Kind, ev.Actor)
|
||||
case "task.workflow_stage":
|
||||
return fmt.Sprintf("started stage %v/%v of workflow %q: %s", p["stage"], p["stages"], text("workflow"), text("name"))
|
||||
case "task.workflow_finished":
|
||||
return fmt.Sprintf("finished workflow %q: %s", text("workflow"), clip(text("output"), 300))
|
||||
case "review.finished":
|
||||
r := api.Review{}
|
||||
err := remarshal(p, &r)
|
||||
|
||||
Reference in New Issue
Block a user