2026-10-16 22:46:54 -07:00
import Foundation
import Observation
2026-10-17 10:13:26 -07:00
import SwiftData
2026-10-16 22:46:54 -07:00
2026-10-19 09:14:21 -07:00
let taskTabs : [ TaskTab ] = [. transcript , . board , . changes , . files , . terminal , . workflow , . project ]
enum JumpTarget : Equatable {
case question ( String )
case review ( Int )
}
struct Jump : Equatable {
let taskId : String
let target : JumpTarget
let n : Int
}
2026-10-16 22:46:54 -07:00
private let historyPage = 1000
private let leadPage = 50
2026-10-17 10:54:06 -07:00
private let routeHash = Re (# "^#/(task|project)/(\w+)(?:/(\w+))?$" #)
2026-10-16 22:46:54 -07:00
struct TaskState {
enum Kind {
2026-10-19 17:53:54 -07:00
case answer , merging , held , working , ready , idle
2026-10-16 22:46:54 -07:00
}
let kind : Kind
let label : String
let title : String
2026-10-19 17:53:54 -07:00
var needsYou : Bool {
kind == . answer || kind == . ready
}
2026-10-16 22:46:54 -07:00
}
2026-10-18 23:13:59 -07:00
private let workingTitles : [ ByreTask . StatePayload : ( label : String , title : String )] = [
. running : ( "Working" , "The agent is mid-turn or its subagents are running" ),
. reviewPending : ( "Reviewing" , "A review of the turn's work is about to start" ),
. reviewing : ( "Reviewing" , "A review is running" ),
2026-10-18 23:22:39 -07:00
. discarding : ( "Discarding" , "The agent is writing the task summary, then byre removes the task" ),
2026-10-16 22:46:54 -07:00
]
@ MainActor
@ Observable
final class Store {
let api : API
var me : User ?
var projects : [ Project ] = []
var tasks : [ String : [ ByreTask ]] = [:]
var sessions : [ String : [ AgentSession ]] = [:]
var inbox : [ InboxItem ] = []
private ( set ) var selectedTaskId : String ?
var pinned : Int ?
var connected = false
2026-10-19 08:56:37 -07:00
private ( set ) var focus = Focus ( tab : . transcript )
2026-10-16 22:46:54 -07:00
var leadSessions : [ AgentSession ] = []
let leadFeed : Feed
2026-10-19 09:14:21 -07:00
var jump : Jump ?
@ ObservationIgnored private var jumps = 0
2026-10-17 09:04:32 -07:00
var composerFocus : String ?
2026-10-16 22:46:54 -07:00
var summaryReads : [ String : Int64 ] = [:]
private ( set ) var leadMore = false
2026-10-17 10:54:06 -07:00
var todoBusy : [ String : String ] = [:]
2026-10-18 08:48:57 -07:00
var pickedMembers : [ String : String ] = [:]
2026-10-19 16:53:21 -07:00
var filePlaces : [ String : FilePlace ] = [:]
2026-10-16 23:30:57 -07:00
private ( set ) var drafts : Drafts ?
2026-10-19 09:51:42 -07:00
let images : ImageDrafts
2026-10-18 20:58:56 -07:00
let imageCache = ImageCache ()
2026-10-16 22:46:54 -07:00
let events = Listeners < ByreEvent >()
2026-10-16 23:30:57 -07:00
let paneFocus : PaneFocus
2026-10-17 23:45:31 -07:00
let remote : Remote
2026-10-16 22:46:54 -07:00
private var taskFeeds : [ String : Feed ] = [:]
@ ObservationIgnored private var historyLoads : Set < ObjectIdentifier > = []
@ ObservationIgnored private var requestSeqs : [ String : Int ] = [:]
@ ObservationIgnored private var globalStream : EventStream ?
@ ObservationIgnored private var leadStream : EventStream ?
@ ObservationIgnored private var stopped = false
2026-10-18 20:58:56 -07:00
@ ObservationIgnored var changesMemory = ChangesMemory ()
2026-10-16 22:46:54 -07:00
2026-10-19 09:51:42 -07:00
init ( api : API , images : ImageDrafts ) {
2026-10-16 22:46:54 -07:00
self . api = api
2026-10-19 09:51:42 -07:00
self . images = images
2026-10-16 22:46:54 -07:00
leadFeed = Feed ( shown : { true })
2026-10-16 23:30:57 -07:00
paneFocus = PaneFocus ( api : api )
2026-10-17 23:45:31 -07:00
remote = Remote ( api : api )
2026-10-16 22:46:54 -07:00
}
2026-10-16 23:30:57 -07:00
func stop () async {
2026-10-16 22:46:54 -07:00
stopped = true
globalStream ?. stop ()
leadStream ?. stop ()
2026-10-17 23:45:31 -07:00
remote . stop ()
2026-10-16 23:30:57 -07:00
drafts ?. flush ()
await paneFocus . leave ()
}
2026-10-19 09:51:42 -07:00
func loadDrafts ( from old : Store ?) {
drafts = old ?. drafts ?? Drafts ( origin : api . origin , login : me !. login )
2026-10-16 23:30:57 -07:00
drafts !. prune ( self )
}
2026-10-17 10:54:06 -07:00
var todoStarting : Set < String > {
Set ( todoBusy . keys )
}
2026-10-16 23:30:57 -07:00
func startTodo ( _ projectId : String , _ title : String , discuss : Bool , _ overlays : Overlays ) {
let key = " \( projectId ) \n \( title ) "
2026-10-17 10:54:06 -07:00
if todoBusy [ key ] != nil {
2026-10-16 23:30:57 -07:00
return
}
2026-10-17 10:54:06 -07:00
todoBusy [ key ] = discuss ? "discuss" : "start"
2026-10-16 23:30:57 -07:00
Task {
do {
2026-10-18 00:57:51 -07:00
_ = try await api . client . startTodoItem ( path : . init ( projectId : projectId ), body : . json (. init ( title : title , model : defaultModel , discuss : discuss ))). created
2026-10-16 23:30:57 -07:00
} catch {
overlays . showAlert ( errorText ( error ))
}
2026-10-17 10:54:06 -07:00
todoBusy [ key ] = nil
2026-10-16 23:30:57 -07:00
}
2026-10-16 22:46:54 -07:00
}
2026-10-17 08:10:55 -07:00
func startSuggestion ( _ projectId : String , _ title : String , brief : String , _ overlays : Overlays ) {
let key = " \( projectId ) \n \( title ) "
2026-10-17 10:54:06 -07:00
if todoBusy [ key ] != nil {
2026-10-17 08:10:55 -07:00
return
}
2026-10-17 10:54:06 -07:00
todoBusy [ key ] = "start"
2026-10-17 08:10:55 -07:00
Task {
do {
2026-10-18 00:57:51 -07:00
_ = try await api . client . createTask ( path : . init ( projectId : projectId ), body : . json (. init ( title : title , prompt : brief , model : defaultModel ))). created
2026-10-17 08:10:55 -07:00
} catch {
overlays . showAlert ( errorText ( error ))
}
2026-10-17 10:54:06 -07:00
todoBusy [ key ] = nil
2026-10-17 08:10:55 -07:00
}
}
2026-10-17 10:54:06 -07:00
func discussSuggestion ( _ projectId : String , _ title : String , brief : String , _ overlays : Overlays ) {
let key = " \( projectId ) \n \( title ) "
if todoBusy [ key ] != nil {
return
}
todoBusy [ key ] = "discuss"
2026-10-17 08:10:55 -07:00
let lead = currentLead ()
let live = lead != nil && lead !. state != . completed
Task {
do {
2026-10-18 00:57:51 -07:00
_ = try await api . client . messageLead ( body : . json (. init ( text : "Let's discuss your suggestion \" \( title ) \" before starting it: \n\n \( brief ) " , model : live ? nil : defaultModel ))). accepted
2026-10-17 08:10:55 -07:00
} catch {
overlays . showAlert ( errorText ( error ))
}
2026-10-17 10:54:06 -07:00
todoBusy [ key ] = nil
2026-10-17 08:10:55 -07:00
}
}
2026-10-16 22:46:54 -07:00
var transcript : [ ByreEvent ] {
selectedFeed ?. events ?? []
}
var selectedFeed : Feed ? {
selectedTaskId . flatMap { taskFeeds [ $0 ] }
}
func activeTasks () -> [( project : Project , task : ByreTask )] {
projects . flatMap { project in
( tasks [ project . id ] ?? []). filter { $0 . status == . active }. map { ( project , $0 ) }
}
}
func findTask ( _ id : String ) -> ByreTask ? {
for list in tasks . values {
if let task = list . first ( where : { $0 . id == id }) {
return task
}
}
return nil
}
func currentSession ( _ taskId : String ) -> AgentSession ? {
2026-10-18 08:48:57 -07:00
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 }
2026-10-16 22:46:54 -07:00
}
func taskState ( _ task : ByreTask ) -> TaskState {
2026-10-18 15:06:06 -07:00
if inbox . contains ( where : { $0 . taskId == task . id }) {
return TaskState ( kind : . answer , label : "Needs your answer" , title : "The agent asked a question" )
2026-10-16 22:46:54 -07:00
}
if task . merging {
return TaskState ( kind : . merging , label : "Merging" , title : "The merge turn is running" )
}
if task . mergingIntoParent {
return TaskState ( kind : . merging , label : "Merging" , title : "Waiting for the parent task's agent to merge it" )
}
if task . mergeQueuedAt != nil {
return TaskState ( kind : . held , label : "Merge held" , title : "Waiting for the merge in progress in this project to finish" )
}
if task . mergeAfterReview {
return TaskState ( kind : . held , label : "Merge after review" , title : "Merges when the running review finishes with no findings" )
}
if let working = workingTitles [ task . state ] {
2026-10-18 23:13:59 -07:00
return TaskState ( kind : . working , label : working . label , title : working . title )
2026-10-16 22:46:54 -07:00
}
2026-10-19 17:53:54 -07:00
switch task . ready {
case . merge :
return TaskState ( kind : . ready , label : "Ready to merge" , title : "The agent says the task is ready to merge" )
case . discard :
return TaskState ( kind : . ready , label : "Ready to discard" , title : "The agent suggests discarding the task" )
case nil :
break
}
2026-10-16 22:46:54 -07:00
return TaskState ( kind : . idle , label : "Idle" , title : "Waiting for a message" )
}
2026-10-19 17:53:54 -07:00
func taskRank ( _ task : ByreTask ) -> Int {
if task . discarding {
return 4
}
switch taskState ( task ). kind {
case . answer : return 0
case . ready : return 1
case . idle : return 2
case . working : return 3
case . merging , . held : return 4
}
}
func byRank ( _ tasks : [ ByreTask ]) -> [ ByreTask ] {
tasks . enumerated ()
. sorted { a , b in
let ra = taskRank ( a . element ), rb = taskRank ( b . element )
if ra != rb {
return ra < rb
}
return a . offset < b . offset
}
. map ( \ . element )
}
2026-10-16 22:46:54 -07:00
func currentLead () -> AgentSession ? {
leadSessions . last ( where : { $0 . state != . completed }) ?? leadSessions . last
}
func snapshots () -> [ Snapshot ] {
transcript . filter { $0 . kind == "snapshot.created" }. map { try ! decodePayload ( $0 . payload , as : Snapshot . self ) }
}
func pinSnapshot ( _ n : Int ?) {
pinned = n
}
2026-10-19 09:14:21 -07:00
func jumpTo ( _ taskId : String , _ target : JumpTarget ) {
jumps += 1
jump = Jump ( taskId : taskId , target : target , n : jumps )
focusTask ( taskId , . transcript )
2026-10-18 15:06:06 -07:00
}
2026-10-19 09:14:21 -07:00
func showQuestion ( _ taskId : String ) {
guard let item = inbox . filter ({ $0 . taskId == taskId }). min ( by : { $0 . createdAt < $1 . createdAt }) else {
focusTask ( taskId , . transcript )
return
2026-10-18 15:06:06 -07:00
}
2026-10-19 09:14:21 -07:00
if let member = sessions [ taskId ]?. first ( where : { $0 . id == item . agentSessionId })?. member , ! member . isEmpty {
pickedMembers [ taskId ] = member
2026-10-18 15:06:06 -07:00
}
2026-10-19 09:14:21 -07:00
jumpTo ( taskId , . question ( item . id ))
2026-10-18 15:06:06 -07:00
}
2026-10-18 00:57:51 -07:00
var defaultModel : String {
me !. defaultModel
}
2026-10-16 22:46:54 -07:00
func loadMe () async throws {
me = try await api . client . getMe (). ok . body . json
}
func loadProjects () async throws {
projects = try await api . client . listProjects (). ok . body . json
try await each ( projects . map ( \ . id )) { id in
try await self . loadTasks ( id )
}
}
private func claimRequest ( _ key : String ) -> () -> Bool {
let seq = ( requestSeqs [ key ] ?? 0 ) + 1
requestSeqs [ key ] = seq
return { [ weak self ] in
self ?. requestSeqs [ key ] == seq
}
}
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 () {
return
}
tasks [ projectId ] = list
for t in list where t . status == . active {
_ = taskFeed ( t . id )
}
dropClosedFeeds ()
2026-10-16 23:30:57 -07:00
drafts ?. prune ( self )
2026-10-16 22:46:54 -07:00
try await each ( list . filter { $0 . status == . active }. map ( \ . id )) { id in
try await self . loadSessions ( id )
}
}
func loadSessions ( _ taskId : String ) async throws {
let latest = claimRequest ( "sessions: \( taskId ) " )
let list = try await api . client . listSessions ( path : . init ( taskId : taskId )). ok . body . json
if ! latest () {
return
}
sessions [ taskId ] = list
}
2026-10-18 07:30:00 -07:00
func messageTask ( _ taskId : String , _ text : String ) async throws {
let sessions = try await api . client . listSessions ( path : . init ( taskId : taskId )). ok . body . json
if let live = sessions . last ( where : { $0 . state != . completed }) {
_ = try await api . client . sendMessage ( path : . init ( sessionId : live . id ), body : . json (. init ( text : text ))). accepted
} else {
_ = try await api . client . createSession ( path : . init ( taskId : taskId ), body : . json (. init ( prompt : text ))). created
}
}
2026-10-16 22:46:54 -07:00
func loadInbox () async throws {
let latest = claimRequest ( "inbox" )
let items = try await api . client . listInbox (). ok . body . json
if ! latest () {
return
}
inbox = items
2026-10-16 23:30:57 -07:00
drafts ?. prune ( self )
2026-10-16 22:46:54 -07:00
}
func loadLeadSessions () async throws {
leadSessions = try await api . client . listLeadSessions (). ok . body . json
}
func loadFocus () async throws {
let latest = claimRequest ( "focus" )
let f = try await api . client . getFocus (). ok . body . json
if ! latest () {
return
}
applyFocus ( f )
}
func applyFocus ( _ f : Focus ) {
focus = f
if f . taskId != selectedTaskId {
selectTask ( f . taskId )
}
}
func focusedProject () -> Project ? {
projects . first { $0 . id == focus . projectId }
}
func setFocus ( taskId : String ?? = . none , projectId : String ?? = . none , tab : TaskTab ? = nil ) async throws {
let task = taskId ?? focus . taskId
let project = task != nil ? findTask ( task !)?. projectId : ( projectId ?? focus . projectId )
2026-10-19 08:56:37 -07:00
var next = Focus ( tab : tab ?? focus . tab )
2026-10-16 22:46:54 -07:00
if let task , ! task . isEmpty {
next . taskId = task
}
if let project , ! project . isEmpty {
next . projectId = project
}
let latest = claimRequest ( "focus" )
applyFocus ( next )
let f = try await api . client . setFocus ( body : . json ( next )). ok . body . json
if ! latest () {
return
}
applyFocus ( f )
}
func focusTask ( _ taskId : String , _ tab : TaskTab = . transcript ) {
spawn ( "set focus" ) {
try await self . setFocus ( taskId : taskId , tab : tab )
}
}
func focusProject ( _ projectId : String ) {
spawn ( "set focus" ) {
try await self . setFocus ( taskId : . some ( nil ), projectId : projectId , tab : . project )
}
}
func focusTab ( _ tab : TaskTab ) {
spawn ( "set focus" ) {
try await self . setFocus ( tab : tab )
}
}
2026-10-17 10:54:06 -07:00
func route ( _ hash : String ) {
let m = routeHash . exec ( hash )
let kind = m ?[ 1 ] ?? nil
let id = m ?[ 2 ] ?? nil
let name = m ?[ 3 ] ?? nil
let tab = taskTabs . first { $0 . rawValue == name }
if kind == "task" , let id {
if id != focus . taskId || ( tab != nil && tab != focus . tab ) {
spawn ( "set focus" ) {
try await self . setFocus ( taskId : id , tab : tab )
}
}
return
}
let projectId = kind == "project" ? id : nil
if focus . taskId != nil || projectId != focus . projectId {
spawn ( "set focus" ) {
try await self . setFocus ( taskId : . some ( nil ), projectId : . some ( projectId ))
}
}
}
2026-10-16 22:46:54 -07:00
private func loadHistory ( _ taskId : String , _ feed : Feed ) async throws {
var got : [ ByreEvent ] = []
while true {
let page = try await api . client . listTaskEvents ( path : . init ( taskId : taskId ), query : . init ( after : got . last ?. seq ?? 0 , limit : historyPage )). ok . body . json
got += page
if page . count < historyPage {
break
}
}
feed . load ( got )
}
private func taskFeed ( _ taskId : String ) -> Feed {
let feed = taskFeeds [ taskId ] ?? Feed ( shown : { [ weak self ] in
self ?. selectedTaskId == taskId
})
taskFeeds [ taskId ] = feed
let key = ObjectIdentifier ( feed )
if feed . ready || historyLoads . contains ( key ) {
return feed
}
historyLoads . insert ( key )
Task {
do {
try await loadHistory ( taskId , feed )
} catch {
log . error ( "load task history \( taskId , privacy : . public ) : \( errorText ( error ) , privacy : . public ) " )
}
historyLoads . remove ( key )
}
return feed
}
private func dropClosedFeeds () {
for taskId in taskFeeds . keys {
if taskId != selectedTaskId , let task = findTask ( taskId ), task . status != . active {
taskFeeds [ taskId ] = nil
}
}
}
func transcriptLoading () -> Bool {
selectedTaskId != nil && selectedFeed ?. ready != true
}
func leadLoading () -> Bool {
! leadFeed . ready
}
func lastVisits () -> [ String : Double ] {
2026-10-17 10:13:26 -07:00
fetchRecords ( FetchDescriptor < VisitRecord >()). reduce ( into : [:]) { visits , v in
visits [ v . taskId ] = max ( visits [ v . taskId ] ?? 0 , v . at )
}
2026-10-16 22:46:54 -07:00
}
private func markVisited ( _ taskId : String ?) {
guard let taskId else {
return
}
2026-10-17 10:13:26 -07:00
let at = Date (). timeIntervalSince1970 * 1000
let records = fetchRecords ( FetchDescriptor < VisitRecord >( predicate : # Predicate { $0 . taskId == taskId }))
if records . isEmpty {
persistence . insert ( VisitRecord ( taskId : taskId , at : at ))
} else {
records [ 0 ]. at = at
for record in records . dropFirst () {
persistence . delete ( record )
}
}
saveRecords ()
2026-10-16 22:46:54 -07:00
}
func loadSummaryReads () async throws {
summaryReads = try await api . client . getSummaryReads (). ok . body . json . additionalProperties
}
func unreadSummary ( _ task : ByreTask ) -> Bool {
task . workflow == nil && task . summarySeq > ( summaryReads [ task . id ] ?? 0 )
}
2026-10-19 09:06:08 -07:00
var unreadSummaries : Int {
tasks . values . joined (). filter { $0 . status == . active && unreadSummary ( $0 ) }. count
}
2026-10-16 22:46:54 -07:00
private func noteSummaryRead ( _ taskId : String , _ seq : Int64 ) -> Bool {
if ( summaryReads [ taskId ] ?? 0 ) >= seq {
return false
}
summaryReads [ taskId ] = seq
return true
}
func markSummaryRead ( _ taskId : String , _ seq : Int64 ) {
if ! noteSummaryRead ( taskId , seq ) {
return
}
spawn ( "mark summary read" ) {
_ = try await self . api . client . markSummaryRead ( path : . init ( taskId : taskId ), body : . json (. init ( seq : seq ))). noContent
}
}
func selectTask ( _ taskId : String ?) {
markVisited ( selectedTaskId )
markVisited ( taskId )
selectedTaskId = taskId
if let taskId {
_ = taskFeed ( taskId )
}
pinned = nil
dropClosedFeeds ()
}
private func fetchLeadTail () async -> EventPage ? {
while ! stopped {
do {
return try await api . client . listLeadEvents ( query : . init ( turns : leadPage )). ok . body . json
} catch {
log . error ( "load lead history: \( errorText ( error ) , privacy : . public ) " )
try ? await Task . sleep ( for : . seconds ( 2 ))
}
}
return nil
}
func startLeadStream () async {
guard let page = await fetchLeadTail (), ! stopped else {
return
}
leadMore = page . more
leadStream = EventStream ( api : api , after : page . events . last ?. seq ?? 0 , lead : true , onEvent : { [ weak self ] ev in
self ?. leadFeed . push ( ev )
}, onState : { _ in }, onReady : { [ weak self ] in
self ?. leadFeed . load ( page . events )
})
}
2026-10-19 09:51:42 -07:00
func startGlobalStream ( after : Int64 , reconnected : @ escaping () -> Void ) {
2026-10-16 22:46:54 -07:00
globalStream = EventStream ( api : api , after : after , lead : false , onEvent : { [ weak self ] ev in
self ?. handleEvent ( ev )
}, onState : { [ weak self ] up in
self ?. connected = up
2026-10-16 23:30:57 -07:00
if up {
self ?. paneFocus . reconnected ()
2026-10-19 09:51:42 -07:00
reconnected ()
2026-10-16 23:30:57 -07:00
}
2026-10-16 22:46:54 -07:00
}, onReady : {})
}
func leadHasOlder () -> Bool {
leadFeed . ready && leadMore
}
func loadOlderLead () async throws -> [ ByreEvent ] {
guard let first = leadFeed . events . first else {
return []
}
let page = try await api . client . listLeadEvents ( query : . init ( before : first . seq , turns : leadPage )). ok . body . json
leadMore = page . more
leadFeed . prepend ( page . events )
return page . events
}
func handleEvent ( _ ev : ByreEvent ) {
if let taskId = ev . taskId {
taskFeeds [ taskId ]?. push ( ev )
}
if ev . seq == 0 {
events . send ( ev )
return
}
switch ev . kind {
case "user.updated" :
if ev . payload . value [ "login" ] as ? String == me ?. login {
spawn ( "load me" ) {
try await self . loadMe ()
}
}
case "project.created" :
spawn ( "load projects" ) {
try await self . loadProjects ()
}
2026-10-19 17:53:54 -07:00
case "task.created" , "task.merge_title" , "task.merge_queued" , "task.merge_unqueued" , "task.merge_after_review" , "task.merge_after_review_ended" , "task.merge_after_review_cancelled" , "task.parent_merge_started" , "task.parent_merge_stopped" , "task.merge_started" , "task.merge_ended" , "task.discard_started" , "task.merged" , "task.discarded" , "task.state" , "task.ready" , "task.discard_suggested" , "snapshot.created" :
2026-10-19 19:35:23 -07:00
if let taskId = ev . taskId , [ "task.merged" , "task.discarded" ]. contains ( ev . kind ) || ( ev . kind == "snapshot.created" && str ( ev . payload . value , "trigger" ) != "answer" ) {
clearTask ( self , taskId )
}
notifyTask ( self , ev )
2026-10-16 22:46:54 -07:00
if let projectId = ev . projectId {
spawn ( "load tasks" ) {
try await self . loadTasks ( projectId )
}
}
2026-10-17 08:57:26 -07:00
case "session.created" , "session.state" , "session.subagents" , "session.model" :
2026-10-16 22:46:54 -07:00
if ev . owner != nil {
spawn ( "load lead sessions" ) {
try await self . loadLeadSessions ()
}
} else if let taskId = ev . taskId {
spawn ( "load sessions" ) {
try await self . loadSessions ( taskId )
}
}
2026-10-17 08:24:22 -07:00
case "inbox.created" :
2026-10-19 19:35:23 -07:00
if let taskId = ev . taskId {
clearTask ( self , taskId )
}
2026-10-17 08:24:22 -07:00
notifyInbox ( self , try ! decodePayload ( ev . payload , as : InboxItem . self ))
spawn ( "load inbox" ) {
try await self . loadInbox ()
}
2026-10-19 17:53:54 -07:00
if let projectId = ev . projectId {
spawn ( "load tasks" ) {
try await self . loadTasks ( projectId )
}
}
2026-10-17 08:24:22 -07:00
case "inbox.updated" :
2026-10-17 15:41:54 -07:00
clearInbox ( try ! decodePayload ( ev . payload , as : InboxItem . self ))
2026-10-16 22:46:54 -07:00
spawn ( "load inbox" ) {
try await self . loadInbox ()
}
2026-10-18 15:06:06 -07:00
case "session.error" , "session.rate_limited" :
notifyError ( self , ev )
2026-10-19 19:35:23 -07:00
case "task.merge_stopped" , "task.merge_after_review_dropped" , "task.workflow_finished" :
notifyTask ( self , ev )
2026-10-16 22:46:54 -07:00
case "turn.ended" :
2026-10-17 08:24:22 -07:00
notifyTurn ( self , ev )
2026-10-16 22:46:54 -07:00
if ev . payload . value [ "summary" ] as ? Bool == true , let taskId = ev . taskId {
updateTask ( taskId ) { task in
if ev . seq > task . summarySeq {
task . summarySeq = ev . seq
}
}
}
case "summary.read" :
if let taskId = ev . payload . value [ "task_id" ] as ? String , let seq = ev . payload . value [ "seq" ] as ? Int {
_ = noteSummaryRead ( taskId , Int64 ( seq ))
2026-10-17 15:41:54 -07:00
clearTurns ( taskId , through : Int64 ( seq ))
2026-10-16 22:46:54 -07:00
}
case "focus.updated" :
if ev . actor != me ?. login {
_ = claimRequest ( "focus" )
applyFocus ( try ! decodePayload ( ev . payload , as : Focus . self ))
}
default :
break
}
events . send ( ev )
}
private func updateTask ( _ id : String , _ change : ( inout ByreTask ) -> Void ) {
for ( projectId , list ) in tasks {
if let i = list . firstIndex ( where : { $0 . id == id }) {
change (& tasks [ projectId ] ! [ i ])
}
}
}
func spawn ( _ what : String , _ op : @ escaping @ MainActor () async throws -> Void ) {
Task {
do {
try await op ()
} catch {
log . error ( " \( what , privacy : . public ) : \( errorText ( error ) , privacy : . public ) " )
}
}
}
private func each ( _ ids : [ String ], _ op : @ escaping @ Sendable @ MainActor ( String ) async throws -> Void ) async throws {
try await withThrowingTaskGroup ( of : Void . self ) { group in
for id in ids {
group . addTask {
try await op ( id )
}
}
try await group . waitForAll ()
}
}
}