diff --git a/apps/cli/src/cli.ts b/apps/cli/src/cli.ts index d7b81b78..9c901bc6 100644 --- a/apps/cli/src/cli.ts +++ b/apps/cli/src/cli.ts @@ -242,7 +242,7 @@ program } const { streamImport, detectFormat } = await import('./import') - const { dbManager } = initRuntime() + const { dbManager, pathProvider } = initRuntime() const nativeBinding = resolveNativeBinding() const format = detectFormat(file) @@ -269,6 +269,23 @@ program console.log(` Session ID: ${result.sessionId}`) console.log(` Messages written: ${result.diagnostics?.messagesWritten ?? 0}`) console.log(` Messages skipped: ${result.diagnostics?.messagesSkipped ?? 0}`) + + if (result.sessionId) { + try { + const { PreferencesManager, createDatabaseManagerAdapter, ownerProfileService } = + await import('@openchatlab/node-runtime') + const applied = ownerProfileService.tryApplyOwnerProfile( + createDatabaseManagerAdapter(dbManager), + new PreferencesManager(pathProvider.getSystemDir()), + result.sessionId + ) + if (applied.applied) { + console.log(` Owner auto-detected: ${applied.ownerId}`) + } + } catch (ownerErr) { + console.warn(` Owner profile apply skipped: ${ownerErr instanceof Error ? ownerErr.message : ownerErr}`) + } + } } else { console.error(`\n\nImport failed: ${result.error}`) process.exit(1) diff --git a/apps/cli/src/sync/index.ts b/apps/cli/src/sync/index.ts index bd211422..d353dbdf 100644 --- a/apps/cli/src/sync/index.ts +++ b/apps/cli/src/sync/index.ts @@ -6,6 +6,7 @@ import type { FastifyInstance } from 'fastify' import type { DatabaseManager } from '@openchatlab/node-runtime' +import { PreferencesManager, createDatabaseManagerAdapter, ownerProfileService } from '@openchatlab/node-runtime' import type { PathProvider } from '@openchatlab/core' import { DataSourceManager, PullEngine, initScheduler, stopAllTimers } from '@openchatlab/sync' import type { SyncLogger } from '@openchatlab/sync' @@ -42,12 +43,21 @@ export function initSync( const importer = new DirectImporter(dbManager, syncLogger) const notifier = new NoopNotifier() + const sessionAdapter = createDatabaseManagerAdapter(dbManager) + const preferences = new PreferencesManager(pathProvider.getSystemDir()) + pullEngine = new PullEngine({ fetcher, importer, notifier, dsManager, logger: syncLogger, + onSessionImported: (localSessionId) => { + const result = ownerProfileService.tryApplyOwnerProfile(sessionAdapter, preferences, localSessionId) + if (result.applied) { + syncLogger.info(`[Pull] Applied owner profile to session ${localSessionId} (owner: ${result.ownerId})`) + } + }, }) registerAutomationRoutes(server, { dsManager, pullEngine, dbManager, serverInfo }) diff --git a/apps/desktop/main/internal-api.ts b/apps/desktop/main/internal-api.ts index 6257fd25..8ff7fd7b 100644 --- a/apps/desktop/main/internal-api.ts +++ b/apps/desktop/main/internal-api.ts @@ -256,6 +256,11 @@ export function getInternalEndpoint(): InternalEndpoint | null { return endpoint } +/** Main-process DatabaseManager backing the internal server (null before startup). */ +export function getInternalDbManager(): DatabaseManager | null { + return dbManager +} + export async function stopInternalServer(): Promise { if (!server) return try { diff --git a/apps/desktop/main/ipc/api.ts b/apps/desktop/main/ipc/api.ts index 9e420e5f..54ac297e 100644 --- a/apps/desktop/main/ipc/api.ts +++ b/apps/desktop/main/ipc/api.ts @@ -8,10 +8,12 @@ import { ipcMain, net } from 'electron' import type { IpcContext } from './types' import * as apiServer from '../api' import { setConfigManager } from '../api' -import { getSettingsDir } from '../paths' +import { getSettingsDir, getSystemDataDir } from '../paths' import { apiLogger } from '../api/logger' import { getImportingStatus } from '../api/routes/import' import { deleteSession as deleteSessionFile } from '../database/core' +import { getInternalDbManager } from '../internal-api' +import { PreferencesManager, createDatabaseManagerAdapter, ownerProfileService } from '@openchatlab/node-runtime' import { ElectronFetcher, WorkerImporter, BrowserWindowNotifier } from '../api/adapters' import { ConfigManager, @@ -52,6 +54,19 @@ function ensureInstances(): void { dsManager, logger: syncLogger, isImporting: getImportingStatus, + onSessionImported: (localSessionId) => { + // Resolve lazily: the internal server (and its DatabaseManager) starts after IPC registration + const dbManager = getInternalDbManager() + if (!dbManager) return + const result = ownerProfileService.tryApplyOwnerProfile( + createDatabaseManagerAdapter(dbManager), + new PreferencesManager(getSystemDataDir()), + localSessionId + ) + if (result.applied) { + syncLogger.info(`[Pull] Applied owner profile to session ${localSessionId} (owner: ${result.ownerId})`) + } + }, }) } diff --git a/packages/sync/src/pull-engine.ts b/packages/sync/src/pull-engine.ts index 7a75dcac..e92a7217 100644 --- a/packages/sync/src/pull-engine.ts +++ b/packages/sync/src/pull-engine.ts @@ -120,6 +120,12 @@ export interface PullEngineOptions { logger?: SyncLogger /** Return true when an import is already in progress (skip pull) */ isImporting?: () => boolean + /** + * Called after a pull session completes successfully with the local session ID. + * Used for post-import side effects (e.g. applying the platform owner profile). + * Errors thrown by the hook never fail the pull. + */ + onSessionImported?: (localSessionId: string) => void } export class PullEngine { @@ -129,6 +135,7 @@ export class PullEngine { private dsManager: DataSourceManager private logger: SyncLogger private isImporting: () => boolean + private onSessionImported?: (localSessionId: string) => void private pullingSourceIds = new Set() private progressMap = new Map() @@ -139,6 +146,7 @@ export class PullEngine { this.dsManager = options.dsManager this.logger = options.logger ?? NOOP_LOGGER this.isImporting = options.isImporting ?? (() => false) + this.onSessionImported = options.onSessionImported } getProgress(): PullProgress[] { @@ -372,6 +380,13 @@ export class PullEngine { if (totalNewMessages > 0) this.notifier.onSessionListChanged() this.notifier.onPullResult(sourceId, sess.id, 'success', `+${totalNewMessages} messages`) this.markProgressDone(sess.id) + if (this.onSessionImported && sess.targetSessionId) { + try { + this.onSessionImported(sess.targetSessionId) + } catch (hookErr) { + this.logger.warn(`[Pull] onSessionImported hook failed for "${sess.name}": ${hookErr}`) + } + } return { success: true, newMessageCount: totalNewMessages } } catch (error: any) { const errMsg = error.message || 'Pull failed'