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

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

79 lines
2.7 KiB
Go

package app
import (
"encoding/json"
"strings"
"testing"
"infinite-canvas/backend/internal/model"
)
// 自研(2026-09-15):部署改了策略或能力集后,旧轮次的 policy 快照与当前二进制不一致,
// validateCloudAgentPolicySnapshot 会拒绝续跑。旧行为把用户卡死在「请新建一轮消息」,
// 每个对话都得手动重建;新行为继承历史自动接新轮,用户无感。
func TestCloudAgentContinuesAfterContractChange(t *testing.T) {
s, db, _, _ := creationTestService(t)
canvas := model.CanvasProject{ID: "agent-canvas", UserID: "user", Title: "test", PayloadJSON: `{"nodes":[]}`}
if err := db.Create(&canvas).Error; err != nil {
t.Fatal(err)
}
root, err := s.CreateCloudAgentRun("user", agentTestRequest(), "")
if err != nil {
t.Fatal(err)
}
// 让第一轮结束。
if err := db.Model(&model.Task{}).Where("id = ?", root.ID).
Updates(map[string]any{"status": model.TaskStatusFailed, "error": "mock failure"}).Error; err != nil {
t.Fatal(err)
}
// 在该轮快照里制造一次「合同变更」:把能力集哈希改掉。
execution, err := s.repo.CloudAgent("user", root.ID)
if err != nil {
t.Fatal(err)
}
current := cloudAgentCapabilitySetHash()
if !strings.Contains(execution.StateJSON, current) {
t.Fatalf("fixture does not contain the capability set hash %s", current)
}
stale := strings.Replace(execution.StateJSON, current, strings.Repeat("0", len(current)), 1)
if err := db.Model(&model.CloudAgentExecution{}).Where("id = ?", root.ID).
Update("state_json", stale).Error; err != nil {
t.Fatal(err)
}
req := agentTestRequest()
req.Prompt = "继续刚才的任务"
req.IdempotencyKey = "contract-change-continuation"
child, err := s.CreateCloudAgentRun("user", req, root.ID)
if err != nil {
t.Fatalf("contract change must not block the conversation: %v", err)
}
if child == nil || child.ID == "" || child.ID == root.ID {
t.Fatalf("expected a new turn, got %#v", child)
}
// 历史必须继承,否则用户等于从零开始。
task, err := s.repo.TaskForUser("user", child.ID)
if err != nil {
t.Fatal(err)
}
var input struct {
TextHistory []providerTextMessage `json:"textHistory"`
}
if err := json.Unmarshal([]byte(task.InputJSON), &input); err != nil {
t.Fatal(err)
}
if len(input.TextHistory) == 0 {
t.Fatal("continuation must inherit conversation history")
}
// 无法续跑的旧轮应被归档为终态,避免调度器反复重试。
archived, err := s.repo.CloudAgent("user", root.ID)
if err != nil {
t.Fatal(err)
}
if archived.Status == "running" || archived.Status == "queued" {
t.Fatalf("superseded run must be archived, still %q", archived.Status)
}
}