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
* Priority: ~/.claude/settings.json > env var > defaults
* Priority: ~/.claude-mem/settings.json > env var > defaults
*/
function loadContextConfig(): ContextConfig {
const defaults = {
@@ -71,7 +71,7 @@ function loadContextConfig(): ContextConfig {
};
try {
const settingsPath = path.join(homedir(), '.claude', 'settings.json');
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
if (!existsSync(settingsPath)) return defaults;
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
function cwdToDashed(cwd: string): string {
// Remove leading slash and convert remaining slashes to dashes
return cwd.replace(/^\//, '').replace(/\//g, '-');
// Convert all slashes to dashes (including leading slash)
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 } {
try {
if (!existsSync(transcriptPath)) {
@@ -238,16 +238,23 @@ function extractPriorMessages(transcriptPath: string): { userMessage: string; as
}
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 = '';
// 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--) {
try {
const entry = JSON.parse(lines[i]);
const line = lines[i];
// Find last assistant message
if (!lastAssistantMessage && entry.type === 'assistant' && entry.message?.content) {
// Quick check if this line is an assistant message
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 = '';
for (const block of entry.message.content) {
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();
if (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) {
// Skip malformed lines
continue;
}
}
return { userMessage: lastUserMessage, assistantMessage: lastAssistantMessage };
return { userMessage: '', assistantMessage: lastAssistantMessage };
} 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: '' };
}
}
@@ -361,6 +351,8 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
// Retrieve prior session messages if enabled
let priorUserMessage = '';
let priorAssistantMessage = '';
// let debugInfo: string[] = [];
if (config.showLastMessage && observations.length > 0) {
try {
const currentSessionId = input?.session_id;
@@ -375,13 +367,27 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
const dashedCwd = cwdToDashed(cwd);
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
const messages = extractPriorMessages(transcriptPath);
priorUserMessage = messages.userMessage;
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) {
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('');
}
// 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
if (timelineObs.length > 0) {
// 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));
}
// Show last message from previous session if enabled
// Note: last_assistant_message field would need to be added to session_summaries table
// For now, this is a placeholder for the feature
if (config.showLastMessage && mostRecentSummary) {
// This would require the last_assistant_message field to be populated
// The field exists but may not be populated yet in the current implementation
const lastMessage = (mostRecentSummary as any).last_assistant_message;
if (lastMessage) {
// Previously section (last assistant message from prior session) - positioned at bottom for chronological sense
if (priorAssistantMessage) {
output.push('');
output.push('---');
output.push('');
if (useColors) {
output.push(`${colors.bright}${colors.magenta}📋 Previously${colors.reset}`);
output.push('');
if (useColors) {
output.push(`${colors.bright}${colors.magenta}💬 Last Message from Previous Session${colors.reset}`);
output.push(`${colors.dim}${lastMessage}${colors.reset}`);
} else {
output.push(`**💬 Last Message from Previous Session**`);
output.push('');
output.push(lastMessage);
}
output.push(`${colors.dim}A: ${priorAssistantMessage}${colors.reset}`);
} else {
output.push(`**📋 Previously**`);
output.push('');
output.push(`A: ${priorAssistantMessage}`);
}
output.push('');
}
// 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();
// Add debug info directly to output
// if (debugInfo.length > 0) {
// output.push('');
// output.push('---');
// output.push('');
// output.push(...debugInfo);
// }
return output.join('\n').trimEnd();
}
+2 -2
View File
@@ -889,7 +889,7 @@ export class WorkerService {
*/
private handleGetSettings(req: Request, res: Response): void {
try {
const settingsPath = path.join(homedir(), '.claude', 'settings.json');
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
if (!existsSync(settingsPath)) {
// Return defaults if file doesn't exist
@@ -986,7 +986,7 @@ export class WorkerService {
}
// Read existing settings
const settingsPath = path.join(homedir(), '.claude', 'settings.json');
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
let settings: any = { env: {} };
if (existsSync(settingsPath)) {
+20 -1
View File
@@ -17,7 +17,26 @@ export function useSettings() {
setSettings({
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_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 => {