Carry the source transcript into forks and cross-link fork points
This commit is contained in:
+39
-26
@@ -268,7 +268,7 @@ final class ChatModel {
|
||||
store.jump = nil
|
||||
scrollTo(row.id)
|
||||
case .review(let n):
|
||||
guard let row = reviewRow(n) else {
|
||||
guard let row = reviewRow(n, taskId) else {
|
||||
return
|
||||
}
|
||||
store.jump = nil
|
||||
@@ -276,8 +276,8 @@ final class ChatModel {
|
||||
}
|
||||
}
|
||||
|
||||
private func reviewRow(_ n: Int) -> Row? {
|
||||
let rows = builder.root.rows
|
||||
private func reviewRow(_ n: Int, _ task: String?) -> Row? {
|
||||
let rows = builder.root.rows.filter { $0.taskId == task }
|
||||
return rows.first { $0.reviewOf == n } ?? rows.first { r in
|
||||
if case .snapshot(let s) = r.kind {
|
||||
return s.n == n
|
||||
@@ -286,8 +286,8 @@ final class ChatModel {
|
||||
}
|
||||
}
|
||||
|
||||
func showReview(_ n: Int) {
|
||||
if let row = reviewRow(n) {
|
||||
func showReview(_ n: Int, _ task: String?) {
|
||||
if let row = reviewRow(n, task) {
|
||||
scrollTo(row.id)
|
||||
}
|
||||
}
|
||||
@@ -317,7 +317,7 @@ final class ChatModel {
|
||||
var discard = false
|
||||
for row in builder.root.rows where !row.hidden {
|
||||
switch row.kind {
|
||||
case .question where row.question?.state == .open:
|
||||
case .question where row.question?.state == .open && row.taskId == taskId:
|
||||
questions.append(row.id)
|
||||
case .finding(let i):
|
||||
if let r = row.review, r.id == latest?.id, findingOpen(r, i) {
|
||||
@@ -1300,13 +1300,13 @@ struct RowView: View {
|
||||
}
|
||||
|
||||
case .snapshot(let snap):
|
||||
SnapshotRow(snap: snap, review: row.review, taskId: row.taskId) {
|
||||
model.showReview(snap.n)
|
||||
SnapshotRow(snap: snap, review: row.review, taskId: row.taskId, inherited: row.taskId != model.taskId, forks: row.forks) {
|
||||
model.showReview(snap.n, row.taskId)
|
||||
}
|
||||
|
||||
case .question:
|
||||
if let item = row.question {
|
||||
QuestionBubble(item: item)
|
||||
QuestionBubble(item: item, inherited: row.taskId != model.taskId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1314,9 +1314,10 @@ struct RowView: View {
|
||||
|
||||
private struct QuestionBubble: View {
|
||||
let item: InboxItem
|
||||
let inherited: Bool
|
||||
|
||||
var body: some View {
|
||||
let open = item.state == .open
|
||||
let open = item.state == .open && !inherited
|
||||
Group {
|
||||
if open {
|
||||
QuestionCard(item: item)
|
||||
@@ -1357,7 +1358,7 @@ private struct QuestionBubble: View {
|
||||
let answer = item.responses.last
|
||||
switch item.state {
|
||||
case .open:
|
||||
return "waiting for your answer"
|
||||
return inherited ? "unanswered when this task was forked" : "waiting for your answer"
|
||||
case .dismissed:
|
||||
return item.resolvedBy == nil ? "withdrawn by the agent" : "dismissed by \(item.resolvedBy!)"
|
||||
default:
|
||||
@@ -1594,16 +1595,7 @@ struct NoteView: View {
|
||||
case .text(let s):
|
||||
out += AttributedString(s)
|
||||
case .task(let id):
|
||||
guard let task = store.findTask(id) else {
|
||||
out += AttributedString(id)
|
||||
continue
|
||||
}
|
||||
var a = AttributedString(task.name)
|
||||
if task.status == .active {
|
||||
a.link = AppLink.task(task.id)
|
||||
a.foregroundColor = taskPalette[task.color]
|
||||
}
|
||||
out += a
|
||||
out += taskLink(store, id)
|
||||
case .url(let port):
|
||||
let url = previewURL(store.api.origin, Int(port) ?? 0)
|
||||
var a = AttributedString(url.absoluteString)
|
||||
@@ -1616,6 +1608,19 @@ struct NoteView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func taskLink(_ store: Store, _ id: String) -> AttributedString {
|
||||
guard let task = store.findTask(id) else {
|
||||
return AttributedString(id)
|
||||
}
|
||||
var a = AttributedString(task.name)
|
||||
if task.status == .active {
|
||||
a.link = AppLink.task(task.id)
|
||||
a.foregroundColor = taskPalette[task.color]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func previewURL(_ origin: URL, _ port: Int) -> URL {
|
||||
var c = URLComponents()
|
||||
c.scheme = "https"
|
||||
@@ -1629,6 +1634,8 @@ private struct SnapshotRow: View {
|
||||
let snap: Snapshot
|
||||
let review: Review?
|
||||
let taskId: String?
|
||||
let inherited: Bool
|
||||
let forks: [String]
|
||||
let showReview: () -> Void
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@@ -1640,12 +1647,14 @@ private struct SnapshotRow: View {
|
||||
HStack(spacing: 8) {
|
||||
Rectangle().fill(Palette.border).frame(height: 1)
|
||||
Text("snapshot #\(snap.n)")
|
||||
Button("changes") {
|
||||
store.pinSnapshot(snap.n)
|
||||
store.focusTab(.changes)
|
||||
if !inherited {
|
||||
Button("changes") {
|
||||
store.pinSnapshot(snap.n)
|
||||
store.focusTab(.changes)
|
||||
}
|
||||
.buttonStyle(SmallLinkButtonStyle())
|
||||
.help("Show snapshot \(snap.n) in the Changes tab")
|
||||
}
|
||||
.buttonStyle(SmallLinkButtonStyle())
|
||||
.help("Show snapshot \(snap.n) in the Changes tab")
|
||||
Button("fork") {
|
||||
guard let taskId else {
|
||||
return
|
||||
@@ -1667,6 +1676,10 @@ private struct SnapshotRow: View {
|
||||
if ![.answer, .fork, .askHuman].contains(snap.trigger) {
|
||||
reviewButton
|
||||
}
|
||||
ForEach(forks, id: \.self) { id in
|
||||
Text(AttributedString("forked as ") + taskLink(store, id))
|
||||
.fixedSize()
|
||||
}
|
||||
Rectangle().fill(Palette.border).frame(height: 1)
|
||||
}
|
||||
.font(Fonts.font(11))
|
||||
|
||||
@@ -25,6 +25,7 @@ final class Listeners<T> {
|
||||
final class Feed {
|
||||
private(set) var events: [ByreEvent] = []
|
||||
private(set) var ready = false
|
||||
private(set) var inherited = 0
|
||||
let appended = Listeners<[ByreEvent]>()
|
||||
let streamed = Listeners<ByreEvent>()
|
||||
@ObservationIgnored private var last: Int64 = 0
|
||||
@@ -49,15 +50,20 @@ final class Feed {
|
||||
}
|
||||
}
|
||||
|
||||
func load(_ history: [ByreEvent]) {
|
||||
func load(_ history: [ByreEvent], inherited: Int = 0) {
|
||||
if ready {
|
||||
return
|
||||
}
|
||||
ready = true
|
||||
self.inherited = inherited
|
||||
buffer = history + buffer
|
||||
flush()
|
||||
}
|
||||
|
||||
var own: ArraySlice<ByreEvent> {
|
||||
events.dropFirst(inherited)
|
||||
}
|
||||
|
||||
func prepend(_ older: [ByreEvent]) {
|
||||
events.insert(contentsOf: older, at: 0)
|
||||
}
|
||||
|
||||
@@ -164,8 +164,8 @@ final class Store {
|
||||
}
|
||||
}
|
||||
|
||||
var transcript: [ByreEvent] {
|
||||
selectedFeed?.events ?? []
|
||||
var transcript: ArraySlice<ByreEvent> {
|
||||
selectedFeed?.own ?? []
|
||||
}
|
||||
|
||||
var selectedFeed: Feed? {
|
||||
@@ -439,7 +439,7 @@ final class Store {
|
||||
break
|
||||
}
|
||||
}
|
||||
feed.load(got)
|
||||
feed.load(got, inherited: got.prefix { $0.taskId != taskId }.count)
|
||||
}
|
||||
|
||||
private func taskFeed(_ taskId: String) -> Feed {
|
||||
|
||||
@@ -87,6 +87,7 @@ final class Row: Identifiable {
|
||||
var summary: Note?
|
||||
var review: Review?
|
||||
var reviewOf: Int?
|
||||
var forks: [String] = []
|
||||
var question: InboxItem?
|
||||
var text = ""
|
||||
var md: [MDBlock] = []
|
||||
@@ -177,8 +178,8 @@ private final class Ctx {
|
||||
var tools: [String: Row] = [:]
|
||||
var subagents: [String: Subagent] = [:]
|
||||
var reviewers: Set<String> = []
|
||||
var reviewButtons: [Int: Row] = [:]
|
||||
var reviews: [Int: Review] = [:]
|
||||
var snapshots: [String: Row] = [:]
|
||||
var reviews: [String: Review] = [:]
|
||||
var reviewRows: [String: [Row]] = [:]
|
||||
var questions: [String: Row] = [:]
|
||||
var run: Run?
|
||||
@@ -204,12 +205,14 @@ final class TranscriptBuilder {
|
||||
@ObservationIgnored private var stream: Row?
|
||||
@ObservationIgnored private var streamTimer: Task<Void, Never>?
|
||||
@ObservationIgnored private let taskId: String?
|
||||
@ObservationIgnored private var rowTask: String?
|
||||
@ObservationIgnored private let worktree: () -> String
|
||||
@ObservationIgnored private let running: () -> Bool
|
||||
@ObservationIgnored private var pendingIds = 0
|
||||
|
||||
init(taskId: String?, worktree: @escaping () -> String, running: @escaping () -> Bool) {
|
||||
self.taskId = taskId
|
||||
rowTask = taskId
|
||||
self.worktree = worktree
|
||||
self.running = running
|
||||
}
|
||||
@@ -317,7 +320,7 @@ final class TranscriptBuilder {
|
||||
}
|
||||
run.calls.append(call)
|
||||
if run.group == nil {
|
||||
let group = Row(.group, taskId: taskId, body: true)
|
||||
let group = Row(.group, taskId: rowTask, body: true)
|
||||
group.owner = ctx.subagents[parent]?.row
|
||||
for list in [run.container, sink ?? root] {
|
||||
if let i = list.rows.firstIndex(where: { $0 === run.first }) {
|
||||
@@ -356,7 +359,7 @@ final class TranscriptBuilder {
|
||||
if let stream {
|
||||
row = stream
|
||||
} else {
|
||||
row = Row(.stream, taskId: taskId)
|
||||
row = Row(.stream, taskId: rowTask)
|
||||
row.hidden = true
|
||||
stream = row
|
||||
ctx.run = nil
|
||||
@@ -378,7 +381,7 @@ final class TranscriptBuilder {
|
||||
}
|
||||
|
||||
private func openHandoff() {
|
||||
let row = Row(.handoff, taskId: taskId, body: true)
|
||||
let row = Row(.handoff, taskId: rowTask, body: true)
|
||||
row.summary = Note(parts: [.text("writing a handoff…")])
|
||||
put(row)
|
||||
ctx.handoff = row
|
||||
@@ -398,9 +401,14 @@ final class TranscriptBuilder {
|
||||
|
||||
private func render(_ events: [ByreEvent]) -> Bool {
|
||||
var contextChanged = false
|
||||
defer {
|
||||
rowTask = taskId
|
||||
}
|
||||
for ev in events {
|
||||
let p = ev.payload.value
|
||||
let parent = parentOf(p)
|
||||
rowTask = ev.taskId ?? taskId
|
||||
let own = rowTask == taskId
|
||||
if ev.kind == "session.created" && str(p, "role") == "reviewer", let sid = ev.agentSessionId, !sid.isEmpty {
|
||||
ctx.reviewers.insert(sid)
|
||||
continue
|
||||
@@ -423,14 +431,14 @@ final class TranscriptBuilder {
|
||||
if sink == nil && ((ev.kind == "agent.message" && parent.isEmpty) || ev.kind == "turn.ended" || ev.kind == "user.message") {
|
||||
dropStream()
|
||||
}
|
||||
if sink == nil && ev.kind == "user.message" {
|
||||
if sink == nil && own && ev.kind == "user.message" {
|
||||
confirmPending(str(p, "text") ?? "", queued: true, local: ev.actor != "byre")
|
||||
}
|
||||
if sink == nil && (ev.kind == "task.merged" || ev.kind == "task.discarded") {
|
||||
if sink == nil && own && (ev.kind == "task.merged" || ev.kind == "task.discarded") {
|
||||
pending.removeAll { !$0.local }
|
||||
}
|
||||
if ev.kind == "user.queued" {
|
||||
if sink == nil {
|
||||
if sink == nil && own {
|
||||
let text = str(p, "text") ?? ""
|
||||
confirmPending(text, queued: false, local: true)
|
||||
pendingIds += 1
|
||||
@@ -440,7 +448,7 @@ final class TranscriptBuilder {
|
||||
continue
|
||||
}
|
||||
if ev.kind == "user.unqueued" {
|
||||
if sink == nil, let id = int(p, "id") {
|
||||
if sink == nil && own, let id = int(p, "id") {
|
||||
withdrawn(id)
|
||||
}
|
||||
continue
|
||||
@@ -465,18 +473,26 @@ final class TranscriptBuilder {
|
||||
if ev.kind == "snapshot.created" {
|
||||
ctx.run = nil
|
||||
let snap = try! decodePayload(ev.payload, as: Snapshot.self)
|
||||
let row = Row(.snapshot(snap), taskId: taskId)
|
||||
ctx.reviewButtons[snap.n] = row
|
||||
if ![.answer, .fork, .askHuman].contains(snap.trigger) && sink == nil {
|
||||
let row = Row(.snapshot(snap), taskId: rowTask)
|
||||
ctx.snapshots[snapKey(snap.n)] = row
|
||||
if ![.answer, .fork, .askHuman].contains(snap.trigger) && sink == nil && own {
|
||||
lastReady = nil
|
||||
}
|
||||
put(row)
|
||||
continue
|
||||
}
|
||||
if ev.kind == "task.forked", let fork = str(p, "fork") {
|
||||
if let row = ctx.snapshots[snapKey(int(p, "forked_at") ?? 0)] {
|
||||
row.forks.append(fork)
|
||||
continue
|
||||
}
|
||||
put(Row(.note(Note(parts: [.text("\(ev.actor) forked snapshot #\(jsString(p["forked_at"])) as "), .task(fork)], when: ev.createdAt)), taskId: rowTask))
|
||||
continue
|
||||
}
|
||||
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
|
||||
ctx.snapshots[snapKey(r.snapshotN)]?.review = r
|
||||
ctx.reviews[snapKey(r.snapshotN)] = r
|
||||
for row in ctx.reviewRows[r.id] ?? [] {
|
||||
row.review = r
|
||||
}
|
||||
@@ -509,10 +525,10 @@ final class TranscriptBuilder {
|
||||
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)
|
||||
let row = Row(.findingResponse(snapshot: n, index: (int(input, "finding") ?? 0) - 1, response: str(input, "response") ?? "", reason: str(input, "reason") ?? ""), taskId: rowTask)
|
||||
row.started = ev.createdAt
|
||||
ctx.tools[jsString(p["id"])] = row
|
||||
if let r = ctx.reviews[n], r.state == .done {
|
||||
if let r = ctx.reviews[snapKey(n)], r.state == .done {
|
||||
row.review = r
|
||||
ctx.reviewRows[r.id, default: []].append(row)
|
||||
}
|
||||
@@ -569,16 +585,16 @@ final class TranscriptBuilder {
|
||||
if ev.kind == "user.message" && !bool(p, "mid_turn") {
|
||||
ctx.open = true
|
||||
}
|
||||
if ev.kind == "turn.ended" && bool(p, "summary") && taskId != nil {
|
||||
if ev.kind == "turn.ended" && bool(p, "summary") && taskId != nil && own {
|
||||
row.summarySeq = ev.seq
|
||||
}
|
||||
if ev.kind == "task.merge_after_review" && sink == nil {
|
||||
if ev.kind == "task.merge_after_review" && sink == nil && own {
|
||||
lastMergeAfterReview = row.id
|
||||
}
|
||||
if (ev.kind == "task.ready" || ev.kind == "task.discard_suggested") && sink == nil {
|
||||
if (ev.kind == "task.ready" || ev.kind == "task.discard_suggested") && sink == nil && own {
|
||||
lastReady = row.id
|
||||
}
|
||||
if (ev.kind == "inbox.created" || ev.kind == "task.ready_cleared") && sink == nil {
|
||||
if (ev.kind == "inbox.created" || ev.kind == "task.ready_cleared") && sink == nil && own {
|
||||
lastReady = nil
|
||||
}
|
||||
put(row, keepHeld: ev.kind == "agent.thinking")
|
||||
@@ -586,6 +602,10 @@ final class TranscriptBuilder {
|
||||
return contextChanged
|
||||
}
|
||||
|
||||
private func snapKey(_ n: Int) -> String {
|
||||
"\(rowTask ?? "")/\(n)"
|
||||
}
|
||||
|
||||
private func claudeTaskStatus(_ p: JSONDict) {
|
||||
guard str(p, "type") == "system", let sub = ctx.subagents[jsString(p["tool_use_id"])] else {
|
||||
return
|
||||
@@ -619,7 +639,7 @@ final class TranscriptBuilder {
|
||||
}
|
||||
|
||||
private func renderToolCall(_ p: JSONDict) -> Row {
|
||||
let row = Row(.tool(name: toolLabel(jsString(p["name"])), brief: toolBrief(p["input"] ?? nil, worktree()), input: prettyJSON(p["input"] ?? nil)), taskId: taskId)
|
||||
let row = Row(.tool(name: toolLabel(jsString(p["name"])), brief: toolBrief(p["input"] ?? nil, worktree()), input: prettyJSON(p["input"] ?? nil)), taskId: rowTask)
|
||||
row.tag = subagentTag(parentOf(p))
|
||||
row.owner = ctx.subagents[parentOf(p)]?.row
|
||||
ctx.tools[jsString(p["id"])] = row
|
||||
@@ -630,7 +650,7 @@ final class TranscriptBuilder {
|
||||
let input = dict(p, "input") ?? [:]
|
||||
let id = jsString(p["id"])
|
||||
let type = str(input, "subagent_type") ?? "subagent"
|
||||
let row = Row(.subagent(title: "\(jsString(p["name"])) \(type)", description: str(input, "description") ?? "", prompt: parseMarkdown(str(input, "prompt") ?? "")), taskId: taskId, body: true)
|
||||
let row = Row(.subagent(title: "\(jsString(p["name"])) \(type)", description: str(input, "description") ?? "", prompt: parseMarkdown(str(input, "prompt") ?? "")), taskId: rowTask, body: true)
|
||||
row.tag = subagentTag(parentOf(p))
|
||||
row.owner = ctx.subagents[parentOf(p)]?.row
|
||||
ctx.tools[id] = row
|
||||
@@ -643,9 +663,9 @@ final class TranscriptBuilder {
|
||||
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) }
|
||||
? Row(.note(Note(parts: parts, when: when, style: r.state == .failed ? .error : .plain)), taskId: rowTask)
|
||||
: Row(.review(note: Note(parts: parts, when: when), md: parseMarkdown(str(r.result?.value ?? [:], "summary") ?? "")), taskId: rowTask)
|
||||
let rows = [head] + fs.indices.map { Row(.finding(index: $0), taskId: rowTask) }
|
||||
for row in rows {
|
||||
row.review = r
|
||||
row.reviewOf = r.snapshotN
|
||||
@@ -658,7 +678,7 @@ final class TranscriptBuilder {
|
||||
let p = ev.payload.value
|
||||
let when = ev.createdAt
|
||||
func note(_ parts: [NotePart], _ style: NoteStyle = .plain, time: Bool = true) -> Row {
|
||||
Row(.note(Note(parts: parts, when: time ? when : nil, style: style)), taskId: taskId)
|
||||
Row(.note(Note(parts: parts, when: time ? when : nil, style: style)), taskId: rowTask)
|
||||
}
|
||||
|
||||
switch ev.kind {
|
||||
@@ -671,17 +691,17 @@ final class TranscriptBuilder {
|
||||
if ev.actor == "byre" {
|
||||
let first = text.components(separatedBy: "\n").first ?? ""
|
||||
let brief = first.count > 120 ? "\(first.prefix(120))…" : first
|
||||
let row = Row(.byrePrompt(brief: brief, when: when, md: parseMarkdown(text)), taskId: taskId)
|
||||
let row = Row(.byrePrompt(brief: brief, when: when, md: parseMarkdown(text)), taskId: rowTask)
|
||||
row.hidden = bool(p, "continuation")
|
||||
return row
|
||||
}
|
||||
let images = (p["images"] as? [(any Sendable)?] ?? []).map { jsString($0) }
|
||||
return Row(.user(actor: ev.actor, when: when, images: images, md: trimmed(text).isEmpty ? nil : parseMarkdown(text)), taskId: taskId)
|
||||
return Row(.user(actor: ev.actor, when: when, images: images, md: trimmed(text).isEmpty ? nil : parseMarkdown(text)), taskId: rowTask)
|
||||
|
||||
case "agent.message":
|
||||
let text = jsString(p["text"])
|
||||
if !parentOf(p).isEmpty {
|
||||
let row = Row(.subagentMessage(when: when, md: parseMarkdown(text)), taskId: taskId)
|
||||
let row = Row(.subagentMessage(when: when, md: parseMarkdown(text)), taskId: rowTask)
|
||||
row.tag = subagentTag(parentOf(p))
|
||||
return row
|
||||
}
|
||||
@@ -690,17 +710,17 @@ final class TranscriptBuilder {
|
||||
return nil
|
||||
}
|
||||
ctx.quiet = false
|
||||
return Row(.agent(md: parseMarkdown(text)), taskId: taskId)
|
||||
return Row(.agent(md: parseMarkdown(text)), taskId: rowTask)
|
||||
|
||||
case "agent.thinking":
|
||||
let row = Row(.thinking(md: parseMarkdown(jsString(p["text"]))), taskId: taskId)
|
||||
let row = Row(.thinking(md: parseMarkdown(jsString(p["text"]))), taskId: rowTask)
|
||||
row.tag = subagentTag(parentOf(p))
|
||||
return row
|
||||
|
||||
case "agent.tool_result":
|
||||
let body = resultText(p["content"] ?? nil)
|
||||
guard let target = ctx.tools[jsString(p["tool_use_id"])] else {
|
||||
return Row(.orphan(body), taskId: taskId)
|
||||
return Row(.orphan(body), taskId: rowTask)
|
||||
}
|
||||
target.result = body
|
||||
if case .findingResponse = target.kind, bool(p, "is_error") {
|
||||
@@ -713,7 +733,7 @@ final class TranscriptBuilder {
|
||||
if bool(p, "handoff") {
|
||||
return note([.text("handoff written; continuing in a new session with a fresh context")])
|
||||
}
|
||||
return Row(.turnEnd(error: bool(p, "is_error") ? "turn ended: \(jsString(p["subtype"]).replacingOccurrences(of: "_", with: " "))" : nil), taskId: taskId)
|
||||
return Row(.turnEnd(error: bool(p, "is_error") ? "turn ended: \(jsString(p["subtype"]).replacingOccurrences(of: "_", with: " "))" : nil), taskId: rowTask)
|
||||
|
||||
case "session.created":
|
||||
let model = str(p, "model") ?? ""
|
||||
@@ -729,7 +749,7 @@ final class TranscriptBuilder {
|
||||
return note([.text("session \(jsString(p["state"])): \(error)")], .error, time: false)
|
||||
|
||||
case "inbox.created":
|
||||
let row = Row(.question, taskId: taskId)
|
||||
let row = Row(.question, taskId: rowTask)
|
||||
let item = try! decodePayload(ev.payload, as: InboxItem.self)
|
||||
row.question = item
|
||||
ctx.questions[item.id] = row
|
||||
@@ -744,8 +764,11 @@ final class TranscriptBuilder {
|
||||
return note([.text("\(jsString(p["error"])) (\(jsString(p["vendor"])) \(limit["rateLimitType"].map { jsString($0) } ?? "") limit resets \(resets))")], .error)
|
||||
|
||||
case "task.created":
|
||||
if let from = str(p, "forked_from"), let task = rowTask, task != taskId {
|
||||
return note([.text("\(ev.actor) forked "), .task(task), .text(" here from "), .task(from), .text(" at snapshot #\(jsString(p["forked_at"]))")])
|
||||
}
|
||||
if let from = str(p, "forked_from") {
|
||||
return note([.text("\(ev.actor) forked this task from "), .task(from), .text(" at snapshot #\(jsString(p["forked_at"])); its agent continues that task's conversation as it stood there")])
|
||||
return note([.text("\(ev.actor) forked this task here from "), .task(from), .text(" at snapshot #\(jsString(p["forked_at"])); everything above happened in that task, and its agent continues that conversation")])
|
||||
}
|
||||
if let parentTask = str(p, "parent_task_id") {
|
||||
return note([.text("subtask of "), .task(parentTask), .text(", created by its agent")])
|
||||
@@ -773,7 +796,7 @@ final class TranscriptBuilder {
|
||||
case "task.merge_after_review":
|
||||
var n = Note(parts: [.text("\(ev.actor) asked to merge once the review of snapshot #\(jsString(p["snapshot_n"])) finishes with no findings")], when: when)
|
||||
n.cancelMergeAfterReview = jsString(p["id"])
|
||||
return Row(.note(n), taskId: taskId)
|
||||
return Row(.note(n), taskId: rowTask)
|
||||
|
||||
case "task.merge_after_review_cancelled":
|
||||
return note([.text("\(ev.actor) cancelled the merge after review")])
|
||||
@@ -799,10 +822,10 @@ final class TranscriptBuilder {
|
||||
return note([.text("\(ev.actor) set the task summary")])
|
||||
|
||||
case "task.ready":
|
||||
return Row(.ready(when: when, md: parseMarkdown(jsString(p["overview"])), discard: false), taskId: taskId)
|
||||
return Row(.ready(when: when, md: parseMarkdown(jsString(p["overview"])), discard: false), taskId: rowTask)
|
||||
|
||||
case "task.discard_suggested":
|
||||
return Row(.ready(when: when, md: parseMarkdown(jsString(p["reason"])), discard: true), taskId: taskId)
|
||||
return Row(.ready(when: when, md: parseMarkdown(jsString(p["reason"])), discard: true), taskId: rowTask)
|
||||
|
||||
case "task.ready_cleared":
|
||||
return note([.text(ev.actor == "agent" ? "the agent withdrew its ready call" : "\(ev.actor) dismissed the ready call")])
|
||||
@@ -833,7 +856,7 @@ func findingCount(_ r: Review) -> Int {
|
||||
r.state == .done ? findings(r).indices.filter { findingOpen(r, $0) }.count : 0
|
||||
}
|
||||
|
||||
func latestReview(_ events: [ByreEvent]) -> Review? {
|
||||
func latestReview(_ events: ArraySlice<ByreEvent>) -> Review? {
|
||||
var best: Review?
|
||||
for ev in events where isReviewEvent(ev) {
|
||||
let r = try! decodePayload(ev.payload, as: Review.self)
|
||||
|
||||
Reference in New Issue
Block a user