Files
ttstd-infinite-canvas/backend/internal/app/cloud_agent_stuck.go
ddCat 6892732b06 feat(agent): Agent 工作台 - 计划插话记忆、统一模型选择器并修复摘要与视频入库
将计划清单、运行中插话和个人记忆接入云端 Agent;全站模型选择器统一为左右二级菜单。画布摘要按预算截断不再整轮拒绝,首页视频未知宽高可入库。README 补充贡献者 morrow。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 20:21:25 +08:00

81 lines
2.4 KiB
Go

package app
import (
"errors"
"fmt"
"log"
"time"
"infinite-canvas/backend/internal/model"
"infinite-canvas/backend/internal/repository"
)
const cloudAgentStuckAfter = 5 * time.Minute
const cloudAgentConflictLogThreshold = 15
func (s *Service) terminateStuckCloudAgent(run *model.CloudAgentExecution) bool {
if run == nil || run.CleanupPending || (run.Status != "running" && run.Status != "queued") {
return false
}
state, err := cloudAgentDecode(run)
if err != nil {
return false
}
if s.cloudAgentHasLiveWork(run, &state) {
return false
}
last := cloudAgentLastEventAt(&state)
if last.IsZero() || time.Since(last) < cloudAgentStuckAfter {
return false
}
idle := time.Since(last)
if err := s.terminateCloudAgent(run, fmt.Sprintf("运行已 %.0f 分钟没有任何进展(既没有新事件、也没有在跑的任务),已判定为卡住并停止;可以重新发起", idle.Minutes())); err != nil {
if !errors.Is(err, repository.ErrCreationConflict) {
log.Printf("agent stuck cleanup %s: %v", run.ID, err)
}
return false
}
log.Printf("agent scheduler: run=%s 判定卡死并收尾(最后一条事件在 %.0f 分钟前)", run.ID, idle.Minutes())
return true
}
func (s *Service) cloudAgentHasLiveWork(run *model.CloudAgentExecution, state *cloudAgentRuntime) bool {
for _, taskID := range []string{state.ActiveTaskID, state.MediaTaskID, state.StoryboardTaskID} {
if taskID == "" {
continue
}
task, err := s.repo.TaskForUser(run.UserID, taskID)
if err != nil {
return true
}
if task.Status == model.TaskStatusQueued || task.Status == model.TaskStatusRunning {
return true
}
}
return false
}
func cloudAgentLastEventAt(state *cloudAgentRuntime) time.Time {
if state == nil || len(state.Events) == 0 {
return time.Time{}
}
return state.Events[len(state.Events)-1].CreatedAt
}
func (s *Service) noteCloudAgentSchedulerConflict(runID string) {
if s.agentConflictStreak == nil {
s.agentConflictStreak = map[string]int{}
}
s.agentConflictStreak[runID]++
if streak := s.agentConflictStreak[runID]; streak == cloudAgentConflictLogThreshold {
log.Printf("agent scheduler: run=%s 连续 %d 次 CAS 冲突(另有写者持续占用该行),这一轮很可能卡住了", runID, streak)
}
}
func (s *Service) clearCloudAgentSchedulerConflict(runID string) {
if s.agentConflictStreak == nil {
return
}
delete(s.agentConflictStreak, runID)
}