Files
Agent-System/internal/server/workspace_admin_handlers.go
weichen f3f23a3018 feat(errcode): 用稳定错误码记录 HTTP 与异步失败
失败响应此前不写日志,request_id 无法对照原因。注册表不覆盖处理器已经给出的 HTTP 状态。
2026-09-28 02:27:16 +00:00

133 lines
3.1 KiB
Go

package server
import (
"context"
"encoding/json"
"net/http"
"time"
"chat-agent-system/internal/config"
"chat-agent-system/internal/workspace"
)
func (s *Server) handleAdminWorkspaces(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireAdmin(w, r); !ok {
return
}
s.serveAdminWorkspaces(w, r)
}
func (s *Server) handleAdminWorkspaceCleanup(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireAdmin(w, r); !ok {
return
}
s.serveAdminWorkspaceCleanup(w, r)
}
func (s *Server) workspaceRegistry() *workspace.FileRegistry {
if s.wsReg != nil {
return s.wsReg
}
if s.cfg == nil || s.cfg.DataDir == "" {
return nil
}
s.wsReg = workspace.NewFileRegistry(s.cfg.DataDir)
return s.wsReg
}
func (s *Server) serveAdminWorkspaces(w http.ResponseWriter, r *http.Request) {
reg := s.workspaceRegistry()
if reg == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "workspace registry unavailable")
return
}
if err := reg.Discover(); err != nil {
writeDomainError(w, r, http.StatusInternalServerError, err)
return
}
list, err := reg.List()
if err != nil {
writeDomainError(w, r, http.StatusInternalServerError, err)
return
}
var bytes int64
for _, rec := range list {
bytes += rec.Bytes
}
writeJSONResponse(w, http.StatusOK, map[string]any{
"workspaces": list,
"stats": map[string]any{
"count": len(list),
"bytes": bytes,
},
})
}
func (s *Server) serveAdminWorkspaceCleanup(w http.ResponseWriter, r *http.Request) {
reg := s.workspaceRegistry()
if reg == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "workspace registry unavailable")
return
}
id := r.PathValue("id")
var body struct {
Frozen *workspace.FrozenManifest `json:"frozen"`
Receipts []workspace.RetentionReceipt `json:"receipts"`
}
if r.Body != nil {
_ = json.NewDecoder(r.Body).Decode(&body)
}
if body.Frozen != nil {
if err := reg.SetRetention(id, *body.Frozen, body.Receipts); err != nil {
writeDomainError(w, r, http.StatusBadRequest, err)
return
}
}
cap := s.wsCap()
if cap == nil && s.cfg != nil {
cap = workspace.NewCapability(s.cfg.DataDir, nil)
}
if err := reg.CleanupOne(r.Context(), id, cap); err != nil {
status := http.StatusConflict
if err == workspace.ErrWorkDirNotFound {
status = http.StatusNotFound
}
writeDomainError(w, r, status, err)
return
}
writeJSONResponse(w, http.StatusOK, map[string]any{"deleted": id})
}
func workspaceRegistryFor(cfg *config.Config) *workspace.FileRegistry {
if cfg == nil || cfg.DataDir == "" {
return nil
}
return workspace.NewFileRegistry(cfg.DataDir)
}
func (s *Server) startWorkspaceGC() {
reg := s.workspaceRegistry()
stopCh, ok := s.wsGCLoop.start()
if reg == nil || s.cfg == nil || !ok {
return
}
ttl := s.cfg.WorkspaceTTL
quota := s.cfg.WorkspaceQuotaBytes
go func() {
ticker := time.NewTicker(time.Hour)
defer ticker.Stop()
for {
select {
case <-stopCh:
return
case <-ticker.C:
cap := s.wsCap()
if cap == nil {
cap = workspace.NewCapability(s.cfg.DataDir, nil)
}
_, _ = reg.CollectGarbage(context.Background(), time.Now(), ttl, quota, cap)
}
}
}()
}