mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
Accumulated work from the branch, carried across the rebase onto origin/studio and merged with what landed there in the meantime. Storage: a workspace can name where its state lives, and the server tracks the one relocation it may be running (internal/storage, config/storage_roots, serve/storage, the Storage panel). Composer intake: what a drop or a long paste becomes is one pure function — payload in, action out — so "what happens if I drop a folder" is a test rather than a branch of an onDrop handler. It sits on the drop router that landed upstream: that one answers which pane a drop hit, this one answers what it means and says so before the pointer is released. A paste long enough to bury the composer becomes a chip beside it instead. Session: the checkpoint agreement and the lineage/torn-log recovery cases, plus the conflict diagnostic learning where two transcripts parted and which roles stood there — a position and a role tell a local reshape from a foreign write without recording any content. Model refs: the default-model write checks the live catalog, so an extension model the runtime knows can be persisted as the default.
87 lines
2.9 KiB
Go
87 lines
2.9 KiB
Go
package agent
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"reasonix/internal/provider"
|
|
"reasonix/internal/store"
|
|
)
|
|
|
|
// The checkpoint is what a reader falls back to when the log cannot answer, so
|
|
// the two must never describe different transcripts. An append the read model
|
|
// declines used to leave it on the previous save's history: a second lineage
|
|
// beside the log, waiting for the first fallback to pick it.
|
|
func TestDeclinedReadModelAppendStillLeavesOneLineage(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := dir + "/session.jsonl"
|
|
live := NewSession("sys")
|
|
live.Add(provider.Message{Role: provider.RoleUser, Content: "第一句话", RawContent: "第一句话"})
|
|
live.Add(provider.Message{Role: provider.RoleAssistant, Content: "hi"})
|
|
if err := live.SaveSnapshot(path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The index a save is allowed to lose (it is warn-only there) is exactly
|
|
// what the in-place append needs to prove the checkpoint is the log's
|
|
// prefix, so losing it is how the decline happens in the field.
|
|
if err := os.Remove(store.SessionDisplayIndex(path)); err != nil {
|
|
t.Fatalf("drop display index: %v", err)
|
|
}
|
|
|
|
live.Add(provider.Message{Role: provider.RoleUser, Content: "第二句话", RawContent: "第二句话"})
|
|
if err := live.SaveSnapshot(path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
logged, _, damaged, err := loadSessionMessages(path)
|
|
if err != nil || damaged {
|
|
t.Fatalf("replay = %v damaged=%v", err, damaged)
|
|
}
|
|
checkpoint, err := loadSessionMessagesFromJSONL(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !messagesEqualForStorageList(logged, checkpoint) {
|
|
t.Fatalf("checkpoint %q disagrees with the log %q",
|
|
sessionUserTexts(checkpoint), sessionUserTexts(logged))
|
|
}
|
|
if got := sessionUserTexts(checkpoint); len(got) != 2 {
|
|
t.Fatalf("checkpoint users = %q, want both turns", got)
|
|
}
|
|
}
|
|
|
|
// Republishing the read model also republishes the index that proves it, so a
|
|
// session that lost one recovers the in-place append instead of paying a whole
|
|
// rewrite on every later save.
|
|
func TestRepublishedReadModelRearmsTheAppendPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := dir + "/session.jsonl"
|
|
live := NewSession("sys")
|
|
live.Add(provider.Message{Role: provider.RoleUser, Content: "第一句话", RawContent: "第一句话"})
|
|
if err := live.SaveSnapshot(path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Remove(store.SessionDisplayIndex(path)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
live.Add(provider.Message{Role: provider.RoleAssistant, Content: "hi"})
|
|
if err := live.SaveSnapshot(path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
idx, err := LoadSessionDisplayIndex(store.SessionDisplayIndex(path))
|
|
if err != nil {
|
|
t.Fatalf("index was not republished: %v", err)
|
|
}
|
|
if idx.MessageCount != 3 {
|
|
t.Fatalf("index message count = %d, want 3", idx.MessageCount)
|
|
}
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if idx.TranscriptSize != info.Size() {
|
|
t.Fatalf("index describes %d bytes of a %d-byte checkpoint", idx.TranscriptSize, info.Size())
|
|
}
|
|
}
|