Move questions and reviews into the transcript; agents answer findings
This commit is contained in:
@@ -51,6 +51,8 @@ enum RowKind {
|
||||
case turnEnd(error: String?)
|
||||
case note(Note)
|
||||
case review(note: Note, md: [MDBlock])
|
||||
case finding(index: Int)
|
||||
case findingResponse(snapshot: Int, index: Int, response: String, reason: String)
|
||||
case ready(when: Date, md: [MDBlock])
|
||||
case handoff
|
||||
case snapshot(Snapshot)
|
||||
@@ -84,6 +86,7 @@ final class Row: Identifiable {
|
||||
var calls: [Call] = []
|
||||
var summary: Note?
|
||||
var review: Review?
|
||||
var reviewOf: Int?
|
||||
var question: InboxItem?
|
||||
var text = ""
|
||||
var md: [MDBlock] = []
|
||||
@@ -175,6 +178,8 @@ private final class Ctx {
|
||||
var subagents: [String: Subagent] = [:]
|
||||
var reviewers: Set<String> = []
|
||||
var reviewButtons: [Int: Row] = [:]
|
||||
var reviews: [Int: Review] = [:]
|
||||
var reviewRows: [String: [Row]] = [:]
|
||||
var questions: [String: Row] = [:]
|
||||
var run: Run?
|
||||
var held: RowList?
|
||||
@@ -471,6 +476,17 @@ final class TranscriptBuilder {
|
||||
if ev.kind == "review.started" || ev.kind == "review.finished" || ev.kind == "review.finding" {
|
||||
let r = try! decodePayload(ev.payload, as: Review.self)
|
||||
ctx.reviewButtons[r.snapshotN]?.review = r
|
||||
ctx.reviews[r.snapshotN] = r
|
||||
for row in ctx.reviewRows[r.id] ?? [] {
|
||||
row.review = r
|
||||
}
|
||||
if ev.kind == "review.finished" {
|
||||
ctx.run = nil
|
||||
for row in renderReview(r, ev.createdAt) {
|
||||
put(row)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
if ev.kind == "inbox.updated" {
|
||||
let item = try! decodePayload(ev.payload, as: InboxItem.self)
|
||||
@@ -490,6 +506,20 @@ final class TranscriptBuilder {
|
||||
put(row, keepHeld: true)
|
||||
continue
|
||||
}
|
||||
if ev.kind == "agent.tool_call" && sub == nil && str(p, "name") == "mcp__byre__respond_to_finding" {
|
||||
let input = dict(p, "input") ?? [:]
|
||||
let n = int(input, "snapshot") ?? 0
|
||||
let row = Row(.findingResponse(snapshot: n, index: (int(input, "finding") ?? 0) - 1, response: str(input, "response") ?? "", reason: str(input, "reason") ?? ""), taskId: taskId)
|
||||
row.started = ev.createdAt
|
||||
ctx.tools[jsString(p["id"])] = row
|
||||
if let r = ctx.reviews[n], r.state == .done {
|
||||
row.review = r
|
||||
ctx.reviewRows[r.id, default: []].append(row)
|
||||
}
|
||||
ctx.run = nil
|
||||
put(row, keepHeld: true)
|
||||
continue
|
||||
}
|
||||
if ev.kind == "agent.tool_call" {
|
||||
let name = toolLabel(jsString(p["name"]))
|
||||
let row = renderToolCall(p)
|
||||
@@ -605,6 +635,22 @@ final class TranscriptBuilder {
|
||||
return row
|
||||
}
|
||||
|
||||
private func renderReview(_ r: Review, _ when: Date) -> [Row] {
|
||||
let outcome = r.state == .failed ? "failed: \(r.error)" : r.state == .skipped ? "skipped: no changes" : reviewCounts(r)
|
||||
let parts: [NotePart] = [.text("review of snapshot #\(r.snapshotN): \(outcome)")]
|
||||
let fs = findings(r)
|
||||
let head = fs.isEmpty
|
||||
? Row(.note(Note(parts: parts, when: when, style: r.state == .failed ? .error : .plain)), taskId: taskId)
|
||||
: Row(.review(note: Note(parts: parts, when: when), md: parseMarkdown(str(r.result?.value ?? [:], "summary") ?? "")), taskId: taskId)
|
||||
let rows = [head] + fs.indices.map { Row(.finding(index: $0), taskId: taskId) }
|
||||
for row in rows {
|
||||
row.review = r
|
||||
row.reviewOf = r.snapshotN
|
||||
}
|
||||
ctx.reviewRows[r.id, default: []] += rows
|
||||
return rows
|
||||
}
|
||||
|
||||
private func renderEvent(_ ev: ByreEvent) -> Row? {
|
||||
let p = ev.payload.value
|
||||
let when = ev.createdAt
|
||||
@@ -654,6 +700,9 @@ final class TranscriptBuilder {
|
||||
return Row(.orphan(body), taskId: taskId)
|
||||
}
|
||||
target.result = body
|
||||
if case .findingResponse = target.kind, bool(p, "is_error") {
|
||||
target.hidden = true
|
||||
}
|
||||
return nil
|
||||
|
||||
case "turn.ended":
|
||||
@@ -709,15 +758,6 @@ final class TranscriptBuilder {
|
||||
case "task.merge_title":
|
||||
return note([.text("\(ev.actor) set the merge commit title to \"\(jsString(p["merge_title"]))\"")])
|
||||
|
||||
case "review.finished":
|
||||
let r = try! decodePayload(ev.payload, as: Review.self)
|
||||
let outcome = r.state == .failed ? "failed: \(r.error)" : r.state == .skipped ? "skipped: no changes" : reviewCounts(r)
|
||||
let parts: [NotePart] = [.text("review of snapshot #\(r.snapshotN): \(outcome)")]
|
||||
if findings(r).isEmpty {
|
||||
return note(parts, r.state == .failed ? .error : .plain)
|
||||
}
|
||||
return Row(.review(note: Note(parts: parts, when: when), md: parseMarkdown(str(r.result?.value ?? [:], "summary") ?? "")), taskId: taskId)
|
||||
|
||||
case "task.merge_queued":
|
||||
return note([.text("\(ev.actor) asked to merge; held until the merge in progress in this project is done")])
|
||||
|
||||
@@ -778,7 +818,7 @@ func findings(_ r: Review) -> [JSONDict] {
|
||||
}
|
||||
|
||||
func findingCount(_ r: Review) -> Int {
|
||||
r.state == .done ? findings(r).indices.filter { r.findingStates.additionalProperties[String($0)] == nil }.count : 0
|
||||
r.state == .done ? findings(r).indices.filter { findingOpen(r, $0) }.count : 0
|
||||
}
|
||||
|
||||
func latestReview(_ events: [ByreEvent]) -> Review? {
|
||||
|
||||
Reference in New Issue
Block a user