feat(agent): 云端 Agent - 增加上下文预算、任务事实与终态恢复保障

This commit is contained in:
ddCat committed 2026-09-19 20:43:20 +08:00
1 parent 3bf21d79af
commit b52bc9d5f2
61 files changed
+3512 -309

No files matched your search

@@ -43,6 +43,7 @@ func TestCloudAgentRefreshStepSnapshotHash(t *testing.T) {
if err := db.First(&canvas, "id = ?", "agent-canvas").Error; err != nil {
t.Fatal(err)
}
beforeRaw := canvas.PayloadJSON
doc, err := creationDocument(canvas.PayloadJSON)
if err != nil {
t.Fatal(err)
@@ -56,6 +57,9 @@ func TestCloudAgentRefreshStepSnapshotHash(t *testing.T) {
if latest == base {
t.Fatal("测试前提不成立:画布哈希没变")
}
if err := s.repo.CreateCloudAgentCanvasMutation(&model.CloudAgentCanvasMutation{ID: "mutation-1", RunID: run.ID, UserID: run.UserID, CanvasID: run.CanvasID, StepID: state.Calls[0].ID, Operation: "canvas_apply_ops", BeforeSnapshotHash: base, AfterSnapshotHash: latest, BeforeJSON: beforeRaw, Status: "applied"}); err != nil {
t.Fatal(err)
}
got = s.cloudAgentRefreshStepSnapshotHash(run, &state, state.Calls[1])
var args struct {
@@ -112,6 +116,9 @@ func TestCloudAgentRefreshStepSnapshotHash(t *testing.T) {
if afterBind == latest {
t.Fatal("测试前提不成立:提交第一条后画布哈希没变")
}
if err := s.repo.CreateCloudAgentCanvasMutation(&model.CloudAgentCanvasMutation{ID: "mutation-2", RunID: run.ID, UserID: run.UserID, CanvasID: run.CanvasID, StepID: state.Calls[1].ID, Operation: "generate_media_submit", BeforeSnapshotHash: latest, AfterSnapshotHash: afterBind, BeforeJSON: string(raw), Status: "applied"}); err != nil {
t.Fatal(err)
}
fresh := s.cloudAgentRefreshStepSnapshotHash(run, &state, state.Calls[1])
var freshArgs struct {
SnapshotHash string `json:"snapshotHash"`
@@ -149,3 +156,60 @@ func TestCloudAgentRefreshStepSnapshotHash(t *testing.T) {
t.Fatalf("漏传 snapshotHash 应补当前媒体快照:got %s", truncateRunes(filledArgs.SnapshotHash, 12))
}
}
func TestCloudAgentRefreshStepSnapshotHashRejectsExternalWrites(t *testing.T) {
s, db, a := agentMediaFixture(t)
if sqlDB, err := db.DB(); err == nil {
t.Cleanup(func() { _ = sqlDB.Close() })
}
baseCanvas, err := s.repo.CanvasProjectForUser("user", "agent-canvas")
if err != nil {
t.Fatal(err)
}
baseDoc, err := creationDocument(baseCanvas.PayloadJSON)
if err != nil {
t.Fatal(err)
}
base := cloudAgentCanvasHash(baseDoc)
state := cloudAgentRuntime{Request: agentTestRequest(), Step: 1, CallIndex: 1, StepSnapshotHash: base}
state.Request.CanvasID = "agent-canvas"
state.Calls = []cloudAgentCall{agentMediaCall(a), agentMediaCall(a)}
run := &model.CloudAgentExecution{ID: "run-current", UserID: "user", CanvasID: "agent-canvas"}
// A direct autosave or another tab has no current-run mutation record.
directDoc := cloneCreationDocument(baseDoc)
directDoc["nodes"] = append(creationMaps(directDoc["nodes"]), map[string]any{"id": "external-node", "type": "image"})
directRaw, _ := json.Marshal(directDoc)
if err := db.Model(&model.CanvasProject{}).Where("id = ?", "agent-canvas").Update("payload_json", string(directRaw)).Error; err != nil {
t.Fatal(err)
}
got := s.cloudAgentRefreshStepSnapshotHash(run, &state, state.Calls[1])
if got.Function.Arguments != state.Calls[1].Function.Arguments {
t.Fatal("无记录的外部写入不能被吸收")
}
// A different Agent run's mutation is also a conflict for this run.
if err := db.Model(&model.CanvasProject{}).Where("id = ?", "agent-canvas").Update("payload_json", baseCanvas.PayloadJSON).Error; err != nil {
t.Fatal(err)
}
otherDoc := cloneCreationDocument(baseDoc)
otherDoc["nodes"] = append(creationMaps(otherDoc["nodes"]), map[string]any{"id": "other-run-node", "type": "image"})
otherRaw, _ := json.Marshal(otherDoc)
if err := db.Model(&model.CanvasProject{}).Where("id = ?", "agent-canvas").Update("payload_json", string(otherRaw)).Error; err != nil {
t.Fatal(err)
}
otherHash := cloudAgentCanvasHash(otherDoc)
if err := s.repo.CreateCloudAgentCanvasMutation(&model.CloudAgentCanvasMutation{ID: "mutation-other", RunID: "run-other", UserID: "user", CanvasID: "agent-canvas", StepID: "other-call", Operation: "canvas_apply_ops", BeforeSnapshotHash: base, AfterSnapshotHash: otherHash, Status: "applied"}); err != nil {
t.Fatal(err)
}
got = s.cloudAgentRefreshStepSnapshotHash(run, &state, state.Calls[1])
if got.Function.Arguments != state.Calls[1].Function.Arguments {
t.Fatal("其他 Agent run 的写入不能被当前 run 吸收")
}
}
func cloneCreationDocument(doc map[string]any) map[string]any {
raw, _ := json.Marshal(doc)
copy, _ := creationDocument(string(raw))
return copy
}