Load only active tasks on connect and refetch single tasks on events
This commit is contained in:
@@ -97,7 +97,8 @@ final class Store {
|
||||
@ObservationIgnored private var historyLoads: Set<ObjectIdentifier> = []
|
||||
@ObservationIgnored private var requestSeqs: [String: Int] = [:]
|
||||
@ObservationIgnored private var taskReloads: Set<String> = []
|
||||
@ObservationIgnored private var taskReloadAgain: Set<String> = []
|
||||
@ObservationIgnored private var listReloads: Set<String> = []
|
||||
@ObservationIgnored private var taskRefetches: [String: Set<String>] = [:]
|
||||
@ObservationIgnored private var terminalChanges = 0
|
||||
@ObservationIgnored private var streamOpens = 0
|
||||
@ObservationIgnored private var globalStream: EventStream?
|
||||
@@ -368,12 +369,70 @@ final class Store {
|
||||
}
|
||||
|
||||
func loadTasks(_ projectId: String) async throws {
|
||||
let latest = claimRequest("tasks:\(projectId)")
|
||||
let list = try await api.client.listTasks(path: .init(projectId: projectId)).ok.body.json
|
||||
if !latest() {
|
||||
try await reloadTasks(projectId, list: true)
|
||||
}
|
||||
|
||||
// Loads run one at a time per project, so a slower, older response never lands over a newer one.
|
||||
private func reloadTasks(_ projectId: String, list: Bool = false, task: String? = nil) async throws {
|
||||
if list {
|
||||
listReloads.insert(projectId)
|
||||
}
|
||||
if let task {
|
||||
taskRefetches[projectId, default: []].insert(task)
|
||||
}
|
||||
guard taskReloads.insert(projectId).inserted else {
|
||||
return
|
||||
}
|
||||
tasks[projectId] = list
|
||||
defer {
|
||||
taskReloads.remove(projectId)
|
||||
}
|
||||
var failure: (any Error)?
|
||||
while listReloads.contains(projectId) || taskRefetches[projectId] != nil {
|
||||
do {
|
||||
if listReloads.remove(projectId) != nil {
|
||||
try await loadActiveTasks(projectId)
|
||||
} else {
|
||||
try await refetchTasks(projectId, taskRefetches.removeValue(forKey: projectId)!)
|
||||
}
|
||||
} catch {
|
||||
failure = error
|
||||
}
|
||||
}
|
||||
if let failure {
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
|
||||
private func loadActiveTasks(_ projectId: String) async throws {
|
||||
let list = try await api.client.listActiveTasks(path: .init(projectId: projectId)).ok.body.json
|
||||
let fresh = Set(list.map(\.id))
|
||||
let held = (tasks[projectId] ?? []).filter { !fresh.contains($0.id) }
|
||||
for t in held where t.status == .active {
|
||||
taskRefetches[projectId, default: []].insert(t.id)
|
||||
}
|
||||
try await applyTasks(projectId, list + held)
|
||||
}
|
||||
|
||||
private func refetchTasks(_ projectId: String, _ ids: Set<String>) async throws {
|
||||
let api = api
|
||||
let fetched = try await withThrowingTaskGroup(of: ByreTask.self) { group in
|
||||
for id in ids {
|
||||
group.addTask {
|
||||
try await api.client.getTask(path: .init(taskId: id)).ok.body.json
|
||||
}
|
||||
}
|
||||
var out: [ByreTask] = []
|
||||
for try await t in group {
|
||||
out.append(t)
|
||||
}
|
||||
return out
|
||||
}
|
||||
let fresh = Set(fetched.map(\.id))
|
||||
try await applyTasks(projectId, fetched + (tasks[projectId] ?? []).filter { !fresh.contains($0.id) })
|
||||
}
|
||||
|
||||
private func applyTasks(_ projectId: String, _ list: [ByreTask]) async throws {
|
||||
tasks[projectId] = list.sorted { $0.createdAt > $1.createdAt }
|
||||
for t in list where t.status == .active && !t.background {
|
||||
_ = taskFeed(t.id)
|
||||
}
|
||||
@@ -384,26 +443,15 @@ final class Store {
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadTasks(_ projectId: String) async throws {
|
||||
guard taskReloads.insert(projectId).inserted else {
|
||||
taskReloadAgain.insert(projectId)
|
||||
private func lookUpTask(_ taskId: String) {
|
||||
if findTask(taskId) != nil {
|
||||
return
|
||||
}
|
||||
defer {
|
||||
taskReloads.remove(projectId)
|
||||
taskReloadAgain.remove(projectId)
|
||||
}
|
||||
var failure: (any Error)?
|
||||
repeat {
|
||||
taskReloadAgain.remove(projectId)
|
||||
do {
|
||||
try await loadTasks(projectId)
|
||||
} catch {
|
||||
failure = error
|
||||
spawn("load task") {
|
||||
let t = try await self.api.client.getTask(path: .init(taskId: taskId)).ok.body.json
|
||||
if self.findTask(taskId) == nil {
|
||||
self.tasks[t.projectId] = ((self.tasks[t.projectId] ?? []) + [t]).sorted { $0.createdAt > $1.createdAt }
|
||||
}
|
||||
} while taskReloadAgain.contains(projectId)
|
||||
if let failure {
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,6 +722,7 @@ final class Store {
|
||||
markVisited(taskId)
|
||||
selectedTaskId = taskId
|
||||
if let taskId {
|
||||
lookUpTask(taskId)
|
||||
_ = taskFeed(taskId)
|
||||
}
|
||||
pinned = nil
|
||||
@@ -798,11 +847,7 @@ final class Store {
|
||||
forwards.close(taskId)
|
||||
}
|
||||
notifyTask(self, ev)
|
||||
if let projectId = ev.projectId {
|
||||
spawn("load tasks") {
|
||||
try await self.reloadTasks(projectId)
|
||||
}
|
||||
}
|
||||
reloadTask(ev)
|
||||
case "session.created", "session.state", "session.subagents", "session.model":
|
||||
if ev.owner != nil {
|
||||
spawn("load lead sessions") {
|
||||
@@ -821,11 +866,7 @@ final class Store {
|
||||
spawn("load inbox") {
|
||||
try await self.loadInbox()
|
||||
}
|
||||
if let projectId = ev.projectId {
|
||||
spawn("load tasks") {
|
||||
try await self.reloadTasks(projectId)
|
||||
}
|
||||
}
|
||||
reloadTask(ev)
|
||||
case "inbox.updated":
|
||||
clearInbox(try! decodePayload(ev.payload, as: InboxItem.self))
|
||||
spawn("load inbox") {
|
||||
@@ -839,11 +880,7 @@ final class Store {
|
||||
}
|
||||
case "session.error", "session.rate_limited":
|
||||
notifyError(self, ev)
|
||||
if let projectId = ev.projectId {
|
||||
spawn("load tasks") {
|
||||
try await self.reloadTasks(projectId)
|
||||
}
|
||||
}
|
||||
reloadTask(ev)
|
||||
case "task.merge_facts":
|
||||
if let projectId = ev.projectId, let taskId = ev.taskId, let i = tasks[projectId]?.firstIndex(where: { $0.id == taskId }) {
|
||||
tasks[projectId]![i].mergeFacts = try! decodePayload(ev.payload, as: MergeFacts.self)
|
||||
@@ -876,6 +913,15 @@ final class Store {
|
||||
events.send(ev)
|
||||
}
|
||||
|
||||
private func reloadTask(_ ev: ByreEvent) {
|
||||
guard let projectId = ev.projectId, let taskId = ev.taskId else {
|
||||
return
|
||||
}
|
||||
spawn("load task") {
|
||||
try await self.reloadTasks(projectId, task: taskId)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTask(_ id: String, _ change: (inout ByreTask) -> Void) {
|
||||
for (projectId, list) in tasks {
|
||||
if let i = list.firstIndex(where: { $0.id == id }) {
|
||||
|
||||
Reference in New Issue
Block a user