Questions tab, question bubbles, answer snapshots; errors to transcript

This commit is contained in:
Ian Gulliver
2026-10-18 15:06:06 -07:00
parent 1ef3594016
commit 10a80ccb22
31 changed files with 744 additions and 665 deletions
+25 -8
View File
@@ -2,7 +2,7 @@ import Foundation
import Observation
import SwiftData
let taskTabs: [TaskTab] = [.transcript, .board, .changes, .files, .terminal, .inbox, .reviews, .workflow, .project]
let taskTabs: [TaskTab] = [.transcript, .board, .changes, .files, .terminal, .questions, .reviews, .workflow, .project]
private let historyPage = 1000
private let leadPage = 50
@@ -10,7 +10,7 @@ private let routeHash = Re(#"^#/(task|project)/(\w+)(?:/(\w+))?$"#)
struct TaskState {
enum Kind {
case answer, merging, held, error, working, idle
case answer, merging, held, working, idle
}
let kind: Kind
@@ -37,6 +37,7 @@ final class Store {
private(set) var selectedTaskId: String?
var pinned: Int?
var reviewed: Int?
var question: (taskId: String, itemId: String)?
var connected = false
private(set) var focus = Focus(tabs: [.transcript], tab: .transcript)
var leadSessions: [AgentSession] = []
@@ -188,9 +189,8 @@ final class Store {
}
func taskState(_ task: ByreTask) -> TaskState {
let open = inbox.filter { $0.taskId == task.id }
if open.contains(where: { $0.kind == .question }) {
return TaskState(kind: .answer, label: "Needs your answer", title: "The agent asked a question in the inbox")
if inbox.contains(where: { $0.taskId == task.id }) {
return TaskState(kind: .answer, label: "Needs your answer", title: "The agent asked a question")
}
if task.merging {
return TaskState(kind: .merging, label: "Merging", title: "The merge turn is running")
@@ -204,9 +204,6 @@ final class Store {
if task.mergeAfterReview {
return TaskState(kind: .held, label: "Merge after review", title: "Merges when the running review finishes with no findings")
}
if !open.isEmpty {
return TaskState(kind: .error, label: "Error", title: "The agent's session failed or hit a rate limit; see the inbox")
}
if let working = workingTitles[task.state] {
return TaskState(kind: .working, label: "Working", title: working)
}
@@ -234,6 +231,21 @@ final class Store {
focusTab(.reviews)
}
func showQuestion(_ taskId: String, _ itemId: String) {
focusTask(taskId, .questions)
question = (taskId, itemId)
}
func questionItem(_ itemId: String) -> InboxItem? {
if let open = inbox.first(where: { $0.id == itemId }) {
return open
}
guard let ev = transcript.last(where: { ($0.kind == "inbox.created" || $0.kind == "inbox.updated") && str($0.payload.value, "id") == itemId }) else {
return nil
}
return try! decodePayload(ev.payload, as: InboxItem.self)
}
var defaultModel: String {
me!.defaultModel
}
@@ -346,6 +358,7 @@ final class Store {
}
func focusTask(_ taskId: String, _ tab: TaskTab = .transcript) {
question = nil
spawn("set focus") {
try await self.setFocus(taskId: taskId, tab: tab)
}
@@ -358,6 +371,7 @@ final class Store {
}
func focusTab(_ tab: TaskTab) {
question = nil
spawn("set focus") {
try await self.setFocus(tab: tab)
}
@@ -370,6 +384,7 @@ final class Store {
let name = m?[3] ?? nil
let tab = taskTabs.first { $0.rawValue == name }
if kind == "task", let id {
question = nil
if id != focus.taskId || (tab != nil && tab != focus.tab) {
spawn("set focus") {
try await self.setFocus(taskId: id, tab: tab)
@@ -617,6 +632,8 @@ final class Store {
spawn("load inbox") {
try await self.loadInbox()
}
case "session.error", "session.rate_limited":
notifyError(self, ev)
case "turn.ended":
notifyTurn(self, ev)
if ev.payload.value["summary"] as? Bool == true, let taskId = ev.taskId {