20f5f43fb3
- Use acp-go-sdk for all ACP communication in agentctl - Mark agent as completed when Prompt() returns successfully - Update task state to COMPLETED when agent completes - Update task state to FAILED when agent fails and retries exhausted - Enhanced E2E test to verify agent actually creates files - E2E test now validates full flow: task creation -> agent work -> file creation -> task completion
132 lines
2.6 KiB
Go
132 lines
2.6 KiB
Go
package process
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// OutputLine represents a line of output from the agent
|
|
type OutputLine struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Stream string `json:"stream"` // "stdout" or "stderr"
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
// Subscriber is a channel that receives output lines
|
|
type Subscriber chan OutputLine
|
|
|
|
// OutputBuffer is a ring buffer for agent output that supports streaming
|
|
type OutputBuffer struct {
|
|
lines []OutputLine
|
|
size int
|
|
head int
|
|
count int
|
|
mu sync.RWMutex
|
|
|
|
// Subscribers for real-time streaming
|
|
subscribers map[Subscriber]struct{}
|
|
subMu sync.RWMutex
|
|
}
|
|
|
|
// NewOutputBuffer creates a new output buffer with the given capacity
|
|
func NewOutputBuffer(size int) *OutputBuffer {
|
|
return &OutputBuffer{
|
|
lines: make([]OutputLine, size),
|
|
size: size,
|
|
subscribers: make(map[Subscriber]struct{}),
|
|
}
|
|
}
|
|
|
|
// Add adds a new line to the buffer and notifies subscribers
|
|
func (b *OutputBuffer) Add(line OutputLine) {
|
|
b.mu.Lock()
|
|
|
|
// Add to ring buffer
|
|
idx := (b.head + b.count) % b.size
|
|
if b.count < b.size {
|
|
b.count++
|
|
} else {
|
|
b.head = (b.head + 1) % b.size
|
|
}
|
|
b.lines[idx] = line
|
|
|
|
b.mu.Unlock()
|
|
|
|
// Notify subscribers (non-blocking)
|
|
b.subMu.RLock()
|
|
for sub := range b.subscribers {
|
|
select {
|
|
case sub <- line:
|
|
default:
|
|
// Subscriber is slow, skip
|
|
}
|
|
}
|
|
b.subMu.RUnlock()
|
|
}
|
|
|
|
// GetAll returns all lines in the buffer (oldest first)
|
|
func (b *OutputBuffer) GetAll() []OutputLine {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
|
|
result := make([]OutputLine, b.count)
|
|
for i := 0; i < b.count; i++ {
|
|
idx := (b.head + i) % b.size
|
|
result[i] = b.lines[idx]
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetLast returns the last n lines from the buffer
|
|
func (b *OutputBuffer) GetLast(n int) []OutputLine {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
|
|
if n > b.count {
|
|
n = b.count
|
|
}
|
|
|
|
result := make([]OutputLine, n)
|
|
start := b.count - n
|
|
for i := 0; i < n; i++ {
|
|
idx := (b.head + start + i) % b.size
|
|
result[i] = b.lines[idx]
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Subscribe creates a new subscriber that receives output lines in real-time
|
|
func (b *OutputBuffer) Subscribe() Subscriber {
|
|
sub := make(Subscriber, 100)
|
|
|
|
b.subMu.Lock()
|
|
b.subscribers[sub] = struct{}{}
|
|
b.subMu.Unlock()
|
|
|
|
return sub
|
|
}
|
|
|
|
// Unsubscribe removes a subscriber
|
|
func (b *OutputBuffer) Unsubscribe(sub Subscriber) {
|
|
b.subMu.Lock()
|
|
delete(b.subscribers, sub)
|
|
b.subMu.Unlock()
|
|
close(sub)
|
|
}
|
|
|
|
// Count returns the number of lines in the buffer
|
|
func (b *OutputBuffer) Count() int {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
return b.count
|
|
}
|
|
|
|
// Clear clears the buffer
|
|
func (b *OutputBuffer) Clear() {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.head = 0
|
|
b.count = 0
|
|
}
|
|
|