Refactor context configuration and settings handling

- Updated context configuration loading path from ~/.claude/settings.json to ~/.claude-mem/settings.json.
- Modified the extractPriorMessages function to focus on retrieving the last assistant message only, removing user message extraction.
- Enhanced output formatting for displaying prior assistant messages in the context hook.
- Added new settings related to token economics and observation filtering in the useSettings hook.
This commit is contained in:
Alex Newman
2025-12-01 19:26:33 -05:00
parent 6dc648f07c
commit c768a80bf0
6 changed files with 139 additions and 140 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+59 -79
View File
@@ -52,7 +52,7 @@ interface ContextConfig {
/** /**
* Load all context configuration settings * Load all context configuration settings
* Priority: ~/.claude/settings.json > env var > defaults * Priority: ~/.claude-mem/settings.json > env var > defaults
*/ */
function loadContextConfig(): ContextConfig { function loadContextConfig(): ContextConfig {
const defaults = { const defaults = {
@@ -71,7 +71,7 @@ function loadContextConfig(): ContextConfig {
}; };
try { try {
const settingsPath = path.join(homedir(), '.claude', 'settings.json'); const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
if (!existsSync(settingsPath)) return defaults; if (!existsSync(settingsPath)) return defaults;
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
@@ -221,11 +221,11 @@ function renderSummaryField(label: string, value: string | null, color: string,
// Helper: Convert cwd path to dashed format for transcript directory name // Helper: Convert cwd path to dashed format for transcript directory name
function cwdToDashed(cwd: string): string { function cwdToDashed(cwd: string): string {
// Remove leading slash and convert remaining slashes to dashes // Convert all slashes to dashes (including leading slash)
return cwd.replace(/^\//, '').replace(/\//g, '-'); return cwd.replace(/\//g, '-');
} }
// Helper: Extract last user and assistant messages from transcript file // Helper: Extract last assistant message from transcript file
function extractPriorMessages(transcriptPath: string): { userMessage: string; assistantMessage: string } { function extractPriorMessages(transcriptPath: string): { userMessage: string; assistantMessage: string } {
try { try {
if (!existsSync(transcriptPath)) { if (!existsSync(transcriptPath)) {
@@ -238,16 +238,23 @@ function extractPriorMessages(transcriptPath: string): { userMessage: string; as
} }
const lines = content.split('\n').filter(line => line.trim()); const lines = content.split('\n').filter(line => line.trim());
let lastUserMessage = '';
// Find the last assistant message by filtering for assistant type and taking the last one
let lastAssistantMessage = ''; let lastAssistantMessage = '';
// Parse JSONL backwards to find last user and assistant messages // Iterate backwards to find the most recent assistant message with text content
for (let i = lines.length - 1; i >= 0; i--) { for (let i = lines.length - 1; i >= 0; i--) {
try { try {
const entry = JSON.parse(lines[i]); const line = lines[i];
// Find last assistant message // Quick check if this line is an assistant message
if (!lastAssistantMessage && entry.type === 'assistant' && entry.message?.content) { if (!line.includes('"type":"assistant"')) {
continue;
}
const entry = JSON.parse(line);
if (entry.type === 'assistant' && entry.message?.content && Array.isArray(entry.message.content)) {
let text = ''; let text = '';
for (const block of entry.message.content) { for (const block of entry.message.content) {
if (block.type === 'text') { if (block.type === 'text') {
@@ -258,35 +265,18 @@ function extractPriorMessages(transcriptPath: string): { userMessage: string; as
text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '').trim(); text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '').trim();
if (text) { if (text) {
lastAssistantMessage = text; lastAssistantMessage = text;
break; // Found it, stop searching
} }
} }
// Find last user message
if (!lastUserMessage && entry.type === 'user' && entry.message?.content) {
let text = '';
for (const block of entry.message.content) {
if (block.type === 'text') {
text += block.text;
}
}
if (text) {
lastUserMessage = text;
}
}
// Stop once we have both
if (lastUserMessage && lastAssistantMessage) {
break;
}
} catch (parseError) { } catch (parseError) {
// Skip malformed lines // Skip malformed lines
continue; continue;
} }
} }
return { userMessage: lastUserMessage, assistantMessage: lastAssistantMessage }; return { userMessage: '', assistantMessage: lastAssistantMessage };
} catch (error) { } catch (error) {
logger.debug('HOOK', `Failed to extract prior messages from ${transcriptPath}:`, {}, error as Error); logger.failure('HOOK', `Failed to extract prior messages from transcript`, { transcriptPath }, error as Error);
return { userMessage: '', assistantMessage: '' }; return { userMessage: '', assistantMessage: '' };
} }
} }
@@ -361,6 +351,8 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
// Retrieve prior session messages if enabled // Retrieve prior session messages if enabled
let priorUserMessage = ''; let priorUserMessage = '';
let priorAssistantMessage = ''; let priorAssistantMessage = '';
// let debugInfo: string[] = [];
if (config.showLastMessage && observations.length > 0) { if (config.showLastMessage && observations.length > 0) {
try { try {
const currentSessionId = input?.session_id; const currentSessionId = input?.session_id;
@@ -375,13 +367,27 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
const dashedCwd = cwdToDashed(cwd); const dashedCwd = cwdToDashed(cwd);
const transcriptPath = path.join(homedir(), '.claude', 'projects', dashedCwd, `${priorSessionId}.jsonl`); const transcriptPath = path.join(homedir(), '.claude', 'projects', dashedCwd, `${priorSessionId}.jsonl`);
// debugInfo.push(`📋 Prior Message Retrieval:`);
// debugInfo.push(` Session ID: ${priorSessionId}`);
// debugInfo.push(` Transcript: ${transcriptPath}`);
// debugInfo.push(` Exists: ${existsSync(transcriptPath)}`);
// Extract messages from transcript // Extract messages from transcript
const messages = extractPriorMessages(transcriptPath); const messages = extractPriorMessages(transcriptPath);
priorUserMessage = messages.userMessage; priorUserMessage = messages.userMessage;
priorAssistantMessage = messages.assistantMessage; priorAssistantMessage = messages.assistantMessage;
}
// if (!priorUserMessage && !priorAssistantMessage) {
// debugInfo.push(` ⚠️ No messages extracted from transcript`);
// } else {
// debugInfo.push(` ✅ Found user message: ${!!priorUserMessage}`);
// debugInfo.push(` ✅ Found assistant message: ${!!priorAssistantMessage}`);
// }
} // else {
// debugInfo.push(`📋 Prior Message Retrieval: No prior session found (all observations from current session)`);
// }
} catch (error) { } catch (error) {
logger.debug('HOOK', 'Failed to retrieve prior session messages:', {}, error as Error); // debugInfo.push(`📋 Prior Message Retrieval Error: ${(error as Error).message}`);
} }
} }
@@ -413,37 +419,6 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
output.push(''); output.push('');
} }
// Previously section (last messages from prior session)
if (priorUserMessage || priorAssistantMessage) {
if (useColors) {
output.push(`${colors.bright}${colors.magenta}📋 Previously${colors.reset}`);
output.push('');
if (priorUserMessage) {
output.push(`${colors.dim}User: ${priorUserMessage}${colors.reset}`);
output.push('');
}
if (priorAssistantMessage) {
output.push(`${colors.dim}Assistant: ${priorAssistantMessage}${colors.reset}`);
output.push('');
}
output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`);
output.push('');
} else {
output.push(`**📋 Previously**`);
output.push('');
if (priorUserMessage) {
output.push(`User: ${priorUserMessage}`);
output.push('');
}
if (priorAssistantMessage) {
output.push(`Assistant: ${priorAssistantMessage}`);
output.push('');
}
output.push('---');
output.push('');
}
}
// Chronological Timeline // Chronological Timeline
if (timelineObs.length > 0) { if (timelineObs.length > 0) {
// Legend/Key // Legend/Key
@@ -789,25 +764,21 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
output.push(...renderSummaryField('Next Steps', mostRecentSummary.next_steps, colors.magenta, useColors)); output.push(...renderSummaryField('Next Steps', mostRecentSummary.next_steps, colors.magenta, useColors));
} }
// Show last message from previous session if enabled // Previously section (last assistant message from prior session) - positioned at bottom for chronological sense
// Note: last_assistant_message field would need to be added to session_summaries table if (priorAssistantMessage) {
// For now, this is a placeholder for the feature output.push('');
if (config.showLastMessage && mostRecentSummary) { output.push('---');
// This would require the last_assistant_message field to be populated output.push('');
// The field exists but may not be populated yet in the current implementation if (useColors) {
const lastMessage = (mostRecentSummary as any).last_assistant_message; output.push(`${colors.bright}${colors.magenta}📋 Previously${colors.reset}`);
if (lastMessage) {
output.push(''); output.push('');
if (useColors) { output.push(`${colors.dim}A: ${priorAssistantMessage}${colors.reset}`);
output.push(`${colors.bright}${colors.magenta}💬 Last Message from Previous Session${colors.reset}`); } else {
output.push(`${colors.dim}${lastMessage}${colors.reset}`); output.push(`**📋 Previously**`);
} else {
output.push(`**💬 Last Message from Previous Session**`);
output.push('');
output.push(lastMessage);
}
output.push(''); output.push('');
output.push(`A: ${priorAssistantMessage}`);
} }
output.push('');
} }
// Footer with token savings message (only show if token economics is visible) // Footer with token savings message (only show if token economics is visible)
@@ -823,6 +794,15 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
} }
db.close(); db.close();
// Add debug info directly to output
// if (debugInfo.length > 0) {
// output.push('');
// output.push('---');
// output.push('');
// output.push(...debugInfo);
// }
return output.join('\n').trimEnd(); return output.join('\n').trimEnd();
} }
+2 -2
View File
@@ -889,7 +889,7 @@ export class WorkerService {
*/ */
private handleGetSettings(req: Request, res: Response): void { private handleGetSettings(req: Request, res: Response): void {
try { try {
const settingsPath = path.join(homedir(), '.claude', 'settings.json'); const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
if (!existsSync(settingsPath)) { if (!existsSync(settingsPath)) {
// Return defaults if file doesn't exist // Return defaults if file doesn't exist
@@ -986,7 +986,7 @@ export class WorkerService {
} }
// Read existing settings // Read existing settings
const settingsPath = path.join(homedir(), '.claude', 'settings.json'); const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
let settings: any = { env: {} }; let settings: any = { env: {} };
if (existsSync(settingsPath)) { if (existsSync(settingsPath)) {
+20 -1
View File
@@ -17,7 +17,26 @@ export function useSettings() {
setSettings({ setSettings({
CLAUDE_MEM_MODEL: data.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL, CLAUDE_MEM_MODEL: data.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL,
CLAUDE_MEM_CONTEXT_OBSERVATIONS: data.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS, CLAUDE_MEM_CONTEXT_OBSERVATIONS: data.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS,
CLAUDE_MEM_WORKER_PORT: data.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT CLAUDE_MEM_WORKER_PORT: data.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT,
// Token Economics Display
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: data.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS,
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: data.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS,
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: data.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT,
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: data.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT,
// Observation Filtering
CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: data.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES,
CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: data.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS,
// Display Configuration
CLAUDE_MEM_CONTEXT_FULL_COUNT: data.CLAUDE_MEM_CONTEXT_FULL_COUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_FULL_COUNT,
CLAUDE_MEM_CONTEXT_FULL_FIELD: data.CLAUDE_MEM_CONTEXT_FULL_FIELD || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_FULL_FIELD,
CLAUDE_MEM_CONTEXT_SESSION_COUNT: data.CLAUDE_MEM_CONTEXT_SESSION_COUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SESSION_COUNT,
// Feature Toggles
CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: data.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY,
CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: data.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE,
}); });
}) })
.catch(error => { .catch(error => {