feat: Implement user prompt syncing to Chroma and enhance timeline querying
- Added `getObservationById` method to retrieve observations by ID in SessionStore. - Introduced `getSessionSummariesByIds` and `getUserPromptsByIds` methods for fetching session summaries and user prompts by IDs. - Developed `getTimelineAroundTimestamp` and `getTimelineAroundObservation` methods to provide a unified timeline of observations, sessions, and prompts around a specified anchor point. - Enhanced ChromaSync to format and sync user prompts, including a new `syncUserPrompt` method. - Updated WorkerService to sync the latest user prompt to Chroma after updating the worker port. - Created tests for timeline querying and MCP handler logic to ensure functionality. - Documented the implementation plan for user prompts and timeline context tool in the Chroma search completion plan.
This commit is contained in:
@@ -280,6 +280,71 @@ async function main() {
|
||||
const sessionTotalTime = ((Date.now() - sessionStartTime) / 1000).toFixed(1);
|
||||
console.log(`✅ Synced ${sessionDocs.length} session documents in ${sessionTotalTime}s\n`);
|
||||
|
||||
// Fetch user prompts
|
||||
console.log('📖 Reading user prompts from SQLite...');
|
||||
const prompts = store.db.prepare(`
|
||||
SELECT
|
||||
up.*,
|
||||
s.project,
|
||||
s.sdk_session_id
|
||||
FROM user_prompts up
|
||||
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
|
||||
WHERE s.project = ?
|
||||
ORDER BY up.created_at_epoch DESC
|
||||
LIMIT 1000
|
||||
`).all(project) as any[];
|
||||
console.log(`Found ${prompts.length} user prompts`);
|
||||
|
||||
// Prepare prompt documents - one document per prompt
|
||||
const promptDocs: ChromaDocument[] = [];
|
||||
|
||||
for (const prompt of prompts) {
|
||||
promptDocs.push({
|
||||
id: `prompt_${prompt.id}`,
|
||||
document: prompt.prompt_text,
|
||||
metadata: {
|
||||
sqlite_id: prompt.id,
|
||||
doc_type: 'user_prompt',
|
||||
sdk_session_id: prompt.sdk_session_id,
|
||||
project: prompt.project,
|
||||
created_at_epoch: prompt.created_at_epoch,
|
||||
prompt_number: prompt.prompt_number || 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Created ${promptDocs.length} user prompt documents\n`);
|
||||
|
||||
// Sync prompts in batches
|
||||
console.log('⬆️ Syncing user prompts to ChromaDB...');
|
||||
const promptBatches = Math.ceil(promptDocs.length / batchSize);
|
||||
const promptStartTime = Date.now();
|
||||
|
||||
for (let i = 0; i < promptDocs.length; i += batchSize) {
|
||||
const batch = promptDocs.slice(i, i + batchSize);
|
||||
const batchNumber = Math.floor(i / batchSize) + 1;
|
||||
const progress = Math.round((batchNumber / promptBatches) * 100);
|
||||
const docsProcessed = Math.min(i + batchSize, promptDocs.length);
|
||||
const elapsed = ((Date.now() - promptStartTime) / 1000).toFixed(1);
|
||||
|
||||
process.stdout.write(` [${batchNumber}/${promptBatches}] ${progress}% - Syncing docs ${i + 1}-${docsProcessed}/${promptDocs.length} (${elapsed}s elapsed)...`);
|
||||
|
||||
await client.callTool({
|
||||
name: 'chroma_add_documents',
|
||||
arguments: {
|
||||
collection_name: collectionName,
|
||||
documents: batch.map(d => d.document),
|
||||
ids: batch.map(d => d.id),
|
||||
metadatas: batch.map(d => d.metadata)
|
||||
}
|
||||
});
|
||||
|
||||
console.log(' ✓');
|
||||
}
|
||||
|
||||
const promptTotalTime = ((Date.now() - promptStartTime) / 1000).toFixed(1);
|
||||
console.log(`✅ Synced ${promptDocs.length} user prompt documents in ${promptTotalTime}s\n`);
|
||||
|
||||
// Get collection info
|
||||
const infoResult = await client.callTool({
|
||||
name: 'chroma_get_collection_info',
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { SessionStore } from '../src/services/sqlite/SessionStore.js';
|
||||
|
||||
const store = new SessionStore();
|
||||
|
||||
// Simulate what the MCP handler does
|
||||
const args = {
|
||||
anchor: 3300,
|
||||
depth_before: 10,
|
||||
depth_after: 10
|
||||
};
|
||||
|
||||
console.log('Testing MCP handler logic with anchor:', args.anchor);
|
||||
|
||||
try {
|
||||
let timeline;
|
||||
const anchor = args.anchor;
|
||||
const depth_before = args.depth_before;
|
||||
const depth_after = args.depth_after;
|
||||
|
||||
if (typeof anchor === 'number') {
|
||||
console.log('Anchor is number, getting observation...');
|
||||
const obs = store.getObservationById(anchor);
|
||||
if (!obs) {
|
||||
console.error('Observation not found!');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Found observation:', obs.id, 'at epoch:', obs.created_at_epoch);
|
||||
|
||||
console.log('Calling getTimelineAroundObservation...');
|
||||
timeline = store.getTimelineAroundObservation(anchor, obs.created_at_epoch, depth_before, depth_after);
|
||||
|
||||
console.log('Timeline result:', {
|
||||
observations: timeline.observations?.length,
|
||||
sessions: timeline.sessions?.length,
|
||||
prompts: timeline.prompts?.length
|
||||
});
|
||||
|
||||
console.log('Timeline observations type:', typeof timeline.observations);
|
||||
console.log('Timeline sessions type:', typeof timeline.sessions);
|
||||
console.log('Timeline prompts type:', typeof timeline.prompts);
|
||||
|
||||
if (timeline.observations) {
|
||||
console.log('First observation:', timeline.observations[0]);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✓ No errors!');
|
||||
} catch (err) {
|
||||
console.error('ERROR:', err.message);
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
store.close();
|
||||
@@ -0,0 +1,67 @@
|
||||
import { SessionStore } from '../src/services/sqlite/SessionStore.js';
|
||||
|
||||
const store = new SessionStore();
|
||||
|
||||
console.log('=== Test 1: Without project filter ===');
|
||||
try {
|
||||
const result = store.getTimelineAroundTimestamp(
|
||||
1730667961000, // timestamp for observation 3300
|
||||
5,
|
||||
5
|
||||
);
|
||||
|
||||
console.log('Result:', {
|
||||
observations: result?.observations?.length,
|
||||
sessions: result?.sessions?.length,
|
||||
prompts: result?.prompts?.length
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('ERROR:', err);
|
||||
}
|
||||
|
||||
console.log('\n=== Test 2: With project filter ===');
|
||||
try {
|
||||
const result = store.getTimelineAroundTimestamp(
|
||||
1730667961000,
|
||||
5,
|
||||
5,
|
||||
'claude-mem'
|
||||
);
|
||||
|
||||
console.log('Result:', {
|
||||
observations: result?.observations?.length,
|
||||
sessions: result?.sessions?.length,
|
||||
prompts: result?.prompts?.length
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('ERROR:', err);
|
||||
}
|
||||
|
||||
console.log('\n=== Test 3: With actual observation ID ===');
|
||||
// First get the actual timestamp for observation 3300
|
||||
const obs = store.getObservationById(3300);
|
||||
console.log('Observation 3300:', obs ? `Found at epoch ${obs.created_at_epoch}` : 'Not found');
|
||||
|
||||
if (obs) {
|
||||
try {
|
||||
const result = store.getTimelineAroundTimestamp(
|
||||
obs.created_at_epoch,
|
||||
5,
|
||||
5
|
||||
);
|
||||
|
||||
console.log('Result:', {
|
||||
observations: result?.observations?.length,
|
||||
sessions: result?.sessions?.length,
|
||||
prompts: result?.prompts?.length
|
||||
});
|
||||
|
||||
console.log('Observations:', result.observations?.map(o => `#${o.id}`));
|
||||
console.log('Sessions:', result.sessions?.map(s => `#S${s.id}`));
|
||||
console.log('Prompts:', result.prompts?.map(p => `#P${p.id}`));
|
||||
} catch (err) {
|
||||
console.error('ERROR:', err);
|
||||
}
|
||||
}
|
||||
|
||||
store.close();
|
||||
Reference in New Issue
Block a user