From 82ce9dcc2f170da6cb32419cd76bb2999e970bdb Mon Sep 17 00:00:00 2001 From: digua Date: Fri, 12 Jun 2026 22:57:09 +0800 Subject: [PATCH] feat(owner): auto-apply owner profile after pull sync and CLI import PullEngine gains an onSessionImported hook (hook errors never fail the pull), wired in both the CLI server and Electron main to silently fill session owner from the stored platform profile. The chatlab import CLI command does the same after a successful import. Co-Authored-By: Claude Fable 5 --- apps/cli/src/cli.ts | 19 ++++++++++++++++++- apps/cli/src/sync/index.ts | 10 ++++++++++ apps/desktop/main/internal-api.ts | 5 +++++ apps/desktop/main/ipc/api.ts | 17 ++++++++++++++++- packages/sync/src/pull-engine.ts | 15 +++++++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) 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'