Files
kandev/apps/backend/internal/agentctl/server/api/workspace_state.go
T
2026-09-06 11:32:14 +01:00

75 lines
2.8 KiB
Go

package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/kandev/kandev/internal/agentctl/server/process"
"go.uber.org/zap"
)
// SetPollModeRequest is the body for POST /api/v1/workspace/poll-mode.
type SetPollModeRequest struct {
Mode string `json:"mode"`
}
// RefreshWorkspaceRequest is the body for POST /api/v1/workspace/refresh.
type RefreshWorkspaceRequest struct {
Trigger string `json:"trigger,omitempty"`
}
// handleSetPollMode updates the workspace tracker's poll mode based on the
// gateway's view of UI subscription/focus state for sessions in this workspace.
//
// See plan: focus-gated git polling. The gateway computes a workspace-level
// mode (fast if any session focused, slow if any subscribed, paused otherwise)
// and pushes it here so the tracker can throttle expensive git scans.
//
// Multi-repo: the per-repo trackers also need the mode update — otherwise they
// keep their construction-time default (fast) and never throttle, AND they
// miss the focus-triggered immediate scan that handleMonitorModeChange does
// on a transition INTO fast. Fanning out keeps every repo in lockstep.
//
// Snapshot-on-focus: a transition to fast/slow forces a RefreshGitStatus on
// every tracker so a fresh git status reaches subscribers. monitorTick
// otherwise only pushes on detected change; without this, a page opened
// after the agent has been idle would see no events until the user makes
// a file change. Multi-repo amplifies it: per-repo state is a Map and the
// aggregator only renders the repos that have published — missing events
// = missing files in the Changes panel, not just stale data.
func (s *Server) handleSetPollMode(c *gin.Context) {
var req SetPollModeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request: " + err.Error()})
return
}
mode := process.PollMode(req.Mode)
if !mode.IsValid() {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid mode: must be one of fast, slow, paused"})
return
}
s.procMgr.SetWorkspacePollMode(c.Request.Context(), mode)
s.logger.Debug("workspace poll mode updated", zap.String("mode", req.Mode))
c.JSON(http.StatusOK, gin.H{"mode": req.Mode})
}
// handleRefreshWorkspace performs one explicit file and Git scan. Lifecycle
// callers use the turn_complete trigger; browser callers use the default
// manual_refresh trigger, which also retries a tracker paused by denial.
func (s *Server) handleRefreshWorkspace(c *gin.Context) {
var req RefreshWorkspaceRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request: " + err.Error()})
return
}
trigger := req.Trigger
if trigger == "" {
trigger = "manual_refresh"
}
s.procMgr.RefreshWorkspace(c.Request.Context(), trigger)
c.JSON(http.StatusOK, gin.H{"trigger": process.NormalizeWorkspaceTrigger(trigger)})
}