Files
kandev/apps/backend/internal/agentctl/api/output.go
T
jcfs 20f5f43fb3 feat: complete acp-go-sdk integration with task state updates
- 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
2026-01-10 16:46:35 +00:00

103 lines
2.3 KiB
Go

package api
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/kandev/kandev/internal/agentctl/process"
"go.uber.org/zap"
)
// OutputResponse is the response for getting output
type OutputResponse struct {
Lines []process.OutputLine `json:"lines"`
Total int `json:"total"`
}
func (s *Server) handleGetOutput(c *gin.Context) {
// Parse optional 'last' query param
lastStr := c.DefaultQuery("last", "100")
last, err := strconv.Atoi(lastStr)
if err != nil || last < 1 {
last = 100
}
lines := s.procMgr.GetOutputBuffer().GetLast(last)
c.JSON(http.StatusOK, OutputResponse{
Lines: lines,
Total: s.procMgr.GetOutputBuffer().Count(),
})
}
// handleOutputStreamWS streams output via WebSocket
func (s *Server) handleOutputStreamWS(c *gin.Context) {
conn, err := s.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
s.logger.Error("WebSocket upgrade failed", zap.Error(err))
return
}
defer conn.Close()
s.logger.Info("Output stream WebSocket connected")
// Check if client wants history first
sendHistory := c.DefaultQuery("history", "false") == "true"
historyCount, _ := strconv.Atoi(c.DefaultQuery("history_count", "100"))
if sendHistory {
lines := s.procMgr.GetOutputBuffer().GetLast(historyCount)
for _, line := range lines {
data, err := json.Marshal(line)
if err != nil {
continue
}
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
s.logger.Debug("WebSocket write error (history)", zap.Error(err))
return
}
}
}
// Subscribe to real-time output
sub := s.procMgr.GetOutputBuffer().Subscribe()
defer s.procMgr.GetOutputBuffer().Unsubscribe(sub)
// Handle WebSocket close
closeCh := make(chan struct{})
go func() {
for {
_, _, err := conn.ReadMessage()
if err != nil {
close(closeCh)
return
}
}
}()
// Stream output to client
for {
select {
case line, ok := <-sub:
if !ok {
return
}
data, err := json.Marshal(line)
if err != nil {
s.logger.Error("failed to marshal output line", zap.Error(err))
continue
}
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
s.logger.Debug("WebSocket write error", zap.Error(err))
return
}
case <-closeCh:
s.logger.Info("Output stream WebSocket closed by client")
return
}
}
}