refactor(02-02): IPC handlers route via ServiceContextRegistry
- projects.ts: Accept registry, resolve projectScanner via getActive() - sessions.ts: Accept registry, destructure all services from getActive() - search.ts: Accept registry, resolve projectScanner via getActive() - subagents.ts: Accept registry, resolve all services from getActive() - ssh.ts: Accept registry, create/destroy ServiceContext on connect/disconnect - SSH_CONNECT: Create new ServiceContext with SSH provider, register, start, switch - SSH_DISCONNECT: Switch to local, destroy SSH context - Import onContextSwitched from index.ts to rewire file watcher events - All handlers now call registry.getActive() at invocation time - No module-level service variables remain - fully dynamic routing
This commit is contained in:
parent
5bf41c6ed8
commit
24051acac8
5 changed files with 100 additions and 70 deletions
|
|
@ -14,18 +14,18 @@ import { type Project, type RepositoryGroup, type Session } from '../types';
|
|||
|
||||
import { validateProjectId } from './guards';
|
||||
|
||||
import type { ProjectScanner } from '../services';
|
||||
import type { ServiceContextRegistry } from '../services';
|
||||
|
||||
const logger = createLogger('IPC:projects');
|
||||
|
||||
// Service instance - set via initialize
|
||||
let projectScanner: ProjectScanner;
|
||||
// Service registry - set via initialize
|
||||
let registry: ServiceContextRegistry;
|
||||
|
||||
/**
|
||||
* Initializes project handlers with service instance.
|
||||
* Initializes project handlers with service registry.
|
||||
*/
|
||||
export function initializeProjectHandlers(scanner: ProjectScanner): void {
|
||||
projectScanner = scanner;
|
||||
export function initializeProjectHandlers(contextRegistry: ServiceContextRegistry): void {
|
||||
registry = contextRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,6 +60,7 @@ export function removeProjectHandlers(ipcMain: IpcMain): void {
|
|||
*/
|
||||
async function handleGetProjects(_event: IpcMainInvokeEvent): Promise<Project[]> {
|
||||
try {
|
||||
const { projectScanner } = registry.getActive();
|
||||
const projects = await projectScanner.scan();
|
||||
return projects;
|
||||
} catch (error) {
|
||||
|
|
@ -75,6 +76,7 @@ async function handleGetProjects(_event: IpcMainInvokeEvent): Promise<Project[]>
|
|||
*/
|
||||
async function handleGetRepositoryGroups(_event: IpcMainInvokeEvent): Promise<RepositoryGroup[]> {
|
||||
try {
|
||||
const { projectScanner } = registry.getActive();
|
||||
const groups = await projectScanner.scanWithWorktreeGrouping();
|
||||
return groups;
|
||||
} catch (error) {
|
||||
|
|
@ -100,6 +102,7 @@ async function handleGetWorktreeSessions(
|
|||
return [];
|
||||
}
|
||||
|
||||
const { projectScanner } = registry.getActive();
|
||||
const sessions = await projectScanner.listWorktreeSessions(validatedProject.value!);
|
||||
return sessions;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -14,16 +14,16 @@ import { coerceSearchMaxResults, validateProjectId, validateSearchQuery } from '
|
|||
|
||||
const logger = createLogger('IPC:search');
|
||||
|
||||
import type { ProjectScanner } from '../services';
|
||||
import type { ServiceContextRegistry } from '../services';
|
||||
|
||||
// Service instance - set via initialize
|
||||
let projectScanner: ProjectScanner;
|
||||
// Service registry - set via initialize
|
||||
let registry: ServiceContextRegistry;
|
||||
|
||||
/**
|
||||
* Initializes search handlers with service instance.
|
||||
* Initializes search handlers with service registry.
|
||||
*/
|
||||
export function initializeSearchHandlers(scanner: ProjectScanner): void {
|
||||
projectScanner = scanner;
|
||||
export function initializeSearchHandlers(contextRegistry: ServiceContextRegistry): void {
|
||||
registry = contextRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -68,6 +68,7 @@ async function handleSearchSessions(
|
|||
return { results: [], totalMatches: 0, sessionsSearched: 0, query };
|
||||
}
|
||||
|
||||
const { projectScanner } = registry.getActive();
|
||||
const safeMaxResults = coerceSearchMaxResults(maxResults, 50);
|
||||
const result = await projectScanner.searchSessions(
|
||||
validatedProject.value!,
|
||||
|
|
|
|||
|
|
@ -24,33 +24,19 @@ import {
|
|||
|
||||
import { coercePageLimit, validateProjectId, validateSessionId } from './guards';
|
||||
|
||||
import type { ChunkBuilder, ProjectScanner, SessionParser, SubagentResolver } from '../services';
|
||||
import type { ServiceContextRegistry } from '../services';
|
||||
import type { WaterfallData } from '@shared/types';
|
||||
|
||||
const logger = createLogger('IPC:sessions');
|
||||
|
||||
// Service instances - set via initialize
|
||||
let projectScanner: ProjectScanner;
|
||||
let sessionParser: SessionParser;
|
||||
let subagentResolver: SubagentResolver;
|
||||
let chunkBuilder: ChunkBuilder;
|
||||
let dataCache: DataCache;
|
||||
// Service registry - set via initialize
|
||||
let registry: ServiceContextRegistry;
|
||||
|
||||
/**
|
||||
* Initializes session handlers with service instances.
|
||||
* Initializes session handlers with service registry.
|
||||
*/
|
||||
export function initializeSessionHandlers(
|
||||
scanner: ProjectScanner,
|
||||
parser: SessionParser,
|
||||
resolver: SubagentResolver,
|
||||
builder: ChunkBuilder,
|
||||
cache: DataCache
|
||||
): void {
|
||||
projectScanner = scanner;
|
||||
sessionParser = parser;
|
||||
subagentResolver = resolver;
|
||||
chunkBuilder = builder;
|
||||
dataCache = cache;
|
||||
export function initializeSessionHandlers(contextRegistry: ServiceContextRegistry): void {
|
||||
registry = contextRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,6 +86,7 @@ async function handleGetSessions(
|
|||
return [];
|
||||
}
|
||||
|
||||
const { projectScanner } = registry.getActive();
|
||||
const sessions = await projectScanner.listSessions(validatedProject.value!);
|
||||
return sessions;
|
||||
} catch (error) {
|
||||
|
|
@ -128,6 +115,7 @@ async function handleGetSessionsPaginated(
|
|||
return { sessions: [], nextCursor: null, hasMore: false, totalCount: 0 };
|
||||
}
|
||||
|
||||
const { projectScanner } = registry.getActive();
|
||||
const safeLimit = coercePageLimit(limit, 20);
|
||||
const result = await projectScanner.listSessionsPaginated(
|
||||
validatedProject.value!,
|
||||
|
|
@ -162,6 +150,9 @@ async function handleGetSessionDetail(
|
|||
return null;
|
||||
}
|
||||
|
||||
const { projectScanner, sessionParser, subagentResolver, chunkBuilder, dataCache } =
|
||||
registry.getActive();
|
||||
|
||||
const safeProjectId = validatedProject.value!;
|
||||
const safeSessionId = validatedSession.value!;
|
||||
const cacheKey = DataCache.buildKey(safeProjectId, safeSessionId);
|
||||
|
|
@ -223,6 +214,7 @@ async function handleGetSessionGroups(
|
|||
);
|
||||
return [];
|
||||
}
|
||||
const { sessionParser, subagentResolver, chunkBuilder } = registry.getActive();
|
||||
const safeProjectId = validatedProject.value!;
|
||||
const safeSessionId = validatedSession.value!;
|
||||
|
||||
|
|
@ -262,6 +254,7 @@ async function handleGetSessionMetrics(
|
|||
if (!validatedProject.valid || !validatedSession.valid) {
|
||||
return null;
|
||||
}
|
||||
const { sessionParser, dataCache } = registry.getActive();
|
||||
const safeProjectId = validatedProject.value!;
|
||||
const safeSessionId = validatedSession.value!;
|
||||
|
||||
|
|
@ -297,6 +290,7 @@ async function handleGetWaterfallData(
|
|||
return null;
|
||||
}
|
||||
|
||||
const { chunkBuilder } = registry.getActive();
|
||||
return chunkBuilder.buildWaterfallData(detail.chunks, detail.processes);
|
||||
} catch (error) {
|
||||
logger.error(`Error in get-waterfall-data for ${projectId}/${sessionId}:`, error);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* SSH IPC Handlers - Manages SSH connection lifecycle from renderer requests.
|
||||
*
|
||||
* Channels:
|
||||
* - ssh:connect - Connect to SSH host, switch to remote mode
|
||||
* - ssh:disconnect - Disconnect and switch back to local mode
|
||||
* - ssh:connect - Connect to SSH host, create new context
|
||||
* - ssh:disconnect - Disconnect and switch back to local context
|
||||
* - ssh:getState - Get current connection state
|
||||
* - ssh:test - Test connection without switching
|
||||
*/
|
||||
|
|
@ -20,13 +20,14 @@ import {
|
|||
} from '@preload/constants/ipcChannels';
|
||||
import { createLogger } from '@shared/utils/logger';
|
||||
|
||||
import { configManager } from '../services';
|
||||
import { configManager, ServiceContext } from '../services';
|
||||
|
||||
import type {
|
||||
ServiceContextRegistry,
|
||||
SshConnectionConfig,
|
||||
SshConnectionManager,
|
||||
SshConnectionStatus,
|
||||
} from '../services/infrastructure/SshConnectionManager';
|
||||
} from '../services';
|
||||
import type { SshLastConnection } from '@shared/types';
|
||||
import type { IpcMain } from 'electron';
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ const logger = createLogger('IPC:ssh');
|
|||
// =============================================================================
|
||||
|
||||
let connectionManager: SshConnectionManager;
|
||||
let onModeSwitch: ((mode: 'local' | 'ssh') => Promise<void>) | null = null;
|
||||
let registry: ServiceContextRegistry;
|
||||
|
||||
// =============================================================================
|
||||
// Initialization
|
||||
|
|
@ -46,14 +47,14 @@ let onModeSwitch: ((mode: 'local' | 'ssh') => Promise<void>) | null = null;
|
|||
/**
|
||||
* Initialize SSH handlers with required services.
|
||||
* @param manager - The SSH connection manager instance
|
||||
* @param modeSwitchCallback - Called when switching between local/SSH mode
|
||||
* @param contextRegistry - The service context registry
|
||||
*/
|
||||
export function initializeSshHandlers(
|
||||
manager: SshConnectionManager,
|
||||
modeSwitchCallback: (mode: 'local' | 'ssh') => Promise<void>
|
||||
contextRegistry: ServiceContextRegistry
|
||||
): void {
|
||||
connectionManager = manager;
|
||||
onModeSwitch = modeSwitchCallback;
|
||||
registry = contextRegistry;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -63,10 +64,41 @@ export function initializeSshHandlers(
|
|||
export function registerSshHandlers(ipcMain: IpcMain): void {
|
||||
ipcMain.handle(SSH_CONNECT, async (_event, config: SshConnectionConfig) => {
|
||||
try {
|
||||
// Connect to SSH host
|
||||
await connectionManager.connect(config);
|
||||
if (onModeSwitch) {
|
||||
await onModeSwitch('ssh');
|
||||
|
||||
// Get provider and remote path
|
||||
const provider = connectionManager.getProvider();
|
||||
const remoteProjectsPath = connectionManager.getRemoteProjectsPath() ?? undefined;
|
||||
|
||||
// Generate context ID
|
||||
const contextId = `ssh-${config.host}`;
|
||||
|
||||
// Destroy existing SSH context if any (reconnection case)
|
||||
if (registry.has(contextId)) {
|
||||
logger.info(`Destroying existing SSH context: ${contextId}`);
|
||||
registry.destroy(contextId);
|
||||
}
|
||||
|
||||
// Create new SSH context
|
||||
const sshContext = new ServiceContext({
|
||||
id: contextId,
|
||||
type: 'ssh',
|
||||
fsProvider: provider,
|
||||
projectsDir: remoteProjectsPath,
|
||||
});
|
||||
|
||||
// Register and start SSH context
|
||||
registry.registerContext(sshContext);
|
||||
sshContext.start();
|
||||
|
||||
// Switch to SSH context
|
||||
registry.switch(contextId);
|
||||
|
||||
// Import and call context switch callback from index.ts
|
||||
const { onContextSwitched } = await import('../index');
|
||||
onContextSwitched(sshContext);
|
||||
|
||||
return { success: true, data: connectionManager.getStatus() };
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
|
|
@ -77,10 +109,27 @@ export function registerSshHandlers(ipcMain: IpcMain): void {
|
|||
|
||||
ipcMain.handle(SSH_DISCONNECT, async () => {
|
||||
try {
|
||||
// Get current SSH context ID before disconnecting
|
||||
const currentContextId = registry.getActiveContextId();
|
||||
const isSshContext = currentContextId.startsWith('ssh-');
|
||||
|
||||
// Disconnect from SSH
|
||||
connectionManager.disconnect();
|
||||
if (onModeSwitch) {
|
||||
await onModeSwitch('local');
|
||||
|
||||
// If we were on an SSH context, destroy it
|
||||
if (isSshContext) {
|
||||
// Switch back to local first (this also starts local file watcher)
|
||||
registry.switch('local');
|
||||
|
||||
// Destroy the SSH context
|
||||
registry.destroy(currentContextId);
|
||||
|
||||
// Call context switch callback to rewire events
|
||||
const localContext = registry.getActive();
|
||||
const { onContextSwitched } = await import('../index');
|
||||
onContextSwitched(localContext);
|
||||
}
|
||||
|
||||
return { success: true, data: connectionManager.getStatus() };
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
|
|
|
|||
|
|
@ -12,38 +12,18 @@ import { type SubagentDetail } from '../types';
|
|||
|
||||
import { validateProjectId, validateSessionId, validateSubagentId } from './guards';
|
||||
|
||||
import type {
|
||||
ChunkBuilder,
|
||||
DataCache,
|
||||
ProjectScanner,
|
||||
SessionParser,
|
||||
SubagentResolver,
|
||||
} from '../services';
|
||||
import type { ServiceContextRegistry } from '../services';
|
||||
|
||||
const logger = createLogger('IPC:subagents');
|
||||
|
||||
// Service instances - set via initialize
|
||||
let chunkBuilder: ChunkBuilder;
|
||||
let dataCache: DataCache;
|
||||
let sessionParser: SessionParser;
|
||||
let subagentResolver: SubagentResolver;
|
||||
let projectScanner: ProjectScanner;
|
||||
// Service registry - set via initialize
|
||||
let registry: ServiceContextRegistry;
|
||||
|
||||
/**
|
||||
* Initializes subagent handlers with service instances.
|
||||
* Initializes subagent handlers with service registry.
|
||||
*/
|
||||
export function initializeSubagentHandlers(
|
||||
builder: ChunkBuilder,
|
||||
cache: DataCache,
|
||||
parser: SessionParser,
|
||||
resolver: SubagentResolver,
|
||||
scanner: ProjectScanner
|
||||
): void {
|
||||
chunkBuilder = builder;
|
||||
dataCache = cache;
|
||||
sessionParser = parser;
|
||||
subagentResolver = resolver;
|
||||
projectScanner = scanner;
|
||||
export function initializeSubagentHandlers(contextRegistry: ServiceContextRegistry): void {
|
||||
registry = contextRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -97,6 +77,9 @@ async function handleGetSubagentDetail(
|
|||
const safeSessionId = validatedSession.value!;
|
||||
const safeSubagentId = validatedSubagent.value!;
|
||||
|
||||
const { chunkBuilder, sessionParser, subagentResolver, projectScanner, dataCache } =
|
||||
registry.getActive();
|
||||
|
||||
const cacheKey = `subagent-${safeProjectId}-${safeSessionId}-${safeSubagentId}`;
|
||||
|
||||
// Check cache first
|
||||
|
|
|
|||
Loading…
Reference in a new issue