Add team tasks: several agents in one worktree sharing a board

This commit is contained in:
Ian Gulliver
2026-10-18 08:48:57 -07:00
parent 7f62df19d2
commit 6bcde13b2f
31 changed files with 1392 additions and 439 deletions
+53 -4
View File
@@ -2,7 +2,7 @@ import Foundation
import Observation
import SwiftData
let taskTabs: [TaskTab] = [.transcript, .changes, .files, .terminal, .inbox, .reviews, .workflow, .project]
let taskTabs: [TaskTab] = [.transcript, .board, .changes, .files, .terminal, .inbox, .reviews, .workflow, .project]
private let historyPage = 1000
private let leadPage = 50
@@ -46,6 +46,7 @@ final class Store {
var summaryReads: [String: Int64] = [:]
private(set) var leadMore = false
var todoBusy: [String: String] = [:]
var pickedMembers: [String: String] = [:]
private(set) var drafts: Drafts?
let images = ImageDrafts()
let events = Listeners<ByreEvent>()
@@ -158,7 +159,32 @@ final class Store {
}
func currentSession(_ taskId: String) -> AgentSession? {
sessions[taskId]?.last(where: { $0.state != .completed }) ?? sessions[taskId]?.last
let list = (sessions[taskId] ?? []).filter { $0.member == (pickedMember(taskId) ?? "") }
return list.last(where: { $0.state != .completed }) ?? list.last
}
func team(_ taskId: String) -> [AgentSession] {
let list = sessions[taskId] ?? []
var names: [String] = []
for s in list where !s.member.isEmpty && !names.contains(s.member) {
names.append(s.member)
}
return names.compactMap { name in
let mine = list.filter { $0.member == name }
return mine.last(where: { $0.state != .completed }) ?? mine.last
}
}
func pickedMember(_ taskId: String) -> String? {
let names = team(taskId).map(\.member)
if let picked = pickedMembers[taskId], names.contains(picked) {
return picked
}
return names.first
}
func taskBusy(_ taskId: String) -> Bool {
(sessions[taskId] ?? []).contains { $0.state == .running }
}
func taskState(_ task: ByreTask) -> TaskState {
@@ -457,8 +483,31 @@ final class Store {
}
func openSummary(_ task: ByreTask) {
jump = (task.id, task.summarySeq)
focusTask(task.id, .transcript)
let seq = task.summarySeq
if let ev = taskFeeds[task.id]?.events.first(where: { $0.seq == seq }) {
pickMember(task.id, ev)
jump = (task.id, seq)
focusTask(task.id, .transcript)
return
}
Task {
do {
let evs = try await api.client.listTaskEvents(path: .init(taskId: task.id), query: .init(after: seq - 1, limit: 1)).ok.body.json
if let ev = evs.first(where: { $0.seq == seq }) {
pickMember(task.id, ev)
}
} catch {
log.error("load summary event: \(errorText(error), privacy: .public)")
}
jump = (task.id, seq)
focusTask(task.id, .transcript)
}
}
private func pickMember(_ taskId: String, _ ev: ByreEvent) {
if let member = str(ev.payload.value, "member"), !member.isEmpty {
pickedMembers[taskId] = member
}
}
func selectTask(_ taskId: String?) {