From 6fe7367eff370b8fc5f0c573804a886a08cb8005 Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Sat, 27 Dec 2025 11:20:56 +0400 Subject: [PATCH] Fix session migration: cache SDK sessions to localStorage for auto-import on native mode startup --- packages/ui/src/stores/session-api.ts | 33 +++--- packages/ui/src/stores/session-migration.ts | 118 +++++++++++++++++++- 2 files changed, 132 insertions(+), 19 deletions(-) diff --git a/packages/ui/src/stores/session-api.ts b/packages/ui/src/stores/session-api.ts index f71689a..35dcdfd 100644 --- a/packages/ui/src/stores/session-api.ts +++ b/packages/ui/src/stores/session-api.ts @@ -3,7 +3,7 @@ import type { Message } from "../types/message" import { instances } from "./instances" import { nativeSessionApi } from "../lib/lite-mode" -import { needsMigration, migrateSessionsToNative, markMigrated, getExistingSdkSessions } from "./session-migration" +import { needsMigration, autoImportCachedSessions, markMigrated, cacheSDKSessions } from "./session-migration" import { preferences, setAgentModelPreference, getAgentModelPreference } from "./preferences" import { setSessionCompactionState } from "./session-compaction" import { @@ -390,23 +390,16 @@ async function fetchSessions(instanceId: string): Promise { let responseData: any[] = [] if (isNative) { - // Check if we need to migrate sessions from SDK mode + // Auto-import cached SDK sessions on native mode startup if (needsMigration(instanceId)) { - const existingSdkSessions = getExistingSdkSessions(instanceId) - if (existingSdkSessions.length > 0) { - log.info({ instanceId, count: existingSdkSessions.length }, "Migrating SDK sessions to native mode") - const migrationData = existingSdkSessions.map(s => ({ - id: s.id, - title: s.title, - parentId: s.parentId, - time: s.time, - model: s.model, - agent: s.agent - })) - const result = await migrateSessionsToNative(instanceId, migrationData) - log.info({ instanceId, result }, "Migration completed") - } else { - markMigrated(instanceId) + try { + const result = await autoImportCachedSessions(instanceId) + if (result.imported > 0) { + log.info({ instanceId, result }, "Auto-imported SDK sessions to native mode") + } + } catch (error) { + log.error({ instanceId, error }, "Failed to auto-import SDK sessions") + markMigrated(instanceId) // Mark as migrated to prevent repeated failures } } @@ -488,6 +481,12 @@ async function fetchSessions(instanceId: string): Promise { return next }) + // Cache SDK sessions to localStorage for later migration to native mode + if (!isNative && sessionMap.size > 0) { + cacheSDKSessions(instanceId, Array.from(sessionMap.values())) + } + + setMessagesLoaded((prev) => { const next = new Map(prev) const loadedSet = next.get(instanceId) diff --git a/packages/ui/src/stores/session-migration.ts b/packages/ui/src/stores/session-migration.ts index 02eda99..a488770 100644 --- a/packages/ui/src/stores/session-migration.ts +++ b/packages/ui/src/stores/session-migration.ts @@ -1,5 +1,8 @@ /** * Session Migration - Handles importing sessions when switching between SDK and Native modes + * + * This module caches SDK session data to localStorage so it can be imported to Native mode + * when the user switches modes. */ import { nativeSessionApi } from "../lib/lite-mode" @@ -9,9 +12,22 @@ import type { Session } from "../types/session" const log = getLogger("session-migration") +// LocalStorage key prefix for cached SDK sessions +const SDK_SESSION_CACHE_PREFIX = "nomadarch_sdk_sessions_" + // Track which workspaces have already been migrated to prevent duplicate migrations const migratedWorkspaces = new Set() +export interface CachedSession { + id: string + title?: string + parentId?: string | null + createdAt?: number + updatedAt?: number + model?: { providerId: string; modelId: string } + agent?: string +} + export interface MigrationResult { success: boolean imported: number @@ -19,6 +35,63 @@ export interface MigrationResult { error?: string } +/** + * Cache SDK sessions to localStorage for later migration + * This should be called whenever sessions are fetched in SDK mode + */ +export function cacheSDKSessions(workspaceId: string, sessionList: Session[]): void { + if (sessionList.length === 0) return + + try { + const cached: CachedSession[] = sessionList.map(s => ({ + id: s.id, + title: s.title, + parentId: s.parentId, + createdAt: s.time?.created, + updatedAt: s.time?.updated, + model: s.model, + agent: s.agent + })) + + const key = SDK_SESSION_CACHE_PREFIX + workspaceId + localStorage.setItem(key, JSON.stringify(cached)) + log.info({ workspaceId, count: cached.length }, "Cached SDK sessions for migration") + } catch (error) { + log.error({ workspaceId, error }, "Failed to cache SDK sessions") + } +} + +/** + * Get cached SDK sessions from localStorage + */ +export function getCachedSDKSessions(workspaceId: string): CachedSession[] { + try { + const key = SDK_SESSION_CACHE_PREFIX + workspaceId + const cached = localStorage.getItem(key) + if (!cached) return [] + + const sessions = JSON.parse(cached) as CachedSession[] + log.info({ workspaceId, count: sessions.length }, "Retrieved cached SDK sessions") + return sessions + } catch (error) { + log.error({ workspaceId, error }, "Failed to retrieve cached SDK sessions") + return [] + } +} + +/** + * Clear cached SDK sessions after successful migration + */ +export function clearCachedSDKSessions(workspaceId: string): void { + try { + const key = SDK_SESSION_CACHE_PREFIX + workspaceId + localStorage.removeItem(key) + log.info({ workspaceId }, "Cleared cached SDK sessions") + } catch (error) { + log.error({ workspaceId, error }, "Failed to clear cached SDK sessions") + } +} + /** * Check if a workspace needs session migration */ @@ -52,6 +125,8 @@ export async function migrateSessionsToNative( id: string title?: string parentId?: string | null + createdAt?: number + updatedAt?: number time?: { created?: number; updated?: number } model?: { providerId: string; modelId: string } agent?: string @@ -77,8 +152,8 @@ export async function migrateSessionsToNative( id: s.id, title: s.title, parentId: s.parentId, - createdAt: s.time?.created, - updatedAt: s.time?.updated, + createdAt: s.createdAt || s.time?.created, + updatedAt: s.updatedAt || s.time?.updated, model: s.model, agent: s.agent, messages: s.messages?.map(m => ({ @@ -94,6 +169,11 @@ export async function migrateSessionsToNative( log.info({ workspaceId, ...result }, "Session migration completed") markMigrated(workspaceId) + // Clear the cache after successful migration + if (result.success) { + clearCachedSDKSessions(workspaceId) + } + return { success: result.success, imported: result.imported, @@ -110,6 +190,40 @@ export async function migrateSessionsToNative( } } +/** + * Auto-import cached SDK sessions to native mode + * This is the main entry point for automatic migration on startup + */ +export async function autoImportCachedSessions(workspaceId: string): Promise { + if (!needsMigration(workspaceId)) { + return { success: true, imported: 0, skipped: 0 } + } + + // Get cached sessions from localStorage + const cachedSessions = getCachedSDKSessions(workspaceId) + + if (cachedSessions.length === 0) { + // Also check in-memory sessions as a fallback + const memorySessions = getExistingSdkSessions(workspaceId) + if (memorySessions.length > 0) { + const migrationData = memorySessions.map(s => ({ + id: s.id, + title: s.title, + parentId: s.parentId, + time: s.time, + model: s.model, + agent: s.agent + })) + return migrateSessionsToNative(workspaceId, migrationData) + } + + markMigrated(workspaceId) + return { success: true, imported: 0, skipped: 0 } + } + + return migrateSessionsToNative(workspaceId, cachedSessions) +} + /** * Clear migration status (for testing or when user explicitly wants to re-migrate) */