Let the agent stop a merge for review after substantive conflict fixes
This commit is contained in:
@@ -514,7 +514,7 @@ func (m *Manager) ensureLive(ctx context.Context, sess api.AgentSession, task ap
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tools := []mcp.Tool{m.askTool(l), m.cancelQuestionTool(l), m.createTaskTool(l), m.setMergeTitleTool(l), m.exposePortTool(l)}
|
||||
tools := []mcp.Tool{m.askTool(l), m.cancelQuestionTool(l), m.createTaskTool(l), m.setMergeTitleTool(l), m.stopMergeTool(l), m.exposePortTool(l)}
|
||||
env := portEnv(task)
|
||||
stdio := []mcp.Stdio{}
|
||||
if sess.Role == api.Reviewer {
|
||||
|
||||
@@ -21,7 +21,9 @@ const maxMergeTitle = 72
|
||||
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)
|
||||
"Check that the build and tests pass. Leave the merge uncommitted: byre concludes it when the turn ends. "+
|
||||
"If resolving the conflicts took substantive changes rather than combining both sides as written, stop there: "+
|
||||
"end your turn with a description of those changes, so the review of the turn's snapshot covers them.", target)
|
||||
}
|
||||
|
||||
func (m *Manager) MergeIn(ctx context.Context, task api.Task, user api.User, target string) (api.AgentSession, error) {
|
||||
@@ -100,7 +102,8 @@ func (m *Manager) startMerge(ctx context.Context, task api.Task, project api.Pro
|
||||
}
|
||||
m.mergeHolders[project.Id] = task.Id
|
||||
|
||||
_, err = m.sendMergeIn(ctx, task, user, mergeInPrompt(project.DefaultBranch))
|
||||
prompt := mergeInPrompt(project.DefaultBranch) + fmt.Sprintf(" When this turn ends, byre squashes this branch onto %s and pushes it; if you stop, call stop_merge first so it doesn't.", project.DefaultBranch)
|
||||
_, err = m.sendMergeIn(ctx, task, user, prompt)
|
||||
if err != nil {
|
||||
m.takeMerge(task.Id)
|
||||
delete(m.mergeHolders, project.Id)
|
||||
@@ -395,6 +398,51 @@ func (m *Manager) setMergeTitle(ctx context.Context, l *live, raw json.RawMessag
|
||||
return fmt.Sprintf("Merge commit title set to %q.", title), nil
|
||||
}
|
||||
|
||||
type stopMergeArgs struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func (m *Manager) stopMergeTool(l *live) mcp.Tool {
|
||||
return mcp.Tool{
|
||||
Name: "stop_merge",
|
||||
Description: "Stop the merge byre is to make when this turn ends, so the task stays active and its snapshot is reviewed before anything is pushed. " +
|
||||
"Call it during a Merge turn when resolving conflicts took substantive changes, then end your turn describing them. The human merges again after the review.",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reason": {"type": "string", "description": "The substantive changes the resolution made, in a sentence or two."}
|
||||
},
|
||||
"required": ["reason"]
|
||||
}`),
|
||||
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
|
||||
return m.stopMerge(ctx, l, raw)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) stopMerge(ctx context.Context, l *live, raw json.RawMessage) (string, error) {
|
||||
args := stopMergeArgs{}
|
||||
err := json.Unmarshal(raw, &args)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("malformed stop_merge call: %w", err)
|
||||
}
|
||||
reason := strings.TrimSpace(args.Reason)
|
||||
if reason == "" {
|
||||
return "", fmt.Errorf("%w: reason must say what the resolution changed", ErrInvalid)
|
||||
}
|
||||
|
||||
if m.takeMerge(l.taskID) == "" {
|
||||
return "No merge is pending for this task; just end your turn.", nil
|
||||
}
|
||||
go m.releaseMerge(l.projectID, l.taskID)
|
||||
|
||||
err = m.emit(ctx, l, agentActor, "task.merge_stopped", map[string]any{"id": l.taskID, "reason": reason})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Merge stopped: byre won't squash or push when this turn ends. End your turn now with a description of the changes.", nil
|
||||
}
|
||||
|
||||
func cleanMergeTitle(raw string) (string, error) {
|
||||
title := strings.TrimSpace(raw)
|
||||
if title == "" || strings.ContainsAny(title, "\r\n") {
|
||||
|
||||
Reference in New Issue
Block a user