fix(session): restore thread and legacy compatibility

This commit is contained in:
Hoshina
2026-04-08 00:32:53 +08:00
parent a827d01d7c
commit 296077eabf
18 changed files with 568 additions and 46 deletions
+57 -2
View File
@@ -256,11 +256,13 @@ func (h *Handler) findPicoJSONLSessions(dir string) ([]picoJSONLSessionRef, erro
refs := make([]picoJSONLSessionRef, 0)
seen := make(map[string]struct{})
metaBackedBases := make(map[string]struct{})
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".meta.json") {
continue
}
metaPath := filepath.Join(dir, entry.Name())
name := entry.Name()
metaPath := filepath.Join(dir, name)
meta, err := h.readSessionMeta(metaPath, "")
if err != nil {
continue
@@ -269,6 +271,27 @@ func (h *Handler) findPicoJSONLSessions(dir string) ([]picoJSONLSessionRef, erro
if !ok || ref.Key == "" || ref.ID == "" {
continue
}
metaBackedBases[strings.TrimSuffix(name, ".meta.json")] = struct{}{}
if _, exists := seen[ref.ID]; exists {
continue
}
seen[ref.ID] = struct{}{}
refs = append(refs, ref)
}
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".jsonl") {
continue
}
name := entry.Name()
base := strings.TrimSuffix(name, ".jsonl")
if _, ok := metaBackedBases[base]; ok {
continue
}
ref, ok := jsonlSessionRefFromFilename(name)
if !ok || ref.Key == "" || ref.ID == "" {
continue
}
if _, exists := seen[ref.ID]; exists {
continue
}
@@ -300,7 +323,8 @@ func (h *Handler) findLegacyPicoSessions(dir string) ([]picoLegacySessionRef, er
refs := make([]picoLegacySessionRef, 0)
seen := make(map[string]struct{})
for _, entry := range entries {
if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
name := entry.Name()
if entry.IsDir() || filepath.Ext(name) != ".json" || strings.HasSuffix(name, ".meta.json") {
continue
}
@@ -323,6 +347,37 @@ func (h *Handler) findLegacyPicoSessions(dir string) ([]picoLegacySessionRef, er
return refs, nil
}
func jsonlSessionRefFromFilename(name string) (picoJSONLSessionRef, bool) {
if !strings.HasSuffix(name, ".jsonl") {
return picoJSONLSessionRef{}, false
}
base := strings.TrimSuffix(name, ".jsonl")
if base == "" {
return picoJSONLSessionRef{}, false
}
legacyPrefix := sanitizeSessionKey(legacyPicoSessionPrefix)
if strings.HasPrefix(base, legacyPrefix) {
sessionID := strings.TrimPrefix(base, legacyPrefix)
if sessionID == "" {
return picoJSONLSessionRef{}, false
}
return picoJSONLSessionRef{
ID: sessionID,
Key: legacyPicoSessionPrefix + sessionID,
}, true
}
if session.IsOpaqueSessionKey(base) {
return picoJSONLSessionRef{
ID: base,
Key: base,
}, true
}
return picoJSONLSessionRef{}, false
}
func (h *Handler) findLegacyPicoSession(dir, sessionID string) (picoLegacySessionRef, error) {
refs, err := h.findLegacyPicoSessions(dir)
if err != nil {
+79
View File
@@ -750,3 +750,82 @@ func TestHandleSessions_FiltersEmptyJSONLFiles(t *testing.T) {
t.Fatalf("detail status = %d, want %d, body=%s", detailRec.Code, http.StatusNotFound, detailRec.Body.String())
}
}
func TestHandleSessions_ListsLegacyJSONLWithoutMeta(t *testing.T) {
configPath, cleanup := setupOAuthTestEnv(t)
defer cleanup()
dir := sessionsTestDir(t, configPath)
sessionKey := legacyPicoSessionPrefix + "missing-meta"
base := filepath.Join(dir, sanitizeSessionKey(sessionKey))
line, err := json.Marshal(providers.Message{Role: "user", Content: "recover me"})
if err != nil {
t.Fatalf("Marshal(message) error = %v", err)
}
if err := os.WriteFile(base+".jsonl", append(line, '\n'), 0o644); err != nil {
t.Fatalf("WriteFile(jsonl) error = %v", err)
}
h := NewHandler(configPath)
mux := http.NewServeMux()
h.RegisterRoutes(mux)
listRec := httptest.NewRecorder()
listReq := httptest.NewRequest(http.MethodGet, "/api/sessions", nil)
mux.ServeHTTP(listRec, listReq)
if listRec.Code != http.StatusOK {
t.Fatalf("list status = %d, want %d, body=%s", listRec.Code, http.StatusOK, listRec.Body.String())
}
var items []sessionListItem
if err := json.Unmarshal(listRec.Body.Bytes(), &items); err != nil {
t.Fatalf("Unmarshal(list) error = %v", err)
}
if len(items) != 1 {
t.Fatalf("len(items) = %d, want 1", len(items))
}
if items[0].ID != "missing-meta" {
t.Fatalf("items[0].ID = %q, want %q", items[0].ID, "missing-meta")
}
detailRec := httptest.NewRecorder()
detailReq := httptest.NewRequest(http.MethodGet, "/api/sessions/missing-meta", nil)
mux.ServeHTTP(detailRec, detailReq)
if detailRec.Code != http.StatusOK {
t.Fatalf("detail status = %d, want %d, body=%s", detailRec.Code, http.StatusOK, detailRec.Body.String())
}
}
func TestHandleSessions_IgnoresMetaJSONInLegacyFallback(t *testing.T) {
configPath, cleanup := setupOAuthTestEnv(t)
defer cleanup()
dir := sessionsTestDir(t, configPath)
metaOnly := filepath.Join(dir, "agent_main_pico_direct_pico_meta-only.meta.json")
metaOnlyContent := []byte(`{"key":"agent:main:pico:direct:pico:meta-only","summary":"meta only"}`)
if err := os.WriteFile(metaOnly, metaOnlyContent, 0o644); err != nil {
t.Fatalf("WriteFile(meta) error = %v", err)
}
h := NewHandler(configPath)
mux := http.NewServeMux()
h.RegisterRoutes(mux)
listRec := httptest.NewRecorder()
listReq := httptest.NewRequest(http.MethodGet, "/api/sessions", nil)
mux.ServeHTTP(listRec, listReq)
if listRec.Code != http.StatusOK {
t.Fatalf("list status = %d, want %d, body=%s", listRec.Code, http.StatusOK, listRec.Body.String())
}
var items []sessionListItem
if err := json.Unmarshal(listRec.Body.Bytes(), &items); err != nil {
t.Fatalf("Unmarshal(list) error = %v", err)
}
if len(items) != 0 {
t.Fatalf("len(items) = %d, want 0", len(items))
}
}