Refactor code structure for improved readability and maintainability

This commit is contained in:
Alex Newman
2025-10-16 19:50:24 -04:00
parent 307c87b9f6
commit 3e617a8b1e
35 changed files with 3578 additions and 218 deletions
+42 -13
View File
@@ -142,36 +142,60 @@ program
.command('context')
.description('SessionStart hook - show recent session context')
.action(async () => {
const { contextHook } = await import('../hooks/index.js');
const input = await readStdin();
contextHook(JSON.parse(input));
try {
const { contextHook } = await import('../hooks/index.js');
const input = await readStdin();
const data = input.trim() ? JSON.parse(input) : undefined;
contextHook(data);
} catch (error: any) {
console.error(`[claude-mem context] Error: ${error.message}`);
process.exit(0); // Exit gracefully to avoid blocking Claude Code
}
});
program
.command('new')
.description('UserPromptSubmit hook - initialize SDK session')
.action(async () => {
const { newHook } = await import('../hooks/index.js');
const input = await readStdin();
newHook(JSON.parse(input));
try {
const { newHook } = await import('../hooks/index.js');
const input = await readStdin();
const data = input.trim() ? JSON.parse(input) : undefined;
newHook(data);
} catch (error: any) {
console.error(`[claude-mem new] Error: ${error.message}`);
process.exit(0); // Exit gracefully to avoid blocking Claude Code
}
});
program
.command('save')
.description('PostToolUse hook - queue observation')
.action(async () => {
const { saveHook } = await import('../hooks/index.js');
const input = await readStdin();
saveHook(JSON.parse(input));
try {
const { saveHook } = await import('../hooks/index.js');
const input = await readStdin();
const data = input.trim() ? JSON.parse(input) : undefined;
saveHook(data);
} catch (error: any) {
console.error(`[claude-mem save] Error: ${error.message}`);
process.exit(0); // Exit gracefully to avoid blocking Claude Code
}
});
program
.command('summary')
.description('Stop hook - finalize session')
.action(async () => {
const { summaryHook } = await import('../hooks/index.js');
const input = await readStdin();
summaryHook(JSON.parse(input));
try {
const { summaryHook } = await import('../hooks/index.js');
const input = await readStdin();
const data = input.trim() ? JSON.parse(input) : undefined;
summaryHook(data);
} catch (error: any) {
console.error(`[claude-mem summary] Error: ${error.message}`);
process.exit(0); // Exit gracefully to avoid blocking Claude Code
}
});
program
@@ -190,8 +214,13 @@ program
}
});
// Helper function to read stdin
// Helper function to read stdin (Bun-compatible)
async function readStdin(): Promise<string> {
// Use Bun's native stdin.text() if available, otherwise use Node.js streams
if (typeof Bun !== 'undefined' && Bun.stdin) {
return await Bun.stdin.text();
}
return new Promise((resolve) => {
let data = '';
process.stdin.on('data', chunk => {
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bun
/**
* Cleanup Hook Entry Point - SessionEnd
* Standalone executable for plugin hooks
*/
import { cleanupHook } from '../../hooks/cleanup.js';
// Read input from stdin
const input = await Bun.stdin.text();
try {
const parsed = input.trim() ? JSON.parse(input) : undefined;
cleanupHook(parsed);
} catch (error: any) {
console.error(`[claude-mem cleanup-hook error: ${error.message}]`);
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
}
+135
View File
@@ -0,0 +1,135 @@
import { existsSync, unlinkSync } from 'fs';
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
import { getWorkerSocketPath } from '../shared/paths.js';
export interface SessionEndInput {
session_id: string;
cwd: string;
transcript_path?: string;
hook_event_name: string;
reason: 'exit' | 'clear' | 'logout' | 'prompt_input_exit' | 'other';
}
/**
* Cleanup Hook - SessionEnd
* Cleans up worker process and marks session as terminated
*
* This hook runs when a Claude Code session ends. It:
* 1. Finds active SDK session for this Claude session
* 2. Terminates worker process if still running
* 3. Removes stale socket file
* 4. Marks session as failed (since no Stop hook completed it)
*/
export function cleanupHook(input?: SessionEndInput): void {
try {
// Log hook entry point
console.error('[claude-mem cleanup] Hook fired', {
input: input ? {
session_id: input.session_id,
cwd: input.cwd,
reason: input.reason
} : null
});
// Handle standalone execution (no input provided)
if (!input) {
console.log('No input provided - this script is designed to run as a Claude Code SessionEnd hook');
console.log('\nExpected input format:');
console.log(JSON.stringify({
session_id: "string",
cwd: "string",
transcript_path: "string",
hook_event_name: "SessionEnd",
reason: "exit"
}, null, 2));
process.exit(0);
}
const { session_id, reason } = input;
console.error('[claude-mem cleanup] Searching for active SDK session', { session_id, reason });
// Find active SDK session
const db = new HooksDatabase();
const session = db.findActiveSDKSession(session_id);
if (!session) {
// No active session - nothing to clean up
console.error('[claude-mem cleanup] No active SDK session found', { session_id });
db.close();
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
}
console.error('[claude-mem cleanup] Active SDK session found', {
session_id: session.id,
sdk_session_id: session.sdk_session_id,
project: session.project
});
// Get worker PID and socket path
const socketPath = getWorkerSocketPath(session.id);
// 1. Kill worker process if it exists
try {
// Try to read PID from socket file existence
if (existsSync(socketPath)) {
console.error('[claude-mem cleanup] Socket file exists, attempting cleanup', { socketPath });
// Remove socket file
try {
unlinkSync(socketPath);
console.error('[claude-mem cleanup] Socket file removed successfully', { socketPath });
} catch (unlinkErr: any) {
console.error('[claude-mem cleanup] Failed to remove socket file', {
error: unlinkErr.message,
socketPath
});
}
} else {
console.error('[claude-mem cleanup] Socket file does not exist', { socketPath });
}
// Note: We don't kill the worker process here because:
// 1. Workers have a 2-hour watchdog timer that will kill them automatically
// 2. Killing by PID is fragile (PID might be reused)
// 3. The worker will exit on its own when it can't reach the socket
// We just clean up the socket file to prevent stale socket issues
} catch (cleanupErr: any) {
console.error('[claude-mem cleanup] Error during cleanup', {
error: cleanupErr.message,
stack: cleanupErr.stack
});
}
// 2. Mark session as failed (since Stop hook didn't complete it)
try {
db.markSessionFailed(session.id);
console.error('[claude-mem cleanup] Session marked as failed', {
session_id: session.id,
reason: 'SessionEnd hook - session terminated without completion'
});
} catch (markErr: any) {
console.error('[claude-mem cleanup] Failed to mark session as failed', {
error: markErr.message,
session_id: session.id
});
}
db.close();
console.error('[claude-mem cleanup] Cleanup completed successfully');
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
} catch (error: any) {
// On error, don't block Claude Code exit
console.error('[claude-mem cleanup] Unexpected error in hook', {
error: error.message,
stack: error.stack,
name: error.name
});
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
}
}
+64 -13
View File
@@ -3,8 +3,9 @@ import path from 'path';
export interface SessionStartInput {
session_id: string;
cwd: string;
source?: string;
transcript_path: string;
hook_event_name: string;
source: "startup" | "resume" | "clear" | "compact";
[key: string]: any;
}
@@ -14,37 +15,59 @@ export interface SessionStartInput {
*/
export function contextHook(input?: SessionStartInput): void {
try {
// Log hook invocation
console.error('[claude-mem context] Hook fired with input:', JSON.stringify({
session_id: input?.session_id,
transcript_path: input?.transcript_path,
hook_event_name: input?.hook_event_name,
source: input?.source,
has_input: !!input
}));
// Handle standalone execution (no input provided)
if (!input) {
console.error('[claude-mem context] No input provided - exiting (standalone mode)');
console.log('No input provided - this script is designed to run as a Claude Code SessionStart hook');
process.exit(0);
}
// Only run on startup (not on resume)
if (input.source && input.source !== 'startup') {
console.log(''); // Output nothing, just exit
process.exit(0);
}
// Extract project from cwd
const project = path.basename(input.cwd);
// Extract project from transcript_path
// Path format: ~/.claude/projects/{project-name}/{session-id}.jsonl
const transcriptDir = path.dirname(input.transcript_path);
const project = path.basename(transcriptDir);
console.error('[claude-mem context] Extracted project name:', project, 'from transcript_path:', input.transcript_path);
// Get recent summaries
console.error('[claude-mem context] Querying database for recent summaries...');
const db = new HooksDatabase();
const summaries = db.getRecentSummaries(project, 5);
db.close();
console.error('[claude-mem context] Database query complete - found', summaries.length, 'summaries');
// Log preview of each summary found
if (summaries.length > 0) {
console.error('[claude-mem context] Summary previews:');
summaries.forEach((summary, idx) => {
const preview = summary.request?.substring(0, 100) || summary.completed?.substring(0, 100) || '(no content)';
console.error(` [${idx + 1}]`, preview + (preview.length >= 100 ? '...' : ''));
});
}
// If no summaries, provide helpful message
if (summaries.length === 0) {
console.error('[claude-mem context] No summaries found - outputting empty context message');
console.log('# Recent Session Context\n\nNo previous sessions found for this project yet.');
process.exit(0);
}
// Format output for Claude
console.error('[claude-mem context] Building markdown context from summaries...');
const output: string[] = [];
output.push('# Recent Session Context');
output.push('');
output.push(`Here's what happened in recent ${project} sessions:`);
const sessionWord = summaries.length === 1 ? 'session' : 'sessions';
output.push(`Showing last ${summaries.length} ${sessionWord} for **${project}**:`);
output.push('');
for (const summary of summaries) {
@@ -67,6 +90,22 @@ export function contextHook(input?: SessionStartInput): void {
output.push(`**Next Steps:** ${summary.next_steps}`);
}
// Show files that were read during the session
if (summary.files_read) {
try {
const files = JSON.parse(summary.files_read);
if (Array.isArray(files) && files.length > 0) {
output.push(`**Files Read:** ${files.join(', ')}`);
}
} catch {
// Backwards compatibility: if not valid JSON, show as text
if (summary.files_read.trim()) {
output.push(`**Files Read:** ${summary.files_read}`);
}
}
}
// Show files that were edited/written during the session
if (summary.files_edited) {
try {
const files = JSON.parse(summary.files_edited);
@@ -85,13 +124,25 @@ export function contextHook(input?: SessionStartInput): void {
output.push('');
}
// Log details about the markdown output
const markdownOutput = output.join('\n');
console.error('[claude-mem context] Markdown built successfully');
console.error('[claude-mem context] Output length:', markdownOutput.length, 'characters,', output.length, 'lines');
console.error('[claude-mem context] Output preview (first 200 chars):', markdownOutput.substring(0, 200) + '...');
console.error('[claude-mem context] Outputting context to stdout for Claude Code injection');
// Output to stdout for Claude Code to inject
console.log(output.join('\n'));
console.log(markdownOutput);
console.error('[claude-mem context] Context hook completed successfully');
process.exit(0);
} catch (error: any) {
// On error, exit silently - don't block Claude Code
console.error(`[claude-mem context error: ${error.message}]`);
console.error('[claude-mem context] ERROR occurred during context hook execution');
console.error('[claude-mem context] Error message:', error.message);
console.error('[claude-mem context] Error stack:', error.stack);
console.error('[claude-mem context] Exiting gracefully to avoid blocking Claude Code');
process.exit(0);
}
}
+30 -2
View File
@@ -14,6 +14,11 @@ export interface StopInput {
*/
export function summaryHook(input?: StopInput): void {
try {
// Log hook entry point
console.error('[claude-mem summary] Hook fired', {
input: input ? { session_id: input.session_id, cwd: input.cwd } : null
});
// Handle standalone execution (no input provided)
if (!input) {
console.log('No input provided - this script is designed to run as a Claude Code Stop hook');
@@ -26,6 +31,7 @@ export function summaryHook(input?: StopInput): void {
}
const { session_id } = input;
console.error('[claude-mem summary] Searching for active SDK session', { session_id });
// Find active SDK session
const db = new HooksDatabase();
@@ -34,10 +40,17 @@ export function summaryHook(input?: StopInput): void {
if (!session) {
// No active session - nothing to finalize
console.error('[claude-mem summary] No active SDK session found', { session_id });
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
}
console.error('[claude-mem summary] Active SDK session found', {
session_id: session.id,
collection_name: session.collection_name,
worker_pid: session.worker_pid
});
// Get socket path
const socketPath = getWorkerSocketPath(session.id);
@@ -46,25 +59,40 @@ export function summaryHook(input?: StopInput): void {
type: 'finalize'
};
console.error('[claude-mem summary] Attempting to send FINALIZE message to worker socket', {
socketPath,
message
});
const client = net.connect(socketPath, () => {
console.error('[claude-mem summary] Socket connection established, sending message');
client.write(JSON.stringify(message) + '\n');
client.end();
});
client.on('error', (err) => {
// Socket not available - worker may have already finished or crashed
console.error(`[claude-mem summary] Socket error: ${err.message}`);
console.error('[claude-mem summary] Socket error occurred', {
error: err.message,
code: (err as any).code,
socketPath
});
// Continue anyway, don't block Claude
});
client.on('close', () => {
console.error('[claude-mem summary] Socket connection closed successfully');
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
});
} catch (error: any) {
// On error, don't block Claude Code
console.error(`[claude-mem summary error: ${error.message}]`);
console.error('[claude-mem summary] Unexpected error in hook', {
error: error.message,
stack: error.stack,
name: error.name
});
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
}
+239 -22
View File
@@ -4,9 +4,17 @@
* Background server that processes tool observations via Unix socket
*/
// Bun-specific ImportMeta extension
declare global {
interface ImportMeta {
main: boolean;
}
}
import net from 'net';
import { unlinkSync, existsSync } from 'fs';
import { query } from '@anthropic-ai/claude-agent-sdk';
import type { SDKUserMessage, SDKSystemMessage } from '@anthropic-ai/claude-agent-sdk';
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
import { getWorkerSocketPath } from '../shared/paths.js';
import { buildInitPrompt, buildObservationPrompt, buildFinalizePrompt } from './prompts.js';
@@ -67,37 +75,70 @@ class SDKWorker {
this.db = new HooksDatabase();
this.abortController = new AbortController();
this.socketPath = getWorkerSocketPath(sessionDbId);
console.error('[claude-mem worker] Worker instance created', {
sessionDbId,
socketPath: this.socketPath
});
}
/**
* Main run loop
*/
async run(): Promise<void> {
console.error('[claude-mem worker] Worker run() started', {
sessionDbId: this.sessionDbId,
socketPath: this.socketPath
});
try {
// Load session info
const session = await this.loadSession();
if (!session) {
console.error('[SDK Worker] Session not found');
console.error('[claude-mem worker] Session not found in database', {
sessionDbId: this.sessionDbId
});
process.exit(1);
}
console.error('[claude-mem worker] Session loaded successfully', {
sessionDbId: this.sessionDbId,
project: session.project,
sdkSessionId: session.sdk_session_id,
userPromptLength: session.user_prompt?.length || 0
});
this.project = session.project;
this.userPrompt = session.user_prompt;
// Start Unix socket server
await this.startSocketServer();
console.error(`[SDK Worker] Socket server listening: ${this.socketPath}`);
console.error('[claude-mem worker] Socket server started successfully', {
socketPath: this.socketPath,
sessionDbId: this.sessionDbId
});
// Run SDK agent with streaming input
console.error('[claude-mem worker] Starting SDK agent', {
sessionDbId: this.sessionDbId,
model: MODEL
});
await this.runSDKAgent();
// Mark session as completed
console.error('[claude-mem worker] SDK agent completed, marking session as completed', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId
});
this.db.markSessionCompleted(this.sessionDbId);
this.db.close();
this.cleanup();
} catch (error: any) {
console.error('[SDK Worker] Error:', error.message);
console.error('[claude-mem worker] Fatal error in run()', {
sessionDbId: this.sessionDbId,
error: error.message,
stack: error.stack
});
this.db.markSessionFailed(this.sessionDbId);
this.db.close();
this.cleanup();
@@ -121,9 +162,17 @@ class SDKWorker {
return new Promise((resolve, reject) => {
console.error(`[SDK Worker DEBUG] Creating net server...`);
this.server = net.createServer((socket) => {
console.error('[claude-mem worker] Socket connection received', {
sessionDbId: this.sessionDbId,
socketPath: this.socketPath
});
let buffer = '';
socket.on('data', (chunk) => {
console.error('[claude-mem worker] Data received on socket', {
sessionDbId: this.sessionDbId,
chunkSize: chunk.length
});
buffer += chunk.toString();
// Try to parse complete JSON messages (separated by newlines)
@@ -134,22 +183,45 @@ class SDKWorker {
if (line.trim()) {
try {
const message: WorkerMessage = JSON.parse(line);
console.error('[claude-mem worker] Message received from socket', {
sessionDbId: this.sessionDbId,
messageType: message.type,
rawMessage: line.substring(0, 500) // Truncate to avoid massive logs
});
this.handleMessage(message);
} catch (err) {
console.error('[SDK Worker] Invalid message:', line);
console.error('[claude-mem worker] Invalid message - failed to parse JSON', {
sessionDbId: this.sessionDbId,
error: err instanceof Error ? err.message : String(err),
rawLine: line.substring(0, 200)
});
}
}
}
});
socket.on('error', (err) => {
console.error('[SDK Worker] Socket connection error:', err.message);
console.error('[claude-mem worker] Socket connection error', {
sessionDbId: this.sessionDbId,
error: err.message,
stack: err.stack
});
});
});
this.server.on('error', (err: any) => {
if (err.code === 'EADDRINUSE') {
console.error(`[SDK Worker] Socket already in use: ${this.socketPath}`);
console.error('[claude-mem worker] Socket already in use', {
socketPath: this.socketPath,
sessionDbId: this.sessionDbId
});
} else {
console.error('[claude-mem worker] Server error', {
sessionDbId: this.sessionDbId,
error: err.message,
code: err.code,
stack: err.stack
});
}
reject(err);
});
@@ -166,10 +238,27 @@ class SDKWorker {
* Handle incoming message from hook
*/
private handleMessage(message: WorkerMessage): void {
console.error('[claude-mem worker] Processing message in handleMessage()', {
sessionDbId: this.sessionDbId,
messageType: message.type,
pendingMessagesCount: this.pendingMessages.length
});
this.pendingMessages.push(message);
if (message.type === 'finalize') {
this.isFinalized = true;
console.error('[claude-mem worker] FINALIZE message detected - queued for processing', {
sessionDbId: this.sessionDbId,
pendingMessagesCount: this.pendingMessages.length
});
// DON'T set isFinalized here - let the generator set it after yielding finalize prompt
} else if (message.type === 'observation') {
console.error('[claude-mem worker] Observation message queued', {
sessionDbId: this.sessionDbId,
toolName: message.tool_name,
inputLength: message.tool_input?.length || 0,
outputLength: message.tool_output?.length || 0
});
}
}
@@ -197,38 +286,68 @@ class SDKWorker {
const claudePath = process.env.CLAUDE_CODE_PATH || '/Users/alexnewman/.nvm/versions/node/v24.5.0/bin/claude';
console.error(`[SDK Worker DEBUG] About to call query with claudePath: ${claudePath}`);
await query({
const queryResult = query({
prompt: this.createMessageGenerator(),
options: {
model: MODEL,
disallowedTools: DISALLOWED_TOOLS,
abortController: this.abortController,
pathToClaudeCodeExecutable: claudePath,
onSystemInitMessage: (msg) => {
// Capture SDK session ID from init message
if (msg.session_id) {
this.sdkSessionId = msg.session_id;
this.db.updateSDKSessionId(this.sessionDbId, msg.session_id);
}
},
onAgentMessage: (msg) => {
// Parse and store observations from agent response
this.handleAgentMessage(msg.content);
}
pathToClaudeCodeExecutable: claudePath
}
});
// Iterate over SDK messages
for await (const message of queryResult) {
// Handle system init message to capture session ID
if (message.type === 'system' && message.subtype === 'init') {
const systemMsg = message as SDKSystemMessage;
if (systemMsg.session_id) {
console.error('[claude-mem worker] SDK session initialized', {
sessionDbId: this.sessionDbId,
sdkSessionId: systemMsg.session_id
});
this.sdkSessionId = systemMsg.session_id;
this.db.updateSDKSessionId(this.sessionDbId, systemMsg.session_id);
}
}
// Handle assistant messages
else if (message.type === 'assistant') {
const content = message.message.content;
// Extract text content from message
const textContent = Array.isArray(content)
? content.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('\n')
: typeof content === 'string' ? content : '';
console.error('[claude-mem worker] SDK agent response received', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId,
contentLength: textContent.length,
contentPreview: textContent.substring(0, 200)
});
// Parse and store observations from agent response
this.handleAgentMessage(textContent);
}
}
}
/**
* Create async message generator for SDK streaming input
* Now pulls from socket messages instead of polling database
*/
private async* createMessageGenerator(): AsyncIterable<{ type: 'user'; message: { role: 'user'; content: string } }> {
private async* createMessageGenerator(): AsyncIterable<SDKUserMessage> {
// Yield initial prompt
const claudeSessionId = `session-${this.sessionDbId}`;
const initPrompt = buildInitPrompt(this.project, claudeSessionId, this.userPrompt);
console.error('[claude-mem worker] Yielding initial prompt to SDK agent', {
sessionDbId: this.sessionDbId,
claudeSessionId,
project: this.project,
promptLength: initPrompt.length
});
yield {
type: 'user',
session_id: this.sdkSessionId || claudeSessionId,
parent_tool_use_id: null,
message: {
role: 'user',
content: initPrompt
@@ -248,17 +367,33 @@ class SDKWorker {
const message = this.pendingMessages.shift()!;
if (message.type === 'finalize') {
console.error('[claude-mem worker] Processing FINALIZE message in generator', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId
});
this.isFinalized = true;
const session = await this.loadSession();
if (session) {
const finalizePrompt = buildFinalizePrompt(session);
console.error('[claude-mem worker] Yielding finalize prompt to SDK agent', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId,
promptLength: finalizePrompt.length,
promptPreview: finalizePrompt.substring(0, 300)
});
yield {
type: 'user',
session_id: this.sdkSessionId || claudeSessionId,
parent_tool_use_id: null,
message: {
role: 'user',
content: finalizePrompt
}
};
} else {
console.error('[claude-mem worker] Failed to load session for finalize prompt', {
sessionDbId: this.sessionDbId
});
}
break;
}
@@ -266,12 +401,21 @@ class SDKWorker {
if (message.type === 'observation') {
// Build observation prompt
const observationPrompt = buildObservationPrompt({
id: 0, // Not needed for prompt generation
tool_name: message.tool_name,
tool_input: message.tool_input,
tool_output: message.tool_output
tool_output: message.tool_output,
created_at_epoch: Date.now()
});
console.error('[claude-mem worker] Yielding observation prompt to SDK agent', {
sessionDbId: this.sessionDbId,
toolName: message.tool_name,
promptLength: observationPrompt.length
});
yield {
type: 'user',
session_id: this.sdkSessionId || claudeSessionId,
parent_tool_use_id: null,
message: {
role: 'user',
content: observationPrompt
@@ -286,17 +430,58 @@ class SDKWorker {
* Handle agent message and parse observations/summaries
*/
private handleAgentMessage(content: string): void {
console.error('[claude-mem worker] Parsing agent message for observations and summary', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId,
contentLength: content.length
});
// Parse observations
const observations = parseObservations(content);
console.error('[claude-mem worker] Observations parsed from response', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId,
observationCount: observations.length
});
for (const obs of observations) {
if (this.sdkSessionId) {
console.error('[claude-mem worker] Storing observation in database', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId,
project: this.project,
observationType: obs.type,
observationTextLength: obs.text?.length || 0
});
this.db.storeObservation(this.sdkSessionId, this.project, obs.type, obs.text);
} else {
console.error('[claude-mem worker] Cannot store observation - no SDK session ID', {
sessionDbId: this.sessionDbId,
observationType: obs.type
});
}
}
// Parse summary (if present)
console.error('[claude-mem worker] Attempting to parse summary from response', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId
});
const summary = parseSummary(content);
if (summary && this.sdkSessionId) {
console.error('[claude-mem worker] Summary parsed successfully', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId,
project: this.project,
hasRequest: !!summary.request,
hasInvestigated: !!summary.investigated,
hasLearned: !!summary.learned,
hasCompleted: !!summary.completed,
filesReadCount: summary.files_read?.length || 0,
filesEditedCount: summary.files_edited?.length || 0
});
// Convert file arrays to JSON strings
const summaryWithArrays = {
request: summary.request,
@@ -309,7 +494,28 @@ class SDKWorker {
notes: summary.notes
};
console.error('[claude-mem worker] Storing summary in database', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId,
project: this.project
});
this.db.storeSummary(this.sdkSessionId, this.project, summaryWithArrays);
console.error('[claude-mem worker] Summary stored successfully in database', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId,
project: this.project
});
} else if (summary && !this.sdkSessionId) {
console.error('[claude-mem worker] Summary parsed but cannot store - no SDK session ID', {
sessionDbId: this.sessionDbId
});
} else {
console.error('[claude-mem worker] No summary found in response', {
sessionDbId: this.sessionDbId,
sdkSessionId: this.sdkSessionId
});
}
}
@@ -317,12 +523,23 @@ class SDKWorker {
* Cleanup socket server and socket file
*/
private cleanup(): void {
console.error('[claude-mem worker] Cleaning up worker resources', {
sessionDbId: this.sessionDbId,
socketPath: this.socketPath,
hasServer: !!this.server,
socketExists: existsSync(this.socketPath)
});
if (this.server) {
this.server.close();
}
if (existsSync(this.socketPath)) {
unlinkSync(this.socketPath);
}
console.error('[claude-mem worker] Cleanup complete', {
sessionDbId: this.sessionDbId
});
}
/**