feat(backend): add opencode ACP (Agent Client Protocol) client
Adds a minimal but real ACP stack for the opencode process: - pkg/config: process.args default ["acp"] (opencodeCommand still "opencode") - internal/process: NewManager(command, args) — exec.Command uses args - internal/acp (new): NDJSON transport + JSON-RPC client over the existing process stdio. Implements initialize / session/new / session/prompt / session/cancel. Serves fs/read_text_file and fs/write_text_file from the workspace's fs.FileSystem. terminal/* requests get MethodNotFound. - internal/service/acp_service: per-workspace Client + mutex; starts the process on first prompt; transparently re-init on restart. - internal/api/acp_handler: GET /acp/status, GET /acp/history, POST /acp/prompt, POST /acp/cancel. - internal/model/acp: API DTOs. - internal/acp/client_test: NDJSON split-lines, request/response correlation, notification dispatch, agent-initiated request handling (fs + terminal). Existing process WS endpoint and Shell subsystem are unchanged. Conversation: 019f354c-a51b-7ec3-83ad-c647e9b50b19
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package acp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Message is one entry in the conversation history.
|
||||
type Message struct {
|
||||
Role string `json:"role"`
|
||||
Text string `json:"text"`
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
// History accumulates prompt/response messages in memory for a session.
|
||||
type History struct {
|
||||
mu sync.RWMutex
|
||||
messages []Message
|
||||
}
|
||||
|
||||
// NewHistory creates an empty History.
|
||||
func NewHistory() *History {
|
||||
return &History{}
|
||||
}
|
||||
|
||||
// Add appends a message with the current time.
|
||||
func (h *History) Add(role, text string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.messages = append(h.messages, Message{Role: role, Text: text, Time: time.Now()})
|
||||
}
|
||||
|
||||
// List returns a copy of all messages, oldest first.
|
||||
func (h *History) List() []Message {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
out := make([]Message, len(h.messages))
|
||||
copy(out, h.messages)
|
||||
return out
|
||||
}
|
||||
|
||||
// Len returns the number of stored messages.
|
||||
func (h *History) Len() int {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return len(h.messages)
|
||||
}
|
||||
|
||||
// Since returns messages starting at index idx.
|
||||
func (h *History) Since(idx int) []Message {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if idx < 0 {
|
||||
idx = 0
|
||||
}
|
||||
if idx > len(h.messages) {
|
||||
idx = len(h.messages)
|
||||
}
|
||||
out := make([]Message, len(h.messages)-idx)
|
||||
copy(out, h.messages[idx:])
|
||||
return out
|
||||
}
|
||||
|
||||
// Clear removes all stored messages.
|
||||
func (h *History) Clear() {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.messages = h.messages[:0]
|
||||
}
|
||||
Reference in New Issue
Block a user