Files
claude-mem/tests/session_store.test.ts
Alex Newman 266c746d50 feat: Fix observation timestamps, refactor session management, and enhance worker reliability (#437)
* Refactor worker version checks and increase timeout settings

- Updated the default hook timeout from 5000ms to 120000ms for improved stability.
- Modified the worker version check to log a warning instead of restarting the worker on version mismatch.
- Removed legacy PM2 cleanup and worker start logic, simplifying the ensureWorkerRunning function.
- Enhanced polling mechanism for worker readiness with increased retries and reduced interval.

* feat: implement worker queue polling to ensure processing completion before proceeding

* refactor: change worker command from start to restart in hooks configuration

* refactor: remove session management complexity

- Simplify createSDKSession to pure INSERT OR IGNORE
- Remove auto-create logic from storeObservation/storeSummary
- Delete 11 unused session management methods
- Derive prompt_number from user_prompts count
- Keep sdk_sessions table schema unchanged for compatibility

* refactor: simplify session management by removing unused methods and auto-creation logic

* Refactor session prompt number retrieval in SessionRoutes

- Updated the method of obtaining the prompt number from the session.
- Replaced `store.getPromptCounter(sessionDbId)` with `store.getPromptNumberFromUserPrompts(claudeSessionId)` for better clarity and accuracy.
- Adjusted the logic for incrementing the prompt number to derive it from the user prompts count instead of directly incrementing a counter.

* refactor: replace getPromptCounter with getPromptNumberFromUserPrompts in SessionManager

Phase 7 of session management simplification. Updates SessionManager to derive
prompt numbers from user_prompts table count instead of using the deprecated
prompt_counter column.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor: simplify SessionCompletionHandler to use direct SQL query

Phase 8: Remove call to findActiveSDKSession() and replace with direct
database query in SessionCompletionHandler.completeByClaudeId().

This removes dependency on the deleted findActiveSDKSession() method
and simplifies the code by using a straightforward SELECT query.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor: remove markSessionCompleted call from SDKAgent

- Delete call to markSessionCompleted() in SDKAgent.ts
- Session status is no longer tracked or updated
- Part of phase 9: simplifying session management

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor: remove markSessionComplete method (Phase 10)

- Deleted markSessionComplete() method from DatabaseManager
- Removed markSessionComplete call from SessionCompletionHandler
- Session completion status no longer tracked in database
- Part of session management simplification effort

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor: replace deleted updateSDKSessionId calls in import script (Phase 11)

- Replace updateSDKSessionId() calls with direct SQL UPDATE statements
- Method was deleted in Phase 3 as part of session management simplification
- Import script now uses direct database access consistently

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: add validation for SQL updates in sdk_sessions table

* refactor: enhance worker-cli to support manual and automated runs

* Remove cleanup hook and associated session completion logic

- Deleted the cleanup-hook implementation from the hooks directory.
- Removed the session completion endpoint that was used by the cleanup hook.
- Updated the SessionCompletionHandler to eliminate the completeByClaudeId method and its dependencies.
- Adjusted the SessionRoutes to reflect the removal of the session completion route.

* fix: update worker-cli command to use bun for consistency

* feat: Implement timestamp fix for observations and enhance processing logic

- Added `earliestPendingTimestamp` to `ActiveSession` to track the original timestamp of the earliest pending message.
- Updated `SDKAgent` to capture and utilize the earliest pending timestamp during response processing.
- Modified `SessionManager` to track the earliest timestamp when yielding messages.
- Created scripts for fixing corrupted timestamps, validating fixes, and investigating timestamp issues.
- Verified that all corrupted observations have been repaired and logic for future processing is sound.
- Ensured orphan processing can be safely re-enabled after validation.

* feat: Enhance SessionStore to support custom database paths and add timestamp fields for observations and summaries

* Refactor pending queue processing and add management endpoints

- Disabled automatic recovery of orphaned queues on startup; users must now use the new /api/pending-queue/process endpoint.
- Updated processOrphanedQueues method to processPendingQueues with improved session handling and return detailed results.
- Added new API endpoints for managing pending queues: GET /api/pending-queue and POST /api/pending-queue/process.
- Introduced a new script (check-pending-queue.ts) for checking and processing pending observation queues interactively or automatically.
- Enhanced logging and error handling for better monitoring of session processing.

* updated agent sdk

* feat: Add manual recovery guide and queue management endpoints to documentation

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-25 15:36:46 -05:00

107 lines
3.1 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { SessionStore } from '../src/services/sqlite/SessionStore.js';
describe('SessionStore', () => {
let store: SessionStore;
beforeEach(() => {
store = new SessionStore(':memory:');
});
afterEach(() => {
store.close();
});
it('should correctly count user prompts', () => {
const claudeId = 'claude-session-1';
store.createSDKSession(claudeId, 'test-project', 'initial prompt');
// Should be 0 initially
expect(store.getPromptNumberFromUserPrompts(claudeId)).toBe(0);
// Save prompt 1
store.saveUserPrompt(claudeId, 1, 'First prompt');
expect(store.getPromptNumberFromUserPrompts(claudeId)).toBe(1);
// Save prompt 2
store.saveUserPrompt(claudeId, 2, 'Second prompt');
expect(store.getPromptNumberFromUserPrompts(claudeId)).toBe(2);
// Save prompt for another session
store.createSDKSession('claude-session-2', 'test-project', 'initial prompt');
store.saveUserPrompt('claude-session-2', 1, 'Other prompt');
expect(store.getPromptNumberFromUserPrompts(claudeId)).toBe(2);
});
it('should store observation with timestamp override', () => {
const claudeId = 'claude-sess-obs';
const sdkId = store.createSDKSession(claudeId, 'test-project', 'initial prompt');
// Get the sdk_session_id string (createSDKSession returns number ID, need string for FK)
// Wait, createSDKSession inserts using sdk_session_id = claude_session_id in the current implementation
// "VALUES (?, ?, ?, ?, ?, ?, 'active')" -> claudeSessionId, claudeSessionId, ...
const obs = {
type: 'discovery',
title: 'Test Obs',
subtitle: null,
facts: [],
narrative: 'Testing',
concepts: [],
files_read: [],
files_modified: []
};
const pastTimestamp = 1600000000000; // Some time in the past
const result = store.storeObservation(
claudeId, // sdkSessionId is same as claudeSessionId in createSDKSession
'test-project',
obs,
1,
0,
pastTimestamp
);
expect(result.createdAtEpoch).toBe(pastTimestamp);
const stored = store.getObservationById(result.id);
expect(stored).not.toBeNull();
expect(stored?.created_at_epoch).toBe(pastTimestamp);
// Verify ISO string matches
expect(new Date(stored!.created_at).getTime()).toBe(pastTimestamp);
});
it('should store summary with timestamp override', () => {
const claudeId = 'claude-sess-sum';
store.createSDKSession(claudeId, 'test-project', 'initial prompt');
const summary = {
request: 'Do something',
investigated: 'Stuff',
learned: 'Things',
completed: 'Done',
next_steps: 'More',
notes: null
};
const pastTimestamp = 1650000000000;
const result = store.storeSummary(
claudeId,
'test-project',
summary,
1,
0,
pastTimestamp
);
expect(result.createdAtEpoch).toBe(pastTimestamp);
const stored = store.getSummaryForSession(claudeId);
expect(stored).not.toBeNull();
expect(stored?.created_at_epoch).toBe(pastTimestamp);
});
});