Files
Agent-System-Backend/internal/runmgr/rewind_test.go
T
weichenandCursor bead03374e
ci / vet-and-test (pull_request) Successful in 39s
Rewind tracked files and fall back before the first token
Why: workspace writes had no checkpoint, and a 5xx before any output stopped the turn instead of trying the next configured model.
Evidence: GOWORK=off go test ./internal/runtime/ ./internal/runmgr/ ./internal/tools/ ./internal/settings/ ./internal/server/ ./internal/app/
Impact: models.chat.fallbacks defaults to empty, so existing runs keep their model. Rewind restores only tracked writes and appends a system note. Usage records the model that served.
Refs: feature/durable-rewind
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 17:31:56 +00:00

78 lines
2.3 KiB
Go

package runmgr
import (
"os"
"path/filepath"
"testing"
"agent-system-backend/internal/tools"
core "agent-system-core"
)
func TestRewindRestoresTrackedWriteAndLeavesShellFile(t *testing.T) {
c, st := newExecEnv(t)
root := t.TempDir()
c.mgr.SetWorkspaceRoot(root)
c.eff = core.EffectiveSettings{"runtime": map[string]any{"fsTools": true}}
if err := tools.RegisterFSTools(c.mgr.registry); err != nil {
t.Fatal(err)
}
msg := core.Message{ConversationID: c.run.ConversationID, Role: core.RoleUser, Parts: []core.Part{{Type: core.PartText, Text: "go"}}}
if err := st.AppendMessage(&msg); err != nil {
t.Fatal(err)
}
c.userMessage = msg
res := c.ExecuteTool(core.ToolCallRequest{
CallID: "w1", Name: "fs_write_file",
Arguments: []byte(`{"path":"note.txt","content":"new"}`),
})
if res.Status != "ok" {
t.Fatalf("write %s %s", res.Status, res.Output)
}
sandbox := c.ConversationSandbox()
if err := os.WriteFile(filepath.Join(sandbox, "shell.txt"), []byte("from-shell"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(sandbox, "note.txt"), []byte("external"), 0o644); err != nil {
t.Fatal(err)
}
// 外部改动应被跳过。再写一个只被追踪、没有被外改的文件。
res = c.ExecuteTool(core.ToolCallRequest{
CallID: "w2", Name: "fs_write_file",
Arguments: []byte(`{"path":"kept.txt","content":"yes"}`),
})
if res.Status != "ok" {
t.Fatalf("kept %s %s", res.Status, res.Output)
}
got, err := c.mgr.RewindFiles(c.run.Owner, c.run.ConversationID, msg.ID)
if err != nil {
t.Fatal(err)
}
if len(got.Restored) != 1 || got.Restored[0] != "kept.txt" {
t.Fatalf("restored %#v skipped %#v", got.Restored, got.Skipped)
}
if len(got.Skipped) != 1 || got.Skipped[0] != "note.txt" {
t.Fatalf("skipped %#v", got.Skipped)
}
if _, err := os.Stat(filepath.Join(sandbox, "kept.txt")); !os.IsNotExist(err) {
t.Fatal("tracked create was not removed")
}
side, err := os.ReadFile(filepath.Join(sandbox, "shell.txt"))
if err != nil || string(side) != "from-shell" {
t.Fatalf("shell file %q %v", side, err)
}
msgs, err := st.ListMessages(c.run.ConversationID)
if err != nil {
t.Fatal(err)
}
var note string
for _, m := range msgs {
if m.Role == core.RoleSystem && len(m.Parts) > 0 {
note = m.Parts[0].Text
}
}
if note == "" {
t.Fatal("missing system note")
}
}