Add ready_for_merge tool and prompt agents for it after review
This commit is contained in:
@@ -472,6 +472,7 @@ private struct MergeButton: View {
|
||||
let task: ByreTask
|
||||
let busy: Bool
|
||||
let unmerged: Bool
|
||||
var small = false
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
|
||||
@@ -488,12 +489,18 @@ private struct MergeButton: View {
|
||||
: task.parentTaskId != nil
|
||||
? "Merge this subtask into its parent task's branch, or into \(target) (⌘S)"
|
||||
: "The agent merges \(target) in, then byre squashes into one commit on \(target) and pushes it to origin; held while another task in the project is merging (⌘S)"
|
||||
Button(intoParent ? "Merging into parent" : held ? "Merge held" : task.mergeAfterReview ? "Merge after review" : "Merge") {
|
||||
let button = Button(intoParent ? "Merging into parent" : held ? "Merge held" : task.mergeAfterReview ? "Merge after review" : "Merge") {
|
||||
Task {
|
||||
await confirmMerge(task.id, store, overlays)
|
||||
}
|
||||
}
|
||||
.buttonStyle(WebButtonStyle())
|
||||
Group {
|
||||
if small {
|
||||
button.buttonStyle(SmallLinkButtonStyle())
|
||||
} else {
|
||||
button.buttonStyle(WebButtonStyle())
|
||||
}
|
||||
}
|
||||
.disabled(busy || held || intoParent || !unmerged)
|
||||
.help(help)
|
||||
}
|
||||
@@ -502,17 +509,24 @@ private struct MergeButton: View {
|
||||
private struct DiscardButton: View {
|
||||
let task: ByreTask
|
||||
let busy: Bool
|
||||
var small = false
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@ViewState private var frame: CGRect = .zero
|
||||
|
||||
var body: some View {
|
||||
Button("Discard") {
|
||||
let button = Button("Discard") {
|
||||
Task {
|
||||
await confirmDiscard(task, store, overlays, anchor: frame)
|
||||
}
|
||||
}
|
||||
.buttonStyle(WebButtonStyle(danger: true))
|
||||
Group {
|
||||
if small {
|
||||
button.buttonStyle(SmallLinkButtonStyle(color: Palette.danger))
|
||||
} else {
|
||||
button.buttonStyle(WebButtonStyle(danger: true))
|
||||
}
|
||||
}
|
||||
.disabled(busy)
|
||||
.help("The agent writes the task summary, then byre deletes the worktree and branch without merging")
|
||||
.onGeometryChange(for: CGRect.self) { g in
|
||||
@@ -1027,6 +1041,9 @@ struct RowView: View {
|
||||
}
|
||||
}
|
||||
|
||||
case .ready(let when, let blocks):
|
||||
ReadyBubble(when: when, blocks: blocks, taskId: row.taskId, latest: row.id == model.builder.lastReady, unmerged: model.hasUnmerged)
|
||||
|
||||
case .handoff:
|
||||
DetailsBox(boxed: false) {
|
||||
NoteView(note: row.summary ?? Note(parts: []), cancelVisible: false)
|
||||
@@ -1115,6 +1132,42 @@ private struct QuestionBubble: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct ReadyBubble: View {
|
||||
let when: Date
|
||||
let blocks: [MDBlock]
|
||||
let taskId: String?
|
||||
let latest: Bool
|
||||
let unmerged: Bool
|
||||
@Environment(Store.self) private var store
|
||||
|
||||
var body: some View {
|
||||
let task = taskId.flatMap { store.findTask($0) }
|
||||
let current = latest && task?.status == .active
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 8) {
|
||||
Text("ready for merge")
|
||||
.font(Fonts.font(11, weight: current ? 700 : 400))
|
||||
.foregroundStyle(current ? Palette.warn : Palette.muted)
|
||||
WhenText(when: when)
|
||||
.font(Fonts.font(11))
|
||||
.foregroundStyle(Palette.muted)
|
||||
Spacer(minLength: 0)
|
||||
if current, let task {
|
||||
let busy = store.taskBusy(task.id)
|
||||
MergeButton(task: task, busy: busy, unmerged: unmerged, small: true)
|
||||
DiscardButton(task: task, busy: busy, small: true)
|
||||
}
|
||||
}
|
||||
MarkdownView(blocks: blocks, ctx: MDContext(taskId: taskId))
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 8)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(current ? Palette.warnTint : Palette.warnFaint, in: RoundedRectangle(cornerRadius: 10))
|
||||
.overlay(RoundedRectangle(cornerRadius: 10).stroke(current ? Palette.warn.opacity(0.55) : Palette.border))
|
||||
}
|
||||
}
|
||||
|
||||
private struct ToolResult: View {
|
||||
let result: String?
|
||||
let unfinished: Bool
|
||||
|
||||
@@ -51,6 +51,7 @@ enum RowKind {
|
||||
case turnEnd(error: String?)
|
||||
case note(Note)
|
||||
case review(note: Note, md: [MDBlock])
|
||||
case ready(when: Date, md: [MDBlock])
|
||||
case handoff
|
||||
case snapshot(Snapshot)
|
||||
case question
|
||||
@@ -192,6 +193,7 @@ final class TranscriptBuilder {
|
||||
private(set) var context: [String: Usage] = [:]
|
||||
private(set) var open = false
|
||||
private(set) var lastMergeAfterReview: Int?
|
||||
private(set) var lastReady: Int?
|
||||
@ObservationIgnored private var ctx = Ctx()
|
||||
@ObservationIgnored private var sink: RowList?
|
||||
@ObservationIgnored private var stream: Row?
|
||||
@@ -460,6 +462,9 @@ final class TranscriptBuilder {
|
||||
let snap = try! decodePayload(ev.payload, as: Snapshot.self)
|
||||
let row = Row(.snapshot(snap), taskId: taskId)
|
||||
ctx.reviewButtons[snap.n] = row
|
||||
if snap.trigger != .answer && sink == nil {
|
||||
lastReady = nil
|
||||
}
|
||||
put(row)
|
||||
continue
|
||||
}
|
||||
@@ -540,6 +545,9 @@ final class TranscriptBuilder {
|
||||
if ev.kind == "task.merge_after_review" && sink == nil {
|
||||
lastMergeAfterReview = row.id
|
||||
}
|
||||
if ev.kind == "task.ready" && sink == nil {
|
||||
lastReady = row.id
|
||||
}
|
||||
put(row, keepHeld: ev.kind == "agent.thinking")
|
||||
}
|
||||
return contextChanged
|
||||
@@ -744,6 +752,9 @@ final class TranscriptBuilder {
|
||||
case "task.summary":
|
||||
return note([.text("\(ev.actor) set the task summary")])
|
||||
|
||||
case "task.ready":
|
||||
return Row(.ready(when: when, md: parseMarkdown(jsString(p["overview"]))), taskId: taskId)
|
||||
|
||||
case "task.discard_started":
|
||||
return note([.text("\(ev.actor) asked to discard the task; the agent writes the task summary first")])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user