ad8ac7970d
* fix: distinguish connection errors from collection-not-found in ChromaSync Previously, ensureCollection() caught ALL errors from chroma_get_collection_info and assumed they meant "collection doesn't exist". This caused connection errors like "Not connected" to trigger unnecessary collection creation attempts. Now connection-related errors are re-thrown immediately instead of being misinterpreted as missing collections. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: improve error handling for Chroma connection and collection creation * fix: remove dead last_user_message from summarize flow The last_user_message field was extracted from transcripts but never used. In Claude Code transcripts, "user" type messages are mostly tool_results, not actual user input. The user's original request is already stored in user_prompts table. This removes the false warning "Missing last_user_message when queueing summary" which was complaining about missing data that didn't exist and wasn't needed. Changes: - summary-hook: Only extract last_assistant_message - SessionRoutes: Remove last_user_message from request body handling - SessionManager.queueSummarize: Remove lastUserMessage parameter - PendingMessage interface: Remove last_user_message field - SDKSession interface: Remove last_user_message field - All agents: Remove last_user_message from buildSummaryPrompt calls 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * build artifacts for plugin * Enhance error handling across multiple services - Improved logging in `BranchManager.ts` to capture recovery checkout failures. - Updated `PaginationHelper.ts` to log when file paths are plain strings instead of valid JSON. - Enhanced error logging in `SDKAgent.ts` for Claude executable detection failures. - Added logging for plain string handling in `SearchManager.ts` for files read and edited. - Improved logging in `paths.ts` for git root detection failures. - Enhanced JSON parsing error handling in `timeline-formatting.ts` with previews of failed inputs. - Updated `transcript-parser.ts` to log summary of parse errors after processing transcript lines. - Established a baseline for error handling practices in `error-handling-baseline.txt`. - Documented error handling anti-pattern rules in `CLAUDE.md` to prevent silent failures and improve code quality. * Add error handling anti-pattern detection script and guidelines - Introduced `detect-error-handling-antipatterns.ts` to identify common error handling issues in TypeScript code. - Created comprehensive documentation in `CLAUDE.md` outlining forbidden patterns, allowed patterns, and critical path protection rules. - Implemented checks for empty catch blocks, logging practices, and try-catch block sizes to prevent silent failures and improve debugging. - Established a reporting mechanism to summarize detected anti-patterns with severity levels. * feat: add console filter bar and log line parsing with filtering capabilities - Introduced a console filter bar with options to filter logs by level and component. - Implemented parsing of log lines to extract structured data including timestamp, level, component, and correlation ID. - Added functionality to toggle individual and all levels/components for filtering. - Enhanced log line rendering with color coding based on log level and special message types. - Improved responsiveness of the filter bar for smaller screens. --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
198 lines
5.3 KiB
TypeScript
198 lines
5.3 KiB
TypeScript
/**
|
|
* PaginationHelper: DRY pagination utility
|
|
*
|
|
* Responsibility:
|
|
* - DRY helper for paginated queries
|
|
* - Eliminates copy-paste across observations/summaries/prompts endpoints
|
|
* - Efficient LIMIT+1 trick to avoid COUNT(*) query
|
|
*/
|
|
|
|
import { DatabaseManager } from './DatabaseManager.js';
|
|
import { logger } from '../../utils/logger.js';
|
|
import type { PaginatedResult, Observation, Summary, UserPrompt } from '../worker-types.js';
|
|
|
|
export class PaginationHelper {
|
|
private dbManager: DatabaseManager;
|
|
|
|
constructor(dbManager: DatabaseManager) {
|
|
this.dbManager = dbManager;
|
|
}
|
|
|
|
/**
|
|
* Strip project path from file paths using heuristic
|
|
* Converts "/Users/user/project/src/file.ts" -> "src/file.ts"
|
|
* Uses first occurrence of project name from left (project root)
|
|
*/
|
|
private stripProjectPath(filePath: string, projectName: string): string {
|
|
const marker = `/${projectName}/`;
|
|
const index = filePath.indexOf(marker);
|
|
|
|
if (index !== -1) {
|
|
// Strip everything before and including the project name
|
|
return filePath.substring(index + marker.length);
|
|
}
|
|
|
|
// Fallback: return original path if project name not found
|
|
return filePath;
|
|
}
|
|
|
|
/**
|
|
* Strip project path from JSON array of file paths
|
|
*/
|
|
private stripProjectPaths(filePathsStr: string | null, projectName: string): string | null {
|
|
if (!filePathsStr) return filePathsStr;
|
|
|
|
try {
|
|
// Parse JSON array
|
|
const paths = JSON.parse(filePathsStr) as string[];
|
|
|
|
// Strip project path from each file
|
|
const strippedPaths = paths.map(p => this.stripProjectPath(p, projectName));
|
|
|
|
// Return as JSON string
|
|
return JSON.stringify(strippedPaths);
|
|
} catch (err) {
|
|
logger.debug('WORKER', 'File paths is plain string, using as-is', {}, err as Error);
|
|
return filePathsStr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sanitize observation by stripping project paths from files
|
|
*/
|
|
private sanitizeObservation(obs: Observation): Observation {
|
|
return {
|
|
...obs,
|
|
files_read: this.stripProjectPaths(obs.files_read, obs.project),
|
|
files_modified: this.stripProjectPaths(obs.files_modified, obs.project)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get paginated observations
|
|
*/
|
|
getObservations(offset: number, limit: number, project?: string): PaginatedResult<Observation> {
|
|
const result = this.paginate<Observation>(
|
|
'observations',
|
|
'id, memory_session_id, project, type, title, subtitle, narrative, text, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch',
|
|
offset,
|
|
limit,
|
|
project
|
|
);
|
|
|
|
// Strip project paths from file paths before returning
|
|
return {
|
|
...result,
|
|
items: result.items.map(obs => this.sanitizeObservation(obs))
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get paginated summaries
|
|
*/
|
|
getSummaries(offset: number, limit: number, project?: string): PaginatedResult<Summary> {
|
|
const db = this.dbManager.getSessionStore().db;
|
|
|
|
let query = `
|
|
SELECT
|
|
ss.id,
|
|
s.content_session_id as session_id,
|
|
ss.request,
|
|
ss.investigated,
|
|
ss.learned,
|
|
ss.completed,
|
|
ss.next_steps,
|
|
ss.project,
|
|
ss.created_at,
|
|
ss.created_at_epoch
|
|
FROM session_summaries ss
|
|
JOIN sdk_sessions s ON ss.memory_session_id = s.memory_session_id
|
|
`;
|
|
const params: any[] = [];
|
|
|
|
if (project) {
|
|
query += ' WHERE ss.project = ?';
|
|
params.push(project);
|
|
}
|
|
|
|
query += ' ORDER BY ss.created_at_epoch DESC LIMIT ? OFFSET ?';
|
|
params.push(limit + 1, offset);
|
|
|
|
const stmt = db.prepare(query);
|
|
const results = stmt.all(...params) as Summary[];
|
|
|
|
return {
|
|
items: results.slice(0, limit),
|
|
hasMore: results.length > limit,
|
|
offset,
|
|
limit
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get paginated user prompts
|
|
*/
|
|
getPrompts(offset: number, limit: number, project?: string): PaginatedResult<UserPrompt> {
|
|
const db = this.dbManager.getSessionStore().db;
|
|
|
|
let query = `
|
|
SELECT up.id, up.content_session_id, s.project, up.prompt_number, up.prompt_text, up.created_at, up.created_at_epoch
|
|
FROM user_prompts up
|
|
JOIN sdk_sessions s ON up.content_session_id = s.content_session_id
|
|
`;
|
|
const params: any[] = [];
|
|
|
|
if (project) {
|
|
query += ' WHERE s.project = ?';
|
|
params.push(project);
|
|
}
|
|
|
|
query += ' ORDER BY up.created_at_epoch DESC LIMIT ? OFFSET ?';
|
|
params.push(limit + 1, offset);
|
|
|
|
const stmt = db.prepare(query);
|
|
const results = stmt.all(...params) as UserPrompt[];
|
|
|
|
return {
|
|
items: results.slice(0, limit),
|
|
hasMore: results.length > limit,
|
|
offset,
|
|
limit
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generic pagination implementation (DRY)
|
|
*/
|
|
private paginate<T>(
|
|
table: string,
|
|
columns: string,
|
|
offset: number,
|
|
limit: number,
|
|
project?: string
|
|
): PaginatedResult<T> {
|
|
const db = this.dbManager.getSessionStore().db;
|
|
|
|
let query = `SELECT ${columns} FROM ${table}`;
|
|
const params: any[] = [];
|
|
|
|
if (project) {
|
|
query += ' WHERE project = ?';
|
|
params.push(project);
|
|
}
|
|
|
|
query += ' ORDER BY created_at_epoch DESC LIMIT ? OFFSET ?';
|
|
params.push(limit + 1, offset); // Fetch one extra to check hasMore
|
|
|
|
const stmt = db.prepare(query);
|
|
const results = stmt.all(...params) as T[];
|
|
|
|
return {
|
|
items: results.slice(0, limit),
|
|
hasMore: results.length > limit,
|
|
offset,
|
|
limit
|
|
};
|
|
}
|
|
}
|