Refactor silent debugging to happy path error handling

- Replaced instances of silentDebug with happy_path_error__with_fallback across multiple files to improve error logging and handling.
- Updated the utility function to provide clearer semantics for error handling when expected values are missing.
- Introduced a script to find potential silent failures in the codebase that may need to be addressed with the new error handling approach.
This commit is contained in:
Alex Newman
2025-12-09 15:09:44 -05:00
parent d957bff495
commit c3761a2204
25 changed files with 293 additions and 204 deletions
+18 -10
View File
@@ -14,7 +14,7 @@ import path from 'path';
import { DatabaseManager } from './DatabaseManager.js';
import { SessionManager } from './SessionManager.js';
import { logger } from '../../utils/logger.js';
import { silentDebug } from '../../utils/silent-debug.js';
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
import { parseObservations, parseSummary } from '../../sdk/parser.js';
import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt, buildContinuationPrompt } from '../../sdk/prompts.js';
import { SettingsDefaultsManager } from './settings/SettingsDefaultsManager.js';
@@ -233,8 +233,16 @@ export class SDKAgent {
sdk_session_id: session.sdkSessionId,
project: session.project,
user_prompt: session.userPrompt,
last_user_message: message.last_user_message || '',
last_assistant_message: message.last_assistant_message || ''
last_user_message: happy_path_error__with_fallback(
'Missing last_user_message for summary in SDKAgent',
{ sessionDbId: session.sessionDbId, sdkSessionId: session.sdkSessionId },
message.last_user_message || ''
),
last_assistant_message: happy_path_error__with_fallback(
'Missing last_assistant_message for summary in SDKAgent',
{ sessionDbId: session.sessionDbId, sdkSessionId: session.sdkSessionId },
message.last_assistant_message || ''
)
})
},
session_id: session.claudeSessionId,
@@ -268,16 +276,16 @@ export class SDKAgent {
sessionId: session.sessionDbId,
obsId,
type: obs.type,
title: obs.title || silentDebug('obs.title is null', { obsId, type: obs.type }, '(untitled)'),
filesRead: obs.files_read?.length ?? (silentDebug('obs.files_read is null/undefined', { obsId }), 0),
filesModified: obs.files_modified?.length ?? (silentDebug('obs.files_modified is null/undefined', { obsId }), 0),
concepts: obs.concepts?.length ?? (silentDebug('obs.concepts is null/undefined', { obsId }), 0)
title: obs.title || happy_path_error__with_fallback('obs.title is null', { obsId, type: obs.type }, '(untitled)'),
filesRead: obs.files_read?.length ?? (happy_path_error__with_fallback('obs.files_read is null/undefined', { obsId }), 0),
filesModified: obs.files_modified?.length ?? (happy_path_error__with_fallback('obs.files_modified is null/undefined', { obsId }), 0),
concepts: obs.concepts?.length ?? (happy_path_error__with_fallback('obs.concepts is null/undefined', { obsId }), 0)
});
// Sync to Chroma with error logging
const chromaStart = Date.now();
const obsType = obs.type;
const obsTitle = obs.title || silentDebug('obs.title is null for Chroma sync', { obsId, type: obs.type }, '(untitled)');
const obsTitle = obs.title || happy_path_error__with_fallback('obs.title is null for Chroma sync', { obsId, type: obs.type }, '(untitled)');
this.dbManager.getChromaSync().syncObservation(
obsId,
session.claudeSessionId,
@@ -345,14 +353,14 @@ export class SDKAgent {
logger.info('SDK', 'Summary saved', {
sessionId: session.sessionDbId,
summaryId,
request: summary.request || silentDebug('summary.request is null', { summaryId }, '(no request)'),
request: summary.request || happy_path_error__with_fallback('summary.request is null', { summaryId }, '(no request)'),
hasCompleted: !!summary.completed,
hasNextSteps: !!summary.next_steps
});
// Sync to Chroma with error logging
const chromaStart = Date.now();
const summaryRequest = summary.request || silentDebug('summary.request is null for Chroma sync', { summaryId }, '(no request)');
const summaryRequest = summary.request || happy_path_error__with_fallback('summary.request is null for Chroma sync', { summaryId }, '(no request)');
this.dbManager.getChromaSync().syncSummary(
summaryId,
session.claudeSessionId,
+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 { silentDebug } from '../../utils/silent-debug.js';
import { happy_path_error__with_fallback } from '../../utils/silent-debug.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) {
silentDebug(`[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)`);
happy_path_error__with_fallback(`[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)`);
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 {
silentDebug(`[mcp-server] Using ChromaDB semantic search (type filter: ${type || 'all'})`);
happy_path_error__with_fallback(`[mcp-server] Using ChromaDB semantic search (type filter: ${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
silentDebug(`[mcp-server] ChromaDB returned ${chromaResults.ids.length} semantic matches`);
happy_path_error__with_fallback(`[mcp-server] ChromaDB returned ${chromaResults.ids.length} semantic matches`);
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);
silentDebug(`[mcp-server] ${recentMetadata.length} results within 90-day window`);
happy_path_error__with_fallback(`[mcp-server] ${recentMetadata.length} results within 90-day window`);
// Step 3: Categorize IDs by document type
const obsIds: number[] = [];
@@ -157,7 +157,7 @@ export class SearchManager {
}
}
silentDebug(`[mcp-server] Categorized: ${obsIds.length} obs, ${sessionIds.length} sessions, ${promptIds.length} prompts`);
happy_path_error__with_fallback(`[mcp-server] Categorized: ${obsIds.length} obs, ${sessionIds.length} sessions, ${promptIds.length} prompts`);
// 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 });
}
silentDebug(`[mcp-server] Hydrated ${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts from SQLite`);
happy_path_error__with_fallback(`[mcp-server] Hydrated ${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts from SQLite`);
} else {
// Chroma returned 0 results - this is the correct answer, don't fall back to FTS5
silentDebug(`[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)`);
happy_path_error__with_fallback(`[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)`);
}
} catch (chromaError: any) {
silentDebug('[mcp-server] ChromaDB failed - returning empty results (FTS5 fallback removed):', chromaError.message);
silentDebug('[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/');
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/');
// Return empty results - no fallback
observations = [];
sessions = [];
@@ -188,8 +188,8 @@ export class SearchManager {
}
// ChromaDB not initialized - return empty results (no fallback)
else {
silentDebug(`[mcp-server] ChromaDB not initialized - returning empty results (FTS5 fallback removed)`);
silentDebug(`[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/`);
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/`);
observations = [];
sessions = [];
prompts = [];
@@ -312,9 +312,9 @@ export class SearchManager {
if (this.chromaSync) {
try {
silentDebug('[mcp-server] Using hybrid semantic search for timeline query');
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
const chromaResults = await this.queryChroma(query, 100);
silentDebug(`[mcp-server] Chroma returned ${chromaResults?.ids?.length ?? 0} semantic matches`);
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults?.ids?.length ?? 0} semantic matches`);
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) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
}
}
@@ -345,7 +345,7 @@ export class SearchManager {
const topResult = results[0];
anchorId = topResult.id;
anchorEpoch = topResult.created_at_epoch;
silentDebug(`[mcp-server] Query mode: Using observation #${topResult.id} as timeline anchor`);
happy_path_error__with_fallback(`[mcp-server] Query mode: Using observation #${topResult.id} as timeline anchor`);
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
silentDebug('[mcp-server] Using Chroma semantic search with type=decision filter');
happy_path_error__with_fallback('[mcp-server] 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
silentDebug('[mcp-server] Using metadata-first + semantic ranking for decisions');
happy_path_error__with_fallback('[mcp-server] 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) {
silentDebug('[mcp-server] Chroma search failed, using SQLite fallback:', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma search failed, using SQLite fallback:', chromaError.message);
}
}
@@ -709,7 +709,7 @@ export class SearchManager {
// Search for change-type observations and change-related concepts
if (this.chromaSync) {
try {
silentDebug('[mcp-server] Using hybrid search for change-related observations');
happy_path_error__with_fallback('[mcp-server] 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) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
}
}
@@ -807,7 +807,7 @@ export class SearchManager {
// Search for how-it-works concept observations
if (this.chromaSync) {
try {
silentDebug('[mcp-server] Using metadata-first + semantic ranking for how-it-works');
happy_path_error__with_fallback('[mcp-server] 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) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
}
}
@@ -883,11 +883,11 @@ export class SearchManager {
// Vector-first search via ChromaDB
if (this.chromaSync) {
try {
silentDebug('[mcp-server] Using hybrid semantic search (Chroma + SQLite)');
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search (Chroma + SQLite)');
// Step 1: Chroma semantic search (top 100)
const chromaResults = await this.queryChroma(query, 100);
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
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;
});
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`);
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
// 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 });
silentDebug(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
}
}
} catch (chromaError: any) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
}
}
@@ -960,11 +960,11 @@ export class SearchManager {
// Vector-first search via ChromaDB
if (this.chromaSync) {
try {
silentDebug('[mcp-server] Using hybrid semantic search for sessions');
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for sessions');
// Step 1: Chroma semantic search (top 100)
const chromaResults = await this.queryChroma(query, 100, { doc_type: 'session_summary' });
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
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;
});
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`);
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
// 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 });
silentDebug(`[mcp-server] Hydrated ${results.length} sessions from SQLite`);
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} sessions from SQLite`);
}
}
} catch (chromaError: any) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
}
}
@@ -1037,11 +1037,11 @@ export class SearchManager {
// Vector-first search via ChromaDB
if (this.chromaSync) {
try {
silentDebug('[mcp-server] Using hybrid semantic search for user prompts');
happy_path_error__with_fallback('[mcp-server] 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' });
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
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;
});
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`);
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
// 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 });
silentDebug(`[mcp-server] Hydrated ${results.length} user prompts from SQLite`);
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} user prompts from SQLite`);
}
}
} catch (chromaError: any) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
}
}
@@ -1114,11 +1114,11 @@ export class SearchManager {
// Metadata-first, semantic-enhanced search
if (this.chromaSync) {
try {
silentDebug('[mcp-server] Using metadata-first + semantic ranking for concept search');
happy_path_error__with_fallback('[mcp-server] 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);
silentDebug(`[mcp-server] Found ${metadataResults.length} observations with concept "${concept}"`);
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with concept "${concept}"`);
if (metadataResults.length > 0) {
// Step 2: Chroma semantic ranking (rank by relevance to concept)
@@ -1133,7 +1133,7 @@ export class SearchManager {
}
}
silentDebug(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
// Step 3: Hydrate in semantic rank order
if (rankedIds.length > 0) {
@@ -1143,14 +1143,14 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
// Fall through to SQLite fallback
}
}
// Fall back to SQLite-only if Chroma unavailable or failed
if (results.length === 0) {
silentDebug('[mcp-server] Using SQLite-only concept search');
happy_path_error__with_fallback('[mcp-server] 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 {
silentDebug('[mcp-server] Using metadata-first + semantic ranking for file search');
happy_path_error__with_fallback('[mcp-server] 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);
silentDebug(`[mcp-server] Found ${metadataResults.observations.length} observations, ${metadataResults.sessions.length} sessions for file "${filePath}"`);
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.observations.length} observations, ${metadataResults.sessions.length} sessions for file "${filePath}"`);
// Sessions: Keep as-is (already summarized, no semantic ranking needed)
sessions = metadataResults.sessions;
@@ -1227,7 +1227,7 @@ export class SearchManager {
}
}
silentDebug(`[mcp-server] Chroma ranked ${rankedIds.length} observations by semantic relevance`);
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} observations by semantic relevance`);
// Step 3: Hydrate in semantic rank order
if (rankedIds.length > 0) {
@@ -1237,14 +1237,14 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
// Fall through to SQLite fallback
}
}
// Fall back to SQLite-only if Chroma unavailable or failed
if (observations.length === 0 && sessions.length === 0) {
silentDebug('[mcp-server] Using SQLite-only file search');
happy_path_error__with_fallback('[mcp-server] 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 {
silentDebug('[mcp-server] Using metadata-first + semantic ranking for type search');
happy_path_error__with_fallback('[mcp-server] 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);
silentDebug(`[mcp-server] Found ${metadataResults.length} observations with type "${typeStr}"`);
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with type "${typeStr}"`);
if (metadataResults.length > 0) {
// Step 2: Chroma semantic ranking (rank by relevance to type)
@@ -1342,7 +1342,7 @@ export class SearchManager {
}
}
silentDebug(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
// Step 3: Hydrate in semantic rank order
if (rankedIds.length > 0) {
@@ -1352,14 +1352,14 @@ export class SearchManager {
}
}
} catch (chromaError: any) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
// Fall through to SQLite fallback
}
}
// Fall back to SQLite-only if Chroma unavailable or failed
if (results.length === 0) {
silentDebug('[mcp-server] Using SQLite-only type search');
happy_path_error__with_fallback('[mcp-server] 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 {
silentDebug('[mcp-server] Using hybrid semantic search for timeline query');
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
const chromaResults = await this.queryChroma(query, 100);
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
if (chromaResults.ids.length > 0) {
// Filter by recency (90 days)
@@ -1827,15 +1827,15 @@ export class SearchManager {
return meta && meta.created_at_epoch > ninetyDaysAgo;
});
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`);
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
if (recentIds.length > 0) {
results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit: mode === 'auto' ? 1 : limit });
silentDebug(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
}
}
} catch (chromaError: any) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
}
}
@@ -1886,7 +1886,7 @@ export class SearchManager {
} else {
// Auto mode: Use top result as timeline anchor
const topResult = results[0];
silentDebug(`[mcp-server] Auto mode: Using observation #${topResult.id} as timeline anchor`);
happy_path_error__with_fallback(`[mcp-server] Auto mode: Using observation #${topResult.id} as timeline anchor`);
// Get timeline around this observation
const timelineData = this.sessionStore.getTimelineAroundObservation(
+6 -6
View File
@@ -11,7 +11,7 @@
import { EventEmitter } from 'events';
import { DatabaseManager } from './DatabaseManager.js';
import { logger } from '../../utils/logger.js';
import { silentDebug } from '../../utils/silent-debug.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 +43,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) {
silentDebug('[SessionManager] Updating project from database', {
happy_path_error__with_fallback('[SessionManager] Updating project from database', {
sessionDbId,
oldProject: session.project,
newProject: dbSession.project
@@ -53,7 +53,7 @@ export class SessionManager {
// Update userPrompt for continuation prompts
if (currentUserPrompt) {
silentDebug('[SessionManager] Updating userPrompt for continuation', {
happy_path_error__with_fallback('[SessionManager] Updating userPrompt for continuation', {
sessionDbId,
promptNumber,
oldPrompt: session.userPrompt.substring(0, 80),
@@ -62,7 +62,7 @@ export class SessionManager {
session.userPrompt = currentUserPrompt;
session.lastPromptNumber = promptNumber || session.lastPromptNumber;
} else {
silentDebug('[SessionManager] No currentUserPrompt provided for existing session', {
happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for existing session', {
sessionDbId,
promptNumber,
usingCachedPrompt: session.userPrompt.substring(0, 80)
@@ -78,13 +78,13 @@ export class SessionManager {
const userPrompt = currentUserPrompt || dbSession.user_prompt;
if (!currentUserPrompt) {
silentDebug('[SessionManager] No currentUserPrompt provided for new session, using database', {
happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for new session, using database', {
sessionDbId,
promptNumber,
dbPrompt: dbSession.user_prompt.substring(0, 80)
});
} else {
silentDebug('[SessionManager] Initializing session with fresh userPrompt', {
happy_path_error__with_fallback('[SessionManager] Initializing session with fresh userPrompt', {
sessionDbId,
promptNumber,
userPrompt: currentUserPrompt.substring(0, 80)
@@ -9,6 +9,7 @@ import express, { Request, Response } from 'express';
import { getWorkerPort } from '../../../../shared/worker-utils.js';
import { logger } from '../../../../utils/logger.js';
import { stripMemoryTagsFromJson, stripMemoryTagsFromPrompt } from '../../../../utils/tag-stripping.js';
import { happy_path_error__with_fallback } from '../../../../utils/silent-debug.js';
import { SessionManager } from '../../SessionManager.js';
import { DatabaseManager } from '../../DatabaseManager.js';
import { SDKAgent } from '../../SDKAgent.js';
@@ -307,7 +308,11 @@ export class SessionRoutes extends BaseRouteHandler {
tool_input: cleanedToolInput,
tool_response: cleanedToolResponse,
prompt_number: promptNumber,
cwd: cwd || ''
cwd: happy_path_error__with_fallback(
'Missing cwd when queueing observation in SessionRoutes',
{ sessionDbId, tool_name },
cwd || ''
)
});
// Ensure SDK agent is running
@@ -353,7 +358,15 @@ export class SessionRoutes extends BaseRouteHandler {
}
// Queue summarize
this.sessionManager.queueSummarize(sessionDbId, last_user_message || '', last_assistant_message);
this.sessionManager.queueSummarize(
sessionDbId,
happy_path_error__with_fallback(
'Missing last_user_message when queueing summary in SessionRoutes',
{ sessionDbId },
last_user_message || ''
),
last_assistant_message
);
// Ensure SDK agent is running
this.ensureGeneratorRunning(sessionDbId, 'summarize');