fix: enhance null safety and improve logging in SearchManager

- Added optional chaining and nullish coalescing to handle potential undefined values in Chroma results and timeline items.
- Updated logging statements to provide clearer information when no results are found.
- Refactored destructuring of parameters in findByConcept and findByFile methods for consistency.
This commit is contained in:
Alex Newman
2025-12-07 20:56:19 -05:00
parent 7175b527a6
commit 0a34786df9
4 changed files with 276 additions and 3192 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+248 -2521
View File
File diff suppressed because it is too large Load Diff
+17 -17
View File
@@ -314,9 +314,9 @@ export class SearchManager {
try { try {
silentDebug('[search-server] Using hybrid semantic search for timeline query'); silentDebug('[search-server] Using hybrid semantic search for timeline query');
const chromaResults = await this.queryChroma(query, 100); const chromaResults = await this.queryChroma(query, 100);
silentDebug(`[search-server] Chroma returned ${chromaResults.ids.length} semantic matches`); silentDebug(`[search-server] Chroma returned ${chromaResults?.ids?.length ?? 0} semantic matches`);
if (chromaResults.ids.length > 0) { if (chromaResults?.ids && chromaResults.ids.length > 0) {
const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000); const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
const recentIds = chromaResults.ids.filter((_id, idx) => { const recentIds = chromaResults.ids.filter((_id, idx) => {
const meta = chromaResults.metadatas[idx]; const meta = chromaResults.metadatas[idx];
@@ -410,14 +410,14 @@ export class SearchManager {
// Combine, sort, and filter timeline items // Combine, sort, and filter timeline items
const items: TimelineItem[] = [ const items: TimelineItem[] = [
...timelineData.observations.map((obs: any) => ({ type: 'observation' as const, data: obs, epoch: obs.created_at_epoch })), ...(timelineData.observations || []).map((obs: any) => ({ type: 'observation' as const, data: obs, epoch: obs.created_at_epoch })),
...timelineData.sessions.map((sess: any) => ({ type: 'session' as const, data: sess, epoch: sess.created_at_epoch })), ...(timelineData.sessions || []).map((sess: any) => ({ type: 'session' as const, data: sess, epoch: sess.created_at_epoch })),
...timelineData.prompts.map((prompt: any) => ({ type: 'prompt' as const, data: prompt, epoch: prompt.created_at_epoch })) ...(timelineData.prompts || []).map((prompt: any) => ({ type: 'prompt' as const, data: prompt, epoch: prompt.created_at_epoch }))
]; ];
items.sort((a, b) => a.epoch - b.epoch); items.sort((a, b) => a.epoch - b.epoch);
const filteredItems = this.timelineService.filterByDepth(items, anchorId, anchorEpoch, depth_before, depth_after); const filteredItems = this.timelineService.filterByDepth(items, anchorId, anchorEpoch, depth_before, depth_after);
if (filteredItems.length === 0) { if (!filteredItems || filteredItems.length === 0) {
return { return {
content: [{ content: [{
type: 'text' as const, type: 'text' as const,
@@ -476,7 +476,7 @@ export class SearchManager {
lines.push(`# Timeline around anchor: ${anchorId}`); lines.push(`# Timeline around anchor: ${anchorId}`);
} }
lines.push(`**Window:** ${depth_before} records before → ${depth_after} records after | **Items:** ${filteredItems.length}`); lines.push(`**Window:** ${depth_before} records before → ${depth_after} records after | **Items:** ${filteredItems?.length ?? 0}`);
lines.push(''); lines.push('');
// Legend // Legend
@@ -1069,7 +1069,7 @@ export class SearchManager {
return { return {
content: [{ content: [{
type: 'text' as const, type: 'text' as const,
text: `No user prompts found matching "${query}"` text: query ? `No user prompts found matching "${query}"` : 'No user prompts found'
}] }]
}; };
} }
@@ -1108,7 +1108,7 @@ export class SearchManager {
async findByConcept(args: any): Promise<any> { async findByConcept(args: any): Promise<any> {
try { try {
const normalized = this.normalizeParams(args); const normalized = this.normalizeParams(args);
const { concept, format = 'index', ...filters } = normalized; const { concepts: concept, format = 'index', ...filters } = normalized;
let results: ObservationSearchResult[] = []; let results: ObservationSearchResult[] = [];
// Metadata-first, semantic-enhanced search // Metadata-first, semantic-enhanced search
@@ -1197,7 +1197,7 @@ export class SearchManager {
async findByFile(args: any): Promise<any> { async findByFile(args: any): Promise<any> {
try { try {
const normalized = this.normalizeParams(args); const normalized = this.normalizeParams(args);
const { filePath, format = 'index', ...filters } = normalized; const { files: filePath, format = 'index', ...filters } = normalized;
let observations: ObservationSearchResult[] = []; let observations: ObservationSearchResult[] = [];
let sessions: SessionSummarySearchResult[] = []; let sessions: SessionSummarySearchResult[] = [];
@@ -1611,7 +1611,7 @@ export class SearchManager {
items.sort((a, b) => a.epoch - b.epoch); items.sort((a, b) => a.epoch - b.epoch);
const filteredItems = this.timelineService.filterByDepth(items, anchorId, anchorEpoch, depth_before, depth_after); const filteredItems = this.timelineService.filterByDepth(items, anchorId, anchorEpoch, depth_before, depth_after);
if (filteredItems.length === 0) { if (!filteredItems || filteredItems.length === 0) {
const anchorDate = new Date(anchorEpoch).toLocaleString(); const anchorDate = new Date(anchorEpoch).toLocaleString();
return { return {
content: [{ content: [{
@@ -1661,7 +1661,7 @@ export class SearchManager {
// Header // Header
lines.push(`# Timeline around anchor: ${anchorId}`); lines.push(`# Timeline around anchor: ${anchorId}`);
lines.push(`**Window:** ${depth_before} records before → ${depth_after} records after | **Items:** ${filteredItems.length}`); lines.push(`**Window:** ${depth_before} records before → ${depth_after} records after | **Items:** ${filteredItems?.length ?? 0}`);
lines.push(''); lines.push('');
// Legend // Legend
@@ -1899,14 +1899,14 @@ export class SearchManager {
// Combine, sort, and filter timeline items // Combine, sort, and filter timeline items
const items: TimelineItem[] = [ const items: TimelineItem[] = [
...timelineData.observations.map(obs => ({ type: 'observation' as const, data: obs, epoch: obs.created_at_epoch })), ...(timelineData.observations || []).map(obs => ({ type: 'observation' as const, data: obs, epoch: obs.created_at_epoch })),
...timelineData.sessions.map(sess => ({ type: 'session' as const, data: sess, epoch: sess.created_at_epoch })), ...(timelineData.sessions || []).map(sess => ({ type: 'session' as const, data: sess, epoch: sess.created_at_epoch })),
...timelineData.prompts.map(prompt => ({ type: 'prompt' as const, data: prompt, epoch: prompt.created_at_epoch })) ...(timelineData.prompts || []).map(prompt => ({ type: 'prompt' as const, data: prompt, epoch: prompt.created_at_epoch }))
]; ];
items.sort((a, b) => a.epoch - b.epoch); items.sort((a, b) => a.epoch - b.epoch);
const filteredItems = this.timelineService.filterByDepth(items, topResult.id, 0, depth_before, depth_after); const filteredItems = this.timelineService.filterByDepth(items, topResult.id, 0, depth_before, depth_after);
if (filteredItems.length === 0) { if (!filteredItems || filteredItems.length === 0) {
return { return {
content: [{ content: [{
type: 'text' as const, type: 'text' as const,
@@ -1956,7 +1956,7 @@ export class SearchManager {
// Header // Header
lines.push(`# Timeline for query: "${query}"`); lines.push(`# Timeline for query: "${query}"`);
lines.push(`**Anchor:** Observation #${topResult.id} - ${topResult.title || 'Untitled'}`); lines.push(`**Anchor:** Observation #${topResult.id} - ${topResult.title || 'Untitled'}`);
lines.push(`**Window:** ${depth_before} records before → ${depth_after} records after | **Items:** ${filteredItems.length}`); lines.push(`**Window:** ${depth_before} records before → ${depth_after} records after | **Items:** ${filteredItems?.length ?? 0}`);
lines.push(''); lines.push('');
// Legend // Legend