agent: add chat log fields and methods

Moved log buffer, plan, mutex, and condvar onto agent.Agent directly.
These were previously on the fs package's AgentLog type. The fs package
will be migrated to use these in the next commit.

New methods: AppendChat, EnsureTrailingNewline, ChatMu, ChatCond,
ChatLog, Plan, SetPlan.
This commit is contained in:
Ollie Agent 2026-08-11 21:41:12 +02:00
parent b01dbdfba8
commit 31d3466b3b
1 changed files with 58 additions and 0 deletions

View File

@ -56,6 +56,13 @@ type Agent struct {
save func() // trigger debounced persistence
flush func() // immediately flush persistence
onStateChange func(agentID, state string) // optional callback for state changes
// Chat log — the rendered output stream, exposed via 9P.
chatMu sync.RWMutex
chatLog []byte
chatVers uint32
chatCond *sync.Cond
plan []byte
}
// Backend returns the active backend from the runtime.
@ -116,6 +123,56 @@ func (ag *Agent) SetName(name string) {
// ID returns the agent's unique identity.
func (ag *Agent) ID() string { return ag.id }
// --- Chat log methods ---
// AppendChat appends data to the chat log and notifies stream readers.
func (ag *Agent) AppendChat(data []byte) {
if len(data) == 0 {
return
}
ag.chatMu.Lock()
ag.chatLog = append(ag.chatLog, data...)
ag.chatVers++
ag.chatMu.Unlock()
ag.chatCond.Broadcast()
}
// EnsureTrailingNewline appends a newline if the log doesn't end with one.
func (ag *Agent) EnsureTrailingNewline() {
ag.chatMu.Lock()
if len(ag.chatLog) > 0 && ag.chatLog[len(ag.chatLog)-1] != '\n' {
ag.chatLog = append(ag.chatLog, '\n')
}
ag.chatVers++
ag.chatMu.Unlock()
}
// ChatMu returns the chat log mutex for external locking (streaming).
func (ag *Agent) ChatMu() *sync.RWMutex { return &ag.chatMu }
// ChatCond returns the condvar for blocking chat readers.
func (ag *Agent) ChatCond() *sync.Cond { return ag.chatCond }
// ChatLog returns the raw chat log bytes (caller must hold ChatMu.RLock).
func (ag *Agent) ChatLog() []byte { return ag.chatLog }
// Plan returns a copy of the plan.
func (ag *Agent) Plan() []byte {
ag.chatMu.RLock()
p := make([]byte, len(ag.plan))
copy(p, ag.plan)
ag.chatMu.RUnlock()
return p
}
// SetPlan replaces the plan.
func (ag *Agent) SetPlan(data []byte) {
ag.chatMu.Lock()
ag.plan = make([]byte, len(data))
copy(ag.plan, data)
ag.chatMu.Unlock()
}
// BackendName returns the name of the active backend.
func (ag *Agent) BackendName() string {
if ag.runtime.Backend == nil {
@ -651,6 +708,7 @@ func NewAgent(cfg AgentParams) *Agent {
state: "idle",
}
ag.signalCh = make(chan struct{})
ag.chatCond = sync.NewCond(ag.chatMu.RLocker())
ag.SetSessionEnv(cfg.SessionID)
return ag
}