Revert "revert: roll back v12.3.3 (Issue Blowout 2026)"

This reverts commit bfc7de377a.
This commit is contained in:
Alex Newman
2026-04-20 12:18:55 -07:00
parent b9836d6c2a
commit 8d166b47c1
30 changed files with 869 additions and 168 deletions
+21 -10
View File
@@ -36,7 +36,7 @@ export class SessionSearch {
// Cache FTS5 availability once at construction (avoids DDL probe on every query)
this._fts5Available = this.isFts5Available();
// Ensure FTS tables exist
// Ensure FTS tables exist — may downgrade _fts5Available if creation fails
this.ensureFTSTables();
}
@@ -84,6 +84,7 @@ export class SessionSearch {
logger.info('DB', 'FTS5 tables created successfully');
} catch (error) {
// FTS5 creation failed at runtime despite probe succeeding — degrade gracefully
this._fts5Available = false;
logger.warn('DB', 'FTS5 table creation failed — search will use ChromaDB and LIKE queries', {}, error instanceof Error ? error : undefined);
}
}
@@ -327,14 +328,17 @@ export class SessionSearch {
LIMIT ? OFFSET ?
`;
params.unshift(query);
// Escape FTS5 special characters: wrap in quotes to treat as literal phrase
const escapedQuery = '"' + query.replace(/"/g, '""') + '"';
params.unshift(escapedQuery);
params.push(limit, offset);
try {
return this.db.prepare(sql).all(...params) as ObservationSearchResult[];
} catch (error) {
logger.warn('DB', 'FTS5 observation search failed, returning empty', {}, error instanceof Error ? error : undefined);
return [];
// Re-throw so callers can distinguish FTS failure from "no results"
logger.warn('DB', 'FTS5 observation search failed', {}, error instanceof Error ? error : undefined);
throw error;
}
}
@@ -383,7 +387,9 @@ export class SessionSearch {
const orderClause = orderBy === 'date_asc'
? 'ORDER BY s.created_at_epoch ASC'
: 'ORDER BY session_summaries_fts.rank ASC';
: orderBy === 'date_desc'
? 'ORDER BY s.created_at_epoch DESC'
: 'ORDER BY session_summaries_fts.rank ASC';
const sql = `
SELECT s.*, s.discovery_tokens
@@ -395,14 +401,17 @@ export class SessionSearch {
LIMIT ? OFFSET ?
`;
params.unshift(query);
// Escape FTS5 special characters: wrap in quotes to treat as literal phrase
const escapedQuery = '"' + query.replace(/"/g, '""') + '"';
params.unshift(escapedQuery);
params.push(limit, offset);
try {
return this.db.prepare(sql).all(...params) as SessionSummarySearchResult[];
} catch (error) {
logger.warn('DB', 'FTS5 session search failed, returning empty', {}, error instanceof Error ? error : undefined);
return [];
// Re-throw so callers can distinguish FTS failure from "no results"
logger.warn('DB', 'FTS5 session search failed', {}, error instanceof Error ? error : undefined);
throw error;
}
}
@@ -645,8 +654,10 @@ export class SessionSearch {
}
// LIKE fallback for user prompts text search (no FTS table for this entity)
baseConditions.push('up.prompt_text LIKE ?');
params.push(`%${query}%`);
// Escape LIKE metacharacters so %, _, and \ in user input are treated as literals
const escapedQuery = query.replace(/[\\%_]/g, '\\$&');
baseConditions.push("up.prompt_text LIKE ? ESCAPE '\\'");
params.push(`%${escapedQuery}%`);
const whereClause = `WHERE ${baseConditions.join(' AND ')}`;
const orderClause = orderBy === 'date_asc'