| 1 | package control |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "path/filepath" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "reasonix/internal/agent" |
| 10 | "reasonix/internal/event" |
| 11 | ) |
| 12 | |
| 13 | // A checkpoint opens with the composed turn, so its stored prompt carries the |
| 14 | // plan-mode marker and transient blocks. Every consumer treats Prompt as a |
| 15 | // label — and the rewind picker restores it into the composer — so Checkpoints |
| 16 | // must hand back what the user typed. Leaving it composed put |
| 17 | // "必须使用简体中文…" in the rewind list (#6903). |
| 18 | func TestCheckpointsReturnUserPromptWithoutComposedPrefixes(t *testing.T) { |
| 19 | dir := t.TempDir() |
| 20 | sess := agent.NewSession("sys") |
| 21 | exec := agent.New(nil, nil, sess, agent.Options{}, event.Discard) |
| 22 | runner := &recordingSessionRunner{session: sess} |
| 23 | c := New(Options{ |
| 24 | Runner: runner, |
| 25 | Executor: exec, |
| 26 | SessionDir: dir, |
| 27 | SessionPath: filepath.Join(dir, "session.jsonl"), |
| 28 | Label: "test", |
| 29 | }) |
| 30 | |
| 31 | const typed = "修复登录逻辑" |
| 32 | composed := agent.ReasoningLanguageBlock("zh") + "\n\n" + |
| 33 | PlanModeMarker + "\n\n" + |
| 34 | typed |
| 35 | o := newTurnOrchestrator(c) |
| 36 | if err := o.runTurnWithRawDisplay(context.Background(), composed, typed, ""); err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | |
| 40 | cps := c.Checkpoints() |
| 41 | if len(cps) != 1 { |
| 42 | t.Fatalf("checkpoints = %+v, want one", cps) |
| 43 | } |
| 44 | if cps[0].Prompt != typed { |
| 45 | t.Fatalf("checkpoint prompt = %q, want %q", cps[0].Prompt, typed) |
| 46 | } |
| 47 | if strings.Contains(cps[0].Prompt, "<reasoning-language>") || strings.Contains(cps[0].Prompt, PlanModeMarker) { |
| 48 | t.Fatalf("checkpoint prompt still carries composed prefixes: %q", cps[0].Prompt) |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | func TestHeadlessRunOpensCheckpoint(t *testing.T) { |
| 53 | dir := t.TempDir() |
| 54 | sess := agent.NewSession("sys") |
| 55 | exec := agent.New(nil, nil, sess, agent.Options{}, event.Discard) |
| 56 | c := New(Options{ |
| 57 | Runner: &fakeTurnRunner{}, |
| 58 | Executor: exec, |
| 59 | SessionDir: dir, |
| 60 | SessionPath: filepath.Join(dir, "headless.jsonl"), |
| 61 | Label: "test", |
| 62 | }) |
| 63 | |
| 64 | if err := c.Run(context.Background(), "headless edit"); err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | checkpoints := c.Checkpoints() |
| 68 | if len(checkpoints) != 1 || checkpoints[0].Turn != 0 || checkpoints[0].Prompt != "headless edit" { |
| 69 | t.Fatalf("headless checkpoints = %+v, want one checkpoint for the submitted turn", checkpoints) |
| 70 | } |
| 71 | } |
| 72 |