Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Has been cancelled
- OAuth PKCE flow for Max subscription auth (no API key needed) - Claude API client with SSE streaming (Messages API v1) - 16 tool definitions: terminal, SFTP, RDP, session management - Tool dispatch router mapping to existing Wraith services - Conversation manager with SQLite persistence - Terminal output ring buffer for AI context - RDP screenshot encoder (RGBA → JPEG with downscaling) - Wired into Wails app as AIService Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
102 lines
2.3 KiB
Go
102 lines
2.3 KiB
Go
package ai
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// TerminalBuffer is a thread-safe ring buffer that captures terminal output lines.
|
|
// It is written to by SSH read loops and read by the AI tool dispatch for terminal_read.
|
|
type TerminalBuffer struct {
|
|
lines []string
|
|
mu sync.RWMutex
|
|
max int
|
|
partial string // accumulates data that doesn't end with \n
|
|
}
|
|
|
|
// NewTerminalBuffer creates a buffer that retains at most maxLines lines.
|
|
func NewTerminalBuffer(maxLines int) *TerminalBuffer {
|
|
if maxLines <= 0 {
|
|
maxLines = 200
|
|
}
|
|
return &TerminalBuffer{
|
|
lines: make([]string, 0, maxLines),
|
|
max: maxLines,
|
|
}
|
|
}
|
|
|
|
// Write ingests raw terminal output, splitting on newlines and appending complete lines.
|
|
// Partial lines (data without a trailing newline) are accumulated until the next Write.
|
|
func (b *TerminalBuffer) Write(data []byte) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
text := b.partial + string(data)
|
|
b.partial = ""
|
|
|
|
parts := strings.Split(text, "\n")
|
|
|
|
// The last element of Split is always either:
|
|
// - empty string if text ends with \n (discard it)
|
|
// - a partial line if text doesn't end with \n (save as partial)
|
|
last := parts[len(parts)-1]
|
|
parts = parts[:len(parts)-1]
|
|
if last != "" {
|
|
b.partial = last
|
|
}
|
|
|
|
for _, line := range parts {
|
|
b.lines = append(b.lines, line)
|
|
}
|
|
|
|
// Trim to max
|
|
if len(b.lines) > b.max {
|
|
excess := len(b.lines) - b.max
|
|
b.lines = b.lines[excess:]
|
|
}
|
|
}
|
|
|
|
// ReadLast returns the last n lines from the buffer.
|
|
// If fewer than n lines are available, all lines are returned.
|
|
func (b *TerminalBuffer) ReadLast(n int) []string {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
|
|
total := len(b.lines)
|
|
if n > total {
|
|
n = total
|
|
}
|
|
if n <= 0 {
|
|
return []string{}
|
|
}
|
|
|
|
result := make([]string, n)
|
|
copy(result, b.lines[total-n:])
|
|
return result
|
|
}
|
|
|
|
// ReadAll returns all lines currently in the buffer.
|
|
func (b *TerminalBuffer) ReadAll() []string {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
|
|
result := make([]string, len(b.lines))
|
|
copy(result, b.lines)
|
|
return result
|
|
}
|
|
|
|
// Clear removes all lines from the buffer.
|
|
func (b *TerminalBuffer) Clear() {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.lines = b.lines[:0]
|
|
b.partial = ""
|
|
}
|
|
|
|
// Len returns the number of complete lines in the buffer.
|
|
func (b *TerminalBuffer) Len() int {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
return len(b.lines)
|
|
}
|