fix(session): Semantic renaming and memory session ID capture for resume

This commit fixes the session ID confusion identified in PR #475:

PROBLEM:
- Using contentSessionId (user's Claude Code session) for SDK resume was wrong
- Memory agent conversation should persist across the entire user session
- Each SDK call was starting fresh, losing memory agent continuity

SOLUTION:
1. Semantic Renaming (clarity):
   - claudeSessionId → contentSessionId (user's observed session)
   - sdkSessionId → memorySessionId (memory agent's session for resume)
   - Database migration 17 renames columns accordingly

2. Memory Session ID Capture:
   - SDKAgent captures session_id from first SDK message
   - Persists to database via updateMemorySessionId()
   - SessionManager loads memorySessionId on session init

3. Resume Logic Fixed:
   - Only resume if memorySessionId captured from previous interaction
   - Enables memory agent continuity across user prompts

Files changed: 33 (types, database, agents, hooks, routes)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2025-12-28 22:19:57 -05:00
parent b8ce27bd31
commit 30b142d318
33 changed files with 809 additions and 718 deletions
+10 -10
View File
@@ -8,7 +8,7 @@ import { logger } from '../../utils/logger.js';
export interface PersistentPendingMessage {
id: number;
session_db_id: number;
claude_session_id: string;
content_session_id: string;
message_type: 'observation' | 'summarize';
tool_name: string | null;
tool_input: string | null;
@@ -53,11 +53,11 @@ export class PendingMessageStore {
* Enqueue a new message (persist before processing)
* @returns The database ID of the persisted message
*/
enqueue(sessionDbId: number, claudeSessionId: string, message: PendingMessage): number {
enqueue(sessionDbId: number, contentSessionId: string, message: PendingMessage): number {
const now = Date.now();
const stmt = this.db.prepare(`
INSERT INTO pending_messages (
session_db_id, claude_session_id, message_type,
session_db_id, content_session_id, message_type,
tool_name, tool_input, tool_response, cwd,
last_user_message, last_assistant_message,
prompt_number, status, retry_count, created_at_epoch
@@ -66,7 +66,7 @@ export class PendingMessageStore {
const result = stmt.run(
sessionDbId,
claudeSessionId,
contentSessionId,
message.type,
message.tool_name || null,
message.tool_input ? JSON.stringify(message.tool_input) : null,
@@ -140,7 +140,7 @@ export class PendingMessageStore {
const stmt = this.db.prepare(`
SELECT pm.*, ss.project
FROM pending_messages pm
LEFT JOIN sdk_sessions ss ON pm.claude_session_id = ss.claude_session_id
LEFT JOIN sdk_sessions ss ON pm.content_session_id = ss.content_session_id
WHERE pm.status IN ('pending', 'processing', 'failed')
ORDER BY
CASE pm.status
@@ -226,7 +226,7 @@ export class PendingMessageStore {
const stmt = this.db.prepare(`
SELECT pm.*, ss.project
FROM pending_messages pm
LEFT JOIN sdk_sessions ss ON pm.claude_session_id = ss.claude_session_id
LEFT JOIN sdk_sessions ss ON pm.content_session_id = ss.content_session_id
WHERE pm.status = 'processed' AND pm.completed_at_epoch > ?
ORDER BY pm.completed_at_epoch DESC
LIMIT ?
@@ -354,12 +354,12 @@ export class PendingMessageStore {
/**
* Get session info for a pending message (for recovery)
*/
getSessionInfoForMessage(messageId: number): { sessionDbId: number; claudeSessionId: string } | null {
getSessionInfoForMessage(messageId: number): { sessionDbId: number; contentSessionId: string } | null {
const stmt = this.db.prepare(`
SELECT session_db_id, claude_session_id FROM pending_messages WHERE id = ?
SELECT session_db_id, content_session_id FROM pending_messages WHERE id = ?
`);
const result = stmt.get(messageId) as { session_db_id: number; claude_session_id: string } | undefined;
return result ? { sessionDbId: result.session_db_id, claudeSessionId: result.claude_session_id } : null;
const result = stmt.get(messageId) as { session_db_id: number; content_session_id: string } | undefined;
return result ? { sessionDbId: result.session_db_id, contentSessionId: result.content_session_id } : null;
}
/**