Files
ttstd-infinite-canvas/backend/internal/app/cloud_agent_contract_continuation_test.go
ddCat 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
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}