Refactor worker port handling and improve logging

- Replaced hardcoded migration port with dynamic port retrieval using `getWorkerPort()` in worker-cli.ts.
- Updated context generator to clarify error handling comments.
- Introduced timeout constants in ProcessManager for better maintainability.
- Configured SQLite settings using constants for mmap size and cache size in DatabaseManager.
- Added timeout constants for Git and NPM commands in BranchManager.
- Enhanced error logging in FormattingService and SearchManager to provide more context on failures.
- Removed deprecated silentDebug function and replaced its usage with logger.debug.
- Updated tests to use dynamic worker port retrieval instead of hardcoded values.
This commit is contained in:
Alex Newman
2025-12-11 14:49:47 -05:00
parent 83b0f9551b
commit ded9671a82
26 changed files with 283 additions and 235 deletions
+9 -4
View File
@@ -13,6 +13,11 @@ import { logger } from '../../utils/logger.js';
const INSTALLED_PLUGIN_PATH = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
// Timeout constants
const GIT_COMMAND_TIMEOUT_MS = 30_000;
const NPM_INSTALL_TIMEOUT_MS = 120_000;
const DEFAULT_SHELL_TIMEOUT_MS = 60_000;
export interface BranchInfo {
branch: string | null;
isBeta: boolean;
@@ -36,7 +41,7 @@ function execGit(command: string): string {
return execSync(`git ${command}`, {
cwd: INSTALLED_PLUGIN_PATH,
encoding: 'utf-8',
timeout: 30000,
timeout: GIT_COMMAND_TIMEOUT_MS,
windowsHide: true
}).trim();
}
@@ -44,7 +49,7 @@ function execGit(command: string): string {
/**
* Execute shell command in installed plugin directory
*/
function execShell(command: string, timeoutMs: number = 60000): string {
function execShell(command: string, timeoutMs: number = DEFAULT_SHELL_TIMEOUT_MS): string {
return execSync(command, {
cwd: INSTALLED_PLUGIN_PATH,
encoding: 'utf-8',
@@ -165,7 +170,7 @@ export async function switchBranch(targetBranch: string): Promise<SwitchResult>
}
logger.debug('BRANCH', 'Running npm install');
execShell('npm install', 120000); // 2 minute timeout for npm
execShell('npm install', NPM_INSTALL_TIMEOUT_MS);
logger.success('BRANCH', 'Branch switch complete', {
branch: targetBranch
@@ -223,7 +228,7 @@ export async function pullUpdates(): Promise<SwitchResult> {
if (existsSync(installMarker)) {
unlinkSync(installMarker);
}
execShell('npm install', 120000);
execShell('npm install', NPM_INSTALL_TIMEOUT_MS);
logger.success('BRANCH', 'Updates pulled', { branch: info.branch });
+19 -6
View File
@@ -4,6 +4,7 @@
*/
import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
import { logger } from '../../utils/logger.js';
export type FormatType = 'index' | 'full';
@@ -102,7 +103,9 @@ Other tips:
if (facts.length > 0) {
metadata.push(`Facts: ${facts.join('; ')}`);
}
} catch {}
} catch (e) {
logger.warn('FORMAT', 'Invalid JSON in facts field', { obsId: obs.id });
}
}
if (obs.concepts) {
@@ -111,7 +114,9 @@ Other tips:
if (concepts.length > 0) {
metadata.push(`Concepts: ${concepts.join(', ')}`);
}
} catch {}
} catch (e) {
logger.warn('FORMAT', 'Invalid JSON in concepts field', { obsId: obs.id });
}
}
if (obs.files_read || obs.files_modified) {
@@ -119,12 +124,16 @@ Other tips:
if (obs.files_read) {
try {
files.push(...JSON.parse(obs.files_read));
} catch {}
} catch (e) {
logger.warn('FORMAT', 'Invalid JSON in files_read field', { obsId: obs.id });
}
}
if (obs.files_modified) {
try {
files.push(...JSON.parse(obs.files_modified));
} catch {}
} catch (e) {
logger.warn('FORMAT', 'Invalid JSON in files_modified field', { obsId: obs.id });
}
}
if (files.length > 0) {
metadata.push(`Files: ${[...new Set(files)].join(', ')}`);
@@ -190,12 +199,16 @@ Other tips:
if (session.files_read) {
try {
files.push(...JSON.parse(session.files_read));
} catch {}
} catch (e) {
logger.warn('FORMAT', 'Invalid JSON in session files_read field', { sessionId: session.sdk_session_id });
}
}
if (session.files_edited) {
try {
files.push(...JSON.parse(session.files_edited));
} catch {}
} catch (e) {
logger.warn('FORMAT', 'Invalid JSON in session files_edited field', { sessionId: session.sdk_session_id });
}
}
if (files.length > 0) {
metadata.push(`Files: ${[...new Set(files)].join(', ')}`);
+59 -59
View File
@@ -13,7 +13,7 @@ import { ChromaSync } from '../sync/ChromaSync.js';
import { FormattingService } from './FormattingService.js';
import { TimelineService, TimelineItem } from './TimelineService.js';
import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
import { logger } from '../../utils/logger.js';
const COLLECTION_NAME = 'cm__claude-mem';
@@ -97,7 +97,7 @@ export class SearchManager {
// PATH 1: FILTER-ONLY (no query text) - Skip Chroma/FTS5, use direct SQLite filtering
// This path enables date filtering which Chroma cannot do (requires direct SQLite access)
if (!query) {
happy_path_error__with_fallback(`[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)`);
logger.debug('SEARCH', 'Filter-only query (no query text), using direct SQLite filtering', { enablesDateFilters: true });
const obsOptions = { ...options, type: obs_type, concepts, files };
if (searchObservations) {
observations = this.sessionSearch.searchObservations(undefined, obsOptions);
@@ -113,7 +113,7 @@ export class SearchManager {
else if (this.chromaSync) {
let chromaSucceeded = false;
try {
happy_path_error__with_fallback(`[mcp-server] Using ChromaDB semantic search (type filter: ${type || 'all'})`);
logger.debug('SEARCH', 'Using ChromaDB semantic search', { typeFilter: type || 'all' });
// Build Chroma where filter for doc_type
let whereFilter: Record<string, any> | undefined;
@@ -128,7 +128,7 @@ export class SearchManager {
// Step 1: Chroma semantic search with optional type filter
const chromaResults = await this.queryChroma(query, 100, whereFilter);
chromaSucceeded = true; // Chroma didn't throw error
happy_path_error__with_fallback(`[mcp-server] ChromaDB returned ${chromaResults.ids.length} semantic matches`);
logger.debug('SEARCH', 'ChromaDB returned semantic matches', { matchCount: chromaResults.ids.length });
if (chromaResults.ids.length > 0) {
// Step 2: Filter by recency (90 days)
@@ -139,7 +139,7 @@ export class SearchManager {
isRecent: meta && meta.created_at_epoch > ninetyDaysAgo
})).filter(item => item.isRecent);
happy_path_error__with_fallback(`[mcp-server] ${recentMetadata.length} results within 90-day window`);
logger.debug('SEARCH', 'Results within 90-day window', { count: recentMetadata.length });
// Step 3: Categorize IDs by document type
const obsIds: number[] = [];
@@ -157,7 +157,7 @@ export class SearchManager {
}
}
happy_path_error__with_fallback(`[mcp-server] Categorized: ${obsIds.length} obs, ${sessionIds.length} sessions, ${promptIds.length} prompts`);
logger.debug('SEARCH', 'Categorized results by type', { observations: obsIds.length, sessions: sessionIds.length, prompts: promptIds.length });
// Step 4: Hydrate from SQLite with additional filters
if (obsIds.length > 0) {
@@ -172,14 +172,14 @@ export class SearchManager {
prompts = this.sessionStore.getUserPromptsByIds(promptIds, { orderBy: 'date_desc', limit: options.limit });
}
happy_path_error__with_fallback(`[mcp-server] Hydrated ${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts from SQLite`);
logger.debug('SEARCH', 'Hydrated results from SQLite', { observations: observations.length, sessions: sessions.length, prompts: prompts.length });
} else {
// Chroma returned 0 results - this is the correct answer, don't fall back to FTS5
happy_path_error__with_fallback(`[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)`);
logger.debug('SEARCH', 'ChromaDB found no matches (final result, no FTS5 fallback)', {});
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] ChromaDB failed - returning empty results (FTS5 fallback removed):', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/');
logger.debug('SEARCH', 'ChromaDB failed - returning empty results (FTS5 fallback removed)', { error: chromaError.message });
logger.debug('SEARCH', 'Install UVX/Python to enable vector search', { url: 'https://docs.astral.sh/uv/getting-started/installation/' });
// Return empty results - no fallback
observations = [];
sessions = [];
@@ -188,8 +188,8 @@ export class SearchManager {
}
// ChromaDB not initialized - return empty results (no fallback)
else {
happy_path_error__with_fallback(`[mcp-server] ChromaDB not initialized - returning empty results (FTS5 fallback removed)`);
happy_path_error__with_fallback(`[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/`);
logger.debug('SEARCH', 'ChromaDB not initialized - returning empty results (FTS5 fallback removed)', {});
logger.debug('SEARCH', 'Install UVX/Python to enable vector search', { url: 'https://docs.astral.sh/uv/getting-started/installation/' });
observations = [];
sessions = [];
prompts = [];
@@ -312,9 +312,9 @@ export class SearchManager {
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
logger.debug('SEARCH', 'Using hybrid semantic search for timeline query', {});
const chromaResults = await this.queryChroma(query, 100);
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults?.ids?.length ?? 0} semantic matches`);
logger.debug('SEARCH', 'Chroma returned semantic matches for timeline', { matchCount: chromaResults?.ids?.length ?? 0 });
if (chromaResults?.ids && chromaResults.ids.length > 0) {
const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
@@ -328,7 +328,7 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
}
}
@@ -345,7 +345,7 @@ export class SearchManager {
const topResult = results[0];
anchorId = topResult.id;
anchorEpoch = topResult.created_at_epoch;
happy_path_error__with_fallback(`[mcp-server] Query mode: Using observation #${topResult.id} as timeline anchor`);
logger.debug('SEARCH', 'Query mode: Using observation as timeline anchor', { observationId: topResult.id });
timelineData = this.sessionStore.getTimelineAroundObservation(topResult.id, topResult.created_at_epoch, depth_before, depth_after, project);
}
// MODE 2: Anchor-based timeline
@@ -621,7 +621,7 @@ export class SearchManager {
try {
if (query) {
// Semantic search filtered to decision type
happy_path_error__with_fallback('[mcp-server] Using Chroma semantic search with type=decision filter');
logger.debug('SEARCH', 'Using Chroma semantic search with type=decision filter', {});
const chromaResults = await this.queryChroma(query, Math.min((filters.limit || 20) * 2, 100), { type: 'decision' });
const obsIds = chromaResults.ids;
@@ -632,7 +632,7 @@ export class SearchManager {
}
} else {
// No query: get all decisions, rank by "decision" keyword
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for decisions');
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for decisions', {});
const metadataResults = this.sessionSearch.findByType('decision', filters);
if (metadataResults.length > 0) {
@@ -653,7 +653,7 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma search failed, using SQLite fallback:', chromaError.message);
logger.debug('SEARCH', 'Chroma search failed, using SQLite fallback', { error: chromaError.message });
}
}
@@ -709,7 +709,7 @@ export class SearchManager {
// Search for change-type observations and change-related concepts
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using hybrid search for change-related observations');
logger.debug('SEARCH', 'Using hybrid search for change-related observations', {});
// Get all observations with type="change" or concepts containing change
const typeResults = this.sessionSearch.findByType('change', filters);
@@ -737,7 +737,7 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
}
}
@@ -807,7 +807,7 @@ export class SearchManager {
// Search for how-it-works concept observations
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for how-it-works');
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for how-it-works', {});
const metadataResults = this.sessionSearch.findByConcept('how-it-works', filters);
if (metadataResults.length > 0) {
@@ -827,7 +827,7 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
}
}
@@ -883,11 +883,11 @@ export class SearchManager {
// Vector-first search via ChromaDB
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search (Chroma + SQLite)');
logger.debug('SEARCH', 'Using hybrid semantic search (Chroma + SQLite)', {});
// Step 1: Chroma semantic search (top 100)
const chromaResults = await this.queryChroma(query, 100);
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
logger.debug('SEARCH', 'Chroma returned semantic matches', { matchCount: chromaResults.ids.length });
if (chromaResults.ids.length > 0) {
// Step 2: Filter by recency (90 days)
@@ -897,17 +897,17 @@ export class SearchManager {
return meta && meta.created_at_epoch > ninetyDaysAgo;
});
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
logger.debug('SEARCH', 'Results within 90-day window', { count: recentIds.length });
// Step 3: Hydrate from SQLite in temporal order
if (recentIds.length > 0) {
const limit = options.limit || 20;
results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit });
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
logger.debug('SEARCH', 'Hydrated observations from SQLite', { count: results.length });
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
}
}
@@ -960,11 +960,11 @@ export class SearchManager {
// Vector-first search via ChromaDB
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for sessions');
logger.debug('SEARCH', 'Using hybrid semantic search for sessions', {});
// Step 1: Chroma semantic search (top 100)
const chromaResults = await this.queryChroma(query, 100, { doc_type: 'session_summary' });
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
logger.debug('SEARCH', 'Chroma returned semantic matches for sessions', { matchCount: chromaResults.ids.length });
if (chromaResults.ids.length > 0) {
// Step 2: Filter by recency (90 days)
@@ -974,17 +974,17 @@ export class SearchManager {
return meta && meta.created_at_epoch > ninetyDaysAgo;
});
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
logger.debug('SEARCH', 'Results within 90-day window', { count: recentIds.length });
// Step 3: Hydrate from SQLite in temporal order
if (recentIds.length > 0) {
const limit = options.limit || 20;
results = this.sessionStore.getSessionSummariesByIds(recentIds, { orderBy: 'date_desc', limit });
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} sessions from SQLite`);
logger.debug('SEARCH', 'Hydrated sessions from SQLite', { count: results.length });
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
}
}
@@ -1037,11 +1037,11 @@ export class SearchManager {
// Vector-first search via ChromaDB
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for user prompts');
logger.debug('SEARCH', 'Using hybrid semantic search for user prompts', {});
// Step 1: Chroma semantic search (top 100)
const chromaResults = await this.queryChroma(query, 100, { doc_type: 'user_prompt' });
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
logger.debug('SEARCH', 'Chroma returned semantic matches for prompts', { matchCount: chromaResults.ids.length });
if (chromaResults.ids.length > 0) {
// Step 2: Filter by recency (90 days)
@@ -1051,17 +1051,17 @@ export class SearchManager {
return meta && meta.created_at_epoch > ninetyDaysAgo;
});
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
logger.debug('SEARCH', 'Results within 90-day window', { count: recentIds.length });
// Step 3: Hydrate from SQLite in temporal order
if (recentIds.length > 0) {
const limit = options.limit || 20;
results = this.sessionStore.getUserPromptsByIds(recentIds, { orderBy: 'date_desc', limit });
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} user prompts from SQLite`);
logger.debug('SEARCH', 'Hydrated user prompts from SQLite', { count: results.length });
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
}
}
@@ -1114,11 +1114,11 @@ export class SearchManager {
// Metadata-first, semantic-enhanced search
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for concept search');
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for concept search', {});
// Step 1: SQLite metadata filter (get all IDs with this concept)
const metadataResults = this.sessionSearch.findByConcept(concept, filters);
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with concept "${concept}"`);
logger.debug('SEARCH', 'Found observations with concept', { concept, count: metadataResults.length });
if (metadataResults.length > 0) {
// Step 2: Chroma semantic ranking (rank by relevance to concept)
@@ -1133,7 +1133,7 @@ export class SearchManager {
}
}
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
logger.debug('SEARCH', 'Chroma ranked results by semantic relevance', { count: rankedIds.length });
// Step 3: Hydrate in semantic rank order
if (rankedIds.length > 0) {
@@ -1143,14 +1143,14 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
// Fall through to SQLite fallback
}
}
// Fall back to SQLite-only if Chroma unavailable or failed
if (results.length === 0) {
happy_path_error__with_fallback('[mcp-server] Using SQLite-only concept search');
logger.debug('SEARCH', 'Using SQLite-only concept search', {});
results = this.sessionSearch.findByConcept(concept, filters);
}
@@ -1204,11 +1204,11 @@ export class SearchManager {
// Metadata-first, semantic-enhanced search for observations
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for file search');
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for file search', {});
// Step 1: SQLite metadata filter (get all results with this file)
const metadataResults = this.sessionSearch.findByFile(filePath, filters);
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.observations.length} observations, ${metadataResults.sessions.length} sessions for file "${filePath}"`);
logger.debug('SEARCH', 'Found results for file', { file: filePath, observations: metadataResults.observations.length, sessions: metadataResults.sessions.length });
// Sessions: Keep as-is (already summarized, no semantic ranking needed)
sessions = metadataResults.sessions;
@@ -1227,7 +1227,7 @@ export class SearchManager {
}
}
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} observations by semantic relevance`);
logger.debug('SEARCH', 'Chroma ranked observations by semantic relevance', { count: rankedIds.length });
// Step 3: Hydrate in semantic rank order
if (rankedIds.length > 0) {
@@ -1237,14 +1237,14 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
// Fall through to SQLite fallback
}
}
// Fall back to SQLite-only if Chroma unavailable or failed
if (observations.length === 0 && sessions.length === 0) {
happy_path_error__with_fallback('[mcp-server] Using SQLite-only file search');
logger.debug('SEARCH', 'Using SQLite-only file search', {});
const results = this.sessionSearch.findByFile(filePath, filters);
observations = results.observations;
sessions = results.sessions;
@@ -1323,11 +1323,11 @@ export class SearchManager {
// Metadata-first, semantic-enhanced search
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for type search');
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for type search', {});
// Step 1: SQLite metadata filter (get all IDs with this type)
const metadataResults = this.sessionSearch.findByType(type, filters);
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with type "${typeStr}"`);
logger.debug('SEARCH', 'Found observations with type', { type: typeStr, count: metadataResults.length });
if (metadataResults.length > 0) {
// Step 2: Chroma semantic ranking (rank by relevance to type)
@@ -1342,7 +1342,7 @@ export class SearchManager {
}
}
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
logger.debug('SEARCH', 'Chroma ranked results by semantic relevance', { count: rankedIds.length });
// Step 3: Hydrate in semantic rank order
if (rankedIds.length > 0) {
@@ -1352,14 +1352,14 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
// Fall through to SQLite fallback
}
}
// Fall back to SQLite-only if Chroma unavailable or failed
if (results.length === 0) {
happy_path_error__with_fallback('[mcp-server] Using SQLite-only type search');
logger.debug('SEARCH', 'Using SQLite-only type search', {});
results = this.sessionSearch.findByType(type, filters);
}
@@ -1815,9 +1815,9 @@ export class SearchManager {
// Use hybrid search if available
if (this.chromaSync) {
try {
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
logger.debug('SEARCH', 'Using hybrid semantic search for timeline query', {});
const chromaResults = await this.queryChroma(query, 100);
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
logger.debug('SEARCH', 'Chroma returned semantic matches for timeline', { matchCount: chromaResults.ids.length });
if (chromaResults.ids.length > 0) {
// Filter by recency (90 days)
@@ -1827,15 +1827,15 @@ export class SearchManager {
return meta && meta.created_at_epoch > ninetyDaysAgo;
});
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
logger.debug('SEARCH', 'Results within 90-day window', { count: recentIds.length });
if (recentIds.length > 0) {
results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit: mode === 'auto' ? 1 : limit });
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
logger.debug('SEARCH', 'Hydrated observations from SQLite', { count: results.length });
}
}
} catch (chromaError: any) {
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
}
}
@@ -1886,7 +1886,7 @@ export class SearchManager {
} else {
// Auto mode: Use top result as timeline anchor
const topResult = results[0];
happy_path_error__with_fallback(`[mcp-server] Auto mode: Using observation #${topResult.id} as timeline anchor`);
logger.debug('SEARCH', 'Auto mode: Using observation as timeline anchor', { observationId: topResult.id });
// Get timeline around this observation
const timelineData = this.sessionStore.getTimelineAroundObservation(
+5 -6
View File
@@ -11,7 +11,6 @@
import { EventEmitter } from 'events';
import { DatabaseManager } from './DatabaseManager.js';
import { logger } from '../../utils/logger.js';
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
import type { ActiveSession, PendingMessage, ObservationData } from '../worker-types.js';
export class SessionManager {
@@ -43,7 +42,7 @@ export class SessionManager {
// in the database but the in-memory session still has the stale empty value
const dbSession = this.dbManager.getSessionById(sessionDbId);
if (dbSession.project && dbSession.project !== session.project) {
happy_path_error__with_fallback('[SessionManager] Updating project from database', {
logger.debug('SESSION', 'Updating project from database', {
sessionDbId,
oldProject: session.project,
newProject: dbSession.project
@@ -53,7 +52,7 @@ export class SessionManager {
// Update userPrompt for continuation prompts
if (currentUserPrompt) {
happy_path_error__with_fallback('[SessionManager] Updating userPrompt for continuation', {
logger.debug('SESSION', 'Updating userPrompt for continuation', {
sessionDbId,
promptNumber,
oldPrompt: session.userPrompt.substring(0, 80),
@@ -62,7 +61,7 @@ export class SessionManager {
session.userPrompt = currentUserPrompt;
session.lastPromptNumber = promptNumber || session.lastPromptNumber;
} else {
happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for existing session', {
logger.debug('SESSION', 'No currentUserPrompt provided for existing session', {
sessionDbId,
promptNumber,
usingCachedPrompt: session.userPrompt.substring(0, 80)
@@ -78,13 +77,13 @@ export class SessionManager {
const userPrompt = currentUserPrompt || dbSession.user_prompt;
if (!currentUserPrompt) {
happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for new session, using database', {
logger.debug('SESSION', 'No currentUserPrompt provided for new session, using database', {
sessionDbId,
promptNumber,
dbPrompt: dbSession.user_prompt.substring(0, 80)
});
} else {
happy_path_error__with_fallback('[SessionManager] Initializing session with fresh userPrompt', {
logger.debug('SESSION', 'Initializing session with fresh userPrompt', {
sessionDbId,
promptNumber,
userPrompt: currentUserPrompt.substring(0, 80)