mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
fix(migration): list the projects a recovered session was routed back into
This commit is contained in:
1 parent
8c35e9c8b2
commit
e0bbc694d7
6 files changed
+94
-5
No files matched your search
@@ -328,6 +328,13 @@ func (h *Hub) importLegacySessions(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
result := migration.RunLegacySessionImportInto(strings.TrimSpace(body.Path), SessionDirFor(known), legacyImportLog)
|
||||
for _, root := range result.Workspaces {
|
||||
// A folder the import routed sessions back into is a project the person
|
||||
// had in 1.x; without a place in the list its conversations are unreachable.
|
||||
if err := addRememberedWorkspace(r.Context(), root); err != nil && !errors.Is(err, errWorkspaceListFull) {
|
||||
slog.Warn("serve: could not list an imported project", "root", root, "err", err)
|
||||
}
|
||||
}
|
||||
count := 0
|
||||
for _, imported := range result.SessionImports {
|
||||
count += imported.Count
|
||||
|
||||
@@ -262,3 +262,42 @@ func TestImportLegacyAttributesReadAndWriteFailuresToTheirOwnClass(t *testing.T)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sessions that go back into the folder they were made in are only reachable
|
||||
// if that folder has a place in the list: the tree lists remembered folders and
|
||||
// open panes, and the importer's fallback workspace is neither.
|
||||
func TestImportLegacyListsTheProjectsItRoutedSessionsBackInto(t *testing.T) {
|
||||
home := testenv.TempDir(t)
|
||||
t.Setenv("REASONIX_HOME", home)
|
||||
t.Setenv("REASONIX_STATE_HOME", home)
|
||||
root := testenv.TempDir(t)
|
||||
project := testenv.TempDir(t)
|
||||
h := NewHub(HubOptions{})
|
||||
hubRuntime(t, h, root)
|
||||
srv := httptest.NewServer(operatorHandler(h))
|
||||
defer srv.Close()
|
||||
|
||||
old := testenv.TempDir(t)
|
||||
sessions := filepath.Join(old, "sessions")
|
||||
if err := os.MkdirAll(sessions, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log := `{"role":"user","content":"hello from the old project"}` + "\n" + `{"role":"assistant","content":"hi"}` + "\n"
|
||||
if err := os.WriteFile(filepath.Join(sessions, "a.jsonl"), []byte(log), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
meta, _ := json.Marshal(map[string]string{"workspace": project})
|
||||
if err := os.WriteFile(filepath.Join(sessions, "a.meta.json"), meta, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got := importLegacy(t, srv, old, root); got.Imported != 1 {
|
||||
t.Fatalf("imported %d, want 1", got.Imported)
|
||||
}
|
||||
for _, ws := range hubGet[[]treeWorkspace](t, srv, "/tree") {
|
||||
if filepath.Clean(ws.Root) == filepath.Clean(project) && ws.Remembered && len(ws.Sessions) == 1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("the project %s with its imported session is not in the tree", project)
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func desktopSessionOwners(tree fs.FS, home string) map[string]string {
|
||||
|
||||
// routeToOwner sends a session to its former workspace's session directory
|
||||
// while that workspace still exists, and otherwise declines.
|
||||
func routeToOwner(owners map[string]string) func(string) string {
|
||||
func routeToOwner(owners map[string]string, note func(root string)) func(string) string {
|
||||
if len(owners) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -52,6 +52,7 @@ func routeToOwner(owners map[string]string) func(string) string {
|
||||
if info, err := os.Stat(root); err != nil || !info.IsDir() {
|
||||
return ""
|
||||
}
|
||||
note(root)
|
||||
return config.ProjectSessionDir(root)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"reasonix/internal/contract/config"
|
||||
@@ -47,6 +48,9 @@ type Result struct {
|
||||
SessionImports []SessionImport
|
||||
SessionErrs []error
|
||||
SessionSkips []SessionSkip
|
||||
// Workspaces are the folders an explicit import routed sessions back into:
|
||||
// each still exists on disk and now holds sessions of its own.
|
||||
Workspaces []string
|
||||
// Unrecognised is set when an explicit import found no 1.x session
|
||||
// store under the chosen folder, as opposed to finding nothing new.
|
||||
Unrecognised bool
|
||||
@@ -160,7 +164,13 @@ func RunLegacySessionImportInto(sourceRoot, fallbackDest string, sink event.Sink
|
||||
result := Result{}
|
||||
sourceRoot = strings.TrimSpace(sourceRoot)
|
||||
emit(event.LevelInfo, "migration rescue: scanning explicit legacy sessions from "+sourceRoot)
|
||||
sources, closeRoot, err := explicitLegacySessionSources(sourceRoot)
|
||||
var routed []string
|
||||
note := func(root string) {
|
||||
if !slices.Contains(routed, root) {
|
||||
routed = append(routed, root)
|
||||
}
|
||||
}
|
||||
sources, closeRoot, err := explicitLegacySessionSources(sourceRoot, note)
|
||||
defer closeRoot()
|
||||
if err != nil {
|
||||
result.SessionErrs = append(result.SessionErrs, err)
|
||||
@@ -185,7 +195,13 @@ func RunLegacySessionImportInto(sourceRoot, fallbackDest string, sink event.Sink
|
||||
}
|
||||
} else {
|
||||
var rep *sessionstore.LegacyReport
|
||||
n, rep, err = sessionstore.ImportLegacySessionsFromExplicitDir(src.dir, fallbackDest, config.ProjectSessionDir)
|
||||
n, rep, err = sessionstore.ImportLegacySessionsFromExplicitDir(src.dir, fallbackDest, func(root string) string {
|
||||
dir := config.ProjectSessionDir(root)
|
||||
if dir != "" {
|
||||
note(root)
|
||||
}
|
||||
return dir
|
||||
})
|
||||
skipped = rep.Skipped
|
||||
}
|
||||
for _, sk := range skipped {
|
||||
@@ -206,6 +222,7 @@ func RunLegacySessionImportInto(sourceRoot, fallbackDest string, sink event.Sink
|
||||
if len(result.SessionImports) == 0 && len(result.SessionErrs) == 0 && len(result.SessionSkips) == 0 {
|
||||
emit(event.LevelInfo, "migration rescue: no legacy sessions needed migration from "+sourceRoot)
|
||||
}
|
||||
result.Workspaces = routed
|
||||
emit(event.LevelInfo, result.Summary())
|
||||
return result
|
||||
}
|
||||
@@ -568,7 +585,7 @@ func trimMatchingQuotes(s string) string {
|
||||
// explicitLegacySessionSources finds the session stores under a user-picked
|
||||
// folder. Every probe goes through one os.Root, so a symlink inside the folder
|
||||
// cannot lead the scan outside it.
|
||||
func explicitLegacySessionSources(picked string) ([]explicitSessionSource, func(), error) {
|
||||
func explicitLegacySessionSources(picked string, note func(root string)) ([]explicitSessionSource, func(), error) {
|
||||
picked = strings.TrimSpace(picked)
|
||||
if picked == "" {
|
||||
return nil, func() {}, fmt.Errorf("--from requires a legacy directory path")
|
||||
@@ -598,7 +615,7 @@ func explicitLegacySessionSources(picked string) ([]explicitSessionSource, func(
|
||||
for _, home := range []string{".", ".reasonix", "reasonix"} {
|
||||
add(path.Join(home, "sessions"), false, nil)
|
||||
add(path.Join(home, "sessions-v4"), true, nil)
|
||||
add(path.Join(home, "desktop-sessions-v5", "by-id"), true, routeToOwner(desktopSessionOwners(tree, home)))
|
||||
add(path.Join(home, "desktop-sessions-v5", "by-id"), true, routeToOwner(desktopSessionOwners(tree, home), note))
|
||||
projects, _ := fs.ReadDir(tree, path.Join(home, "projects"))
|
||||
for _, project := range projects {
|
||||
if !project.IsDir() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package migration
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -533,3 +534,25 @@ func TestExplicitImportFindsSessionsAddedAfterAnEarlierRun(t *testing.T) {
|
||||
t.Fatalf("second run imported %d, want the new session only", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExplicitImportNamesTheFoldersItRoutedSessionsBackInto(t *testing.T) {
|
||||
home := isolateMigrationHome(t)
|
||||
root := filepath.Join(home, "Roaming", "reasonix")
|
||||
project := filepath.Join(home, "work", "old-project")
|
||||
sessions := filepath.Join(root, "sessions")
|
||||
for _, dir := range []string{project, sessions} {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(sessions, "a.jsonl"), []byte(legacyMessageLog), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(sessions, "a.meta.json"), []byte(`{"workspace":`+strconv.Quote(project)+`}`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res := RunLegacySessionImportInto(root, filepath.Join(config.SessionDir(), "ws"), event.Discard)
|
||||
if len(res.Workspaces) != 1 || res.Workspaces[0] != project {
|
||||
t.Fatalf("Workspaces = %v, want [%s]", res.Workspaces, project)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
- 从旧版 Reasonix 找回会话后,原来的项目文件夹会自动出现在侧栏里,找回的会话不再“导入了却看不到”。关联 #13412。
|
||||
- After recovering sessions from an older Reasonix, the project folders they came from now appear in the sidebar, so recovered sessions are no longer imported but invisible. Fixes #13412.
|
||||
Reference in new issue
Block a user