From 48741e8cbb5e9ef541595f9d521e222a82e6d6b9 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Thu, 22 Oct 2026 11:43:35 -0700 Subject: [PATCH] Load only active tasks on connect and refetch single tasks on events --- api/openapi.yaml | 7 +- internal/api/api.gen.go | 183 ++++++++++++++++++----------------- internal/server/server.go | 4 +- internal/store/store.go | 4 + mac/Sources/Byre/Store.swift | 120 ++++++++++++++++------- 5 files changed, 186 insertions(+), 132 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 6b4ce5c..35bd86e 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -337,10 +337,13 @@ paths: parameters: - { name: projectId, in: path, required: true, schema: { type: string } } get: - operationId: listTasks + operationId: listActiveTasks + description: > + The project's active tasks. A closed task comes from getTask; task events name the task they change, which + clients fetch again with getTask. responses: "200": - description: The project's tasks, newest first. + description: The project's active tasks, newest first. content: application/json: schema: diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 87d104d..bc9f1e7 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -2168,7 +2168,7 @@ type ServerInterface interface { CreateSchedule(w http.ResponseWriter, r *http.Request, projectId string) // (GET /api/projects/{projectId}/tasks) - ListTasks(w http.ResponseWriter, r *http.Request, projectId string) + ListActiveTasks(w http.ResponseWriter, r *http.Request, projectId string) // (POST /api/projects/{projectId}/tasks) CreateTask(w http.ResponseWriter, r *http.Request, projectId string) @@ -3199,8 +3199,8 @@ func (siw *ServerInterfaceWrapper) CreateSchedule(w http.ResponseWriter, r *http handler.ServeHTTP(w, r) } -// ListTasks operation middleware -func (siw *ServerInterfaceWrapper) ListTasks(w http.ResponseWriter, r *http.Request) { +// ListActiveTasks operation middleware +func (siw *ServerInterfaceWrapper) ListActiveTasks(w http.ResponseWriter, r *http.Request) { var err error _ = err @@ -3215,7 +3215,7 @@ func (siw *ServerInterfaceWrapper) ListTasks(w http.ResponseWriter, r *http.Requ } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListTasks(w, r, projectId) + siw.Handler.ListActiveTasks(w, r, projectId) })) for _, middleware := range siw.HandlerMiddlewares { @@ -4756,7 +4756,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}", wrapper.GetProject) m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/projects/{projectId}/review-workflow", wrapper.SetReviewWorkflow) m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/projects/{projectId}/audit-workflow", wrapper.SetAuditWorkflow) - m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/tasks", wrapper.ListTasks) + m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/tasks", wrapper.ListActiveTasks) m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/projects/{projectId}/tasks", wrapper.CreateTask) m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/schedules", wrapper.ListSchedules) m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/projects/{projectId}/schedules", wrapper.CreateSchedule) @@ -5095,92 +5095,93 @@ var swaggerSpec = []string{ "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", + "iEyh7ecrYTUicRMmooABvgNm8qtRUn/48P8PAA==", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/internal/server/server.go b/internal/server/server.go index 17660bb..6287c0c 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -345,14 +345,14 @@ func (s *Server) GetProject(w http.ResponseWriter, r *http.Request, projectID st writeJSON(w, http.StatusOK, p) } -func (s *Server) ListTasks(w http.ResponseWriter, r *http.Request, projectID string) { +func (s *Server) ListActiveTasks(w http.ResponseWriter, r *http.Request, projectID string) { _, err := s.store.GetProject(r.Context(), projectID) if err != nil { writeStoreError(w, err) return } - tasks, err := s.store.ListTasks(r.Context(), projectID) + tasks, err := s.store.ListActiveTasks(r.Context(), projectID) if err != nil { writeStoreError(w, err) return diff --git a/internal/store/store.go b/internal/store/store.go index 16368ce..1ee120b 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -410,6 +410,10 @@ func (s *Store) ListTasks(ctx context.Context, projectID string) ([]api.Task, er return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE project_id = ? ORDER BY created_at DESC`, projectID) } +func (s *Store) ListActiveTasks(ctx context.Context, projectID string) ([]api.Task, error) { + return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE project_id = ? AND status = ? ORDER BY created_at DESC`, projectID, string(api.TaskStatusActive)) +} + func (s *Store) GetTask(ctx context.Context, id string) (api.Task, error) { t, err := scanTask(s.db.QueryRowContext(ctx, `SELECT `+taskColumns+` FROM tasks WHERE id = ?`, id)) if errors.Is(err, sql.ErrNoRows) { diff --git a/mac/Sources/Byre/Store.swift b/mac/Sources/Byre/Store.swift index 5e1546d..509d1d8 100644 --- a/mac/Sources/Byre/Store.swift +++ b/mac/Sources/Byre/Store.swift @@ -97,7 +97,8 @@ final class Store { @ObservationIgnored private var historyLoads: Set = [] @ObservationIgnored private var requestSeqs: [String: Int] = [:] @ObservationIgnored private var taskReloads: Set = [] - @ObservationIgnored private var taskReloadAgain: Set = [] + @ObservationIgnored private var listReloads: Set = [] + @ObservationIgnored private var taskRefetches: [String: Set] = [:] @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) 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 }) {