@calmhive/calmhive-cli
Version:
🐝 Intelligent Claude CLI wrapper with rule injection, AFk background processing, voice interface, and adaptive retry
169 lines (146 loc) • 5.12 kB
JavaScript
/**
* Compact Handler for Claude Code
* Handles context compression more robustly
*/
const fs = require('fs');
const path = require('path');
class CompactHandler {
constructor() {
this.contextIndicators = [
'Prompt is too long',
'Context low',
'Run /compact to compact',
'context limit',
'Message too long',
'approaching context limit'
];
}
/**
* Check if output indicates context limit
*/
needsCompact(output) {
const lowerOutput = output.toLowerCase();
return this.contextIndicators.some(indicator =>
lowerOutput.includes(indicator.toLowerCase())
);
}
/**
* Send compact command with proper formatting
*/
async sendCompactCommand(process) {
try {
// Try multiple approaches
const commands = [
'/compact\n', // Basic command
'\n/compact\n', // With newline prefix
'/compact\r\n', // With carriage return
'compact\n', // Without slash
'\x03/compact\n' // With interrupt character
];
for (const cmd of commands) {
console.log(`📦 Attempting compact with format: ${JSON.stringify(cmd)}`);
process.stdin.write(cmd);
// Give it time to process
await new Promise(resolve => setTimeout(resolve, 1000));
// Check if it worked by looking for response
// This would need to be integrated with stdout monitoring
}
return true;
} catch (error) {
console.error(`⚠️ Failed to send compact command: ${error.message}`);
return false;
}
}
/**
* Alternative: Save context and restart with compressed history
*/
async compressContextManually(sessionId, conversationHistory) {
try {
const compressedHistory = this.compressHistory(conversationHistory);
const contextFile = path.join(process.env.HOME, '.claude', 'afk_registry', sessionId, 'compressed_context.json');
fs.writeFileSync(contextFile, JSON.stringify({
timestamp: new Date().toISOString(),
compressed: true,
compressedHistory: compressedHistory, // Actually use the compressed history
summary: this.generateSummary(conversationHistory),
keyPoints: this.extractKeyPoints(conversationHistory),
recentContext: conversationHistory.slice(-5) // Keep last 5 exchanges
}, null, 2));
return contextFile;
} catch (error) {
console.error(`⚠️ Failed to compress context: ${error.message}`);
return null;
}
}
/**
* Compress conversation history
*/
compressHistory(history) {
// Simple compression: keep system messages and summarize user/assistant exchanges
const compressed = [];
let currentExchange = null;
for (const msg of history) {
if (msg.role === 'system') {
compressed.push(msg);
} else if (msg.role === 'user') {
currentExchange = { user: msg.content, assistant: null };
} else if (msg.role === 'assistant' && currentExchange) {
currentExchange.assistant = msg.content;
// Summarize if too long
if (currentExchange.user.length + currentExchange.assistant.length > 1000) {
compressed.push({
role: 'summary',
content: `User asked about: ${this.summarize(currentExchange.user)}. Assistant provided: ${this.summarize(currentExchange.assistant)}`
});
} else {
compressed.push({ role: 'user', content: currentExchange.user });
compressed.push({ role: 'assistant', content: currentExchange.assistant });
}
currentExchange = null;
}
}
return compressed;
}
/**
* Simple summarization
*/
summarize(text) {
if (text.length < 200) {return text;}
// Take first and last parts
return text.substring(0, 100) + '...' + text.substring(text.length - 100);
}
/**
* Extract key points from conversation
*/
extractKeyPoints(history) {
const keyPoints = [];
for (const msg of history) {
if (msg.content && msg.content.includes('TODO:')) {
keyPoints.push(`TODO: ${msg.content.split('TODO:')[1].split('\n')[0]}`);
}
if (msg.content && msg.content.includes('ERROR:')) {
keyPoints.push(`ERROR: ${msg.content.split('ERROR:')[1].split('\n')[0]}`);
}
if (msg.content && msg.content.includes('FIXED:')) {
keyPoints.push(`FIXED: ${msg.content.split('FIXED:')[1].split('\n')[0]}`);
}
}
return keyPoints;
}
/**
* Generate summary of conversation
*/
generateSummary(history) {
const userMessages = history.filter(m => m.role === 'user').length;
const assistantMessages = history.filter(m => m.role === 'assistant').length;
const totalChars = history.reduce((sum, m) => sum + (m.content?.length || 0), 0);
return {
exchanges: Math.min(userMessages, assistantMessages),
totalMessages: history.length,
totalCharacters: totalChars,
timestamp: new Date().toISOString()
};
}
}
module.exports = CompactHandler;