Files
kandev/backend/internal/agent/streaming/manager.go
T
jcfs ba919305c2 Initial commit: Kandev with SQLite persistence and agent integration
Features:
- Unified Go binary running Task Service, Agent Manager, and Orchestrator
- SQLite persistence for boards, columns, and tasks
- Docker-based agent execution with ACP message streaming
- Augment Agent integration with session resumption support
- RESTful API for all operations (port 8080)
- In-memory event bus for service communication

Architecture:
- cmd/kandev: Single entry point for all services
- internal/task: Task, board, and column management
- internal/agent: Docker container lifecycle and ACP streaming
- internal/orchestrator: Agent coordination and monitoring
- internal/events: In-memory pub/sub event bus

Documentation:
- README.md: Project overview and quick start
- docs/ARCHITECTURE.md: System architecture (current + planned)
- docs/GETTING_STARTED.md: Development setup guide
- docs/IMPLEMENTATION_PLAN.md: Original microservices plan
- backend/NEXT_STEPS.md: Development roadmap
2026-01-10 01:30:24 +00:00

146 lines
3.5 KiB
Go

package streaming
import (
"context"
"fmt"
"io"
"sync"
"go.uber.org/zap"
"github.com/kandev/kandev/internal/common/logger"
"github.com/kandev/kandev/internal/events/bus"
)
// Manager manages ACP stream readers for multiple agent instances
type Manager struct {
eventBus bus.EventBus
logger *logger.Logger
readers map[string]*StreamReader // by instanceID
mu sync.RWMutex
}
// NewManager creates a new streaming manager
func NewManager(eventBus bus.EventBus, log *logger.Logger) *Manager {
return &Manager{
eventBus: eventBus,
logger: log.WithFields(zap.String("component", "streaming-manager")),
readers: make(map[string]*StreamReader),
}
}
// StartStreaming starts streaming for an agent instance
func (m *Manager) StartStreaming(ctx context.Context, instanceID, taskID string, reader io.ReadCloser) error {
m.mu.Lock()
defer m.mu.Unlock()
// Check if already streaming for this instance
if existing, exists := m.readers[instanceID]; exists {
if existing.IsRunning() {
return fmt.Errorf("streaming already active for instance %s", instanceID)
}
// Previous reader exists but not running, replace it
}
// Create new stream reader
streamReader := NewStreamReader(instanceID, taskID, m.eventBus, m.logger)
m.readers[instanceID] = streamReader
// Start reading
if err := streamReader.Start(ctx, reader); err != nil {
delete(m.readers, instanceID)
return fmt.Errorf("failed to start streaming: %w", err)
}
m.logger.Info("started streaming for instance",
zap.String("instance_id", instanceID),
zap.String("task_id", taskID))
return nil
}
// StopStreaming stops streaming for an agent instance
func (m *Manager) StopStreaming(instanceID string) error {
m.mu.Lock()
reader, exists := m.readers[instanceID]
if !exists {
m.mu.Unlock()
return nil // Not streaming, no error
}
delete(m.readers, instanceID)
m.mu.Unlock()
if err := reader.Stop(); err != nil {
m.logger.Warn("error stopping stream reader",
zap.String("instance_id", instanceID),
zap.Error(err))
return err
}
m.logger.Info("stopped streaming for instance",
zap.String("instance_id", instanceID))
return nil
}
// StopAll stops all stream readers
func (m *Manager) StopAll() {
m.mu.Lock()
readers := make([]*StreamReader, 0, len(m.readers))
instanceIDs := make([]string, 0, len(m.readers))
for id, reader := range m.readers {
readers = append(readers, reader)
instanceIDs = append(instanceIDs, id)
}
m.readers = make(map[string]*StreamReader)
m.mu.Unlock()
// Stop all readers outside the lock
for i, reader := range readers {
if err := reader.Stop(); err != nil {
m.logger.Warn("error stopping stream reader during shutdown",
zap.String("instance_id", instanceIDs[i]),
zap.Error(err))
}
}
m.logger.Info("stopped all stream readers", zap.Int("count", len(readers)))
}
// IsStreaming returns true if streaming is active for an instance
func (m *Manager) IsStreaming(instanceID string) bool {
m.mu.RLock()
defer m.mu.RUnlock()
reader, exists := m.readers[instanceID]
if !exists {
return false
}
return reader.IsRunning()
}
// GetActiveCount returns the number of active stream readers
func (m *Manager) GetActiveCount() int {
m.mu.RLock()
defer m.mu.RUnlock()
count := 0
for _, reader := range m.readers {
if reader.IsRunning() {
count++
}
}
return count
}
// GetReader returns the stream reader for an instance, if it exists
func (m *Manager) GetReader(instanceID string) (*StreamReader, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
reader, exists := m.readers[instanceID]
return reader, exists
}