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
+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 => {