mirror of
https://github.com/thedotmack/claude-mem
synced 2026-04-25 17:15:04 +02:00
* Refactor worker version checks and increase timeout settings - Updated the default hook timeout from 5000ms to 120000ms for improved stability. - Modified the worker version check to log a warning instead of restarting the worker on version mismatch. - Removed legacy PM2 cleanup and worker start logic, simplifying the ensureWorkerRunning function. - Enhanced polling mechanism for worker readiness with increased retries and reduced interval. * feat: implement worker queue polling to ensure processing completion before proceeding * refactor: change worker command from start to restart in hooks configuration * refactor: remove session management complexity - Simplify createSDKSession to pure INSERT OR IGNORE - Remove auto-create logic from storeObservation/storeSummary - Delete 11 unused session management methods - Derive prompt_number from user_prompts count - Keep sdk_sessions table schema unchanged for compatibility * refactor: simplify session management by removing unused methods and auto-creation logic * Refactor session prompt number retrieval in SessionRoutes - Updated the method of obtaining the prompt number from the session. - Replaced `store.getPromptCounter(sessionDbId)` with `store.getPromptNumberFromUserPrompts(claudeSessionId)` for better clarity and accuracy. - Adjusted the logic for incrementing the prompt number to derive it from the user prompts count instead of directly incrementing a counter. * refactor: replace getPromptCounter with getPromptNumberFromUserPrompts in SessionManager Phase 7 of session management simplification. Updates SessionManager to derive prompt numbers from user_prompts table count instead of using the deprecated prompt_counter column. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: simplify SessionCompletionHandler to use direct SQL query Phase 8: Remove call to findActiveSDKSession() and replace with direct database query in SessionCompletionHandler.completeByClaudeId(). This removes dependency on the deleted findActiveSDKSession() method and simplifies the code by using a straightforward SELECT query. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: remove markSessionCompleted call from SDKAgent - Delete call to markSessionCompleted() in SDKAgent.ts - Session status is no longer tracked or updated - Part of phase 9: simplifying session management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: remove markSessionComplete method (Phase 10) - Deleted markSessionComplete() method from DatabaseManager - Removed markSessionComplete call from SessionCompletionHandler - Session completion status no longer tracked in database - Part of session management simplification effort 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: replace deleted updateSDKSessionId calls in import script (Phase 11) - Replace updateSDKSessionId() calls with direct SQL UPDATE statements - Method was deleted in Phase 3 as part of session management simplification - Import script now uses direct database access consistently 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * test: add validation for SQL updates in sdk_sessions table * refactor: enhance worker-cli to support manual and automated runs * Remove cleanup hook and associated session completion logic - Deleted the cleanup-hook implementation from the hooks directory. - Removed the session completion endpoint that was used by the cleanup hook. - Updated the SessionCompletionHandler to eliminate the completeByClaudeId method and its dependencies. - Adjusted the SessionRoutes to reflect the removal of the session completion route. * fix: update worker-cli command to use bun for consistency * feat: Implement timestamp fix for observations and enhance processing logic - Added `earliestPendingTimestamp` to `ActiveSession` to track the original timestamp of the earliest pending message. - Updated `SDKAgent` to capture and utilize the earliest pending timestamp during response processing. - Modified `SessionManager` to track the earliest timestamp when yielding messages. - Created scripts for fixing corrupted timestamps, validating fixes, and investigating timestamp issues. - Verified that all corrupted observations have been repaired and logic for future processing is sound. - Ensured orphan processing can be safely re-enabled after validation. * feat: Enhance SessionStore to support custom database paths and add timestamp fields for observations and summaries * Refactor pending queue processing and add management endpoints - Disabled automatic recovery of orphaned queues on startup; users must now use the new /api/pending-queue/process endpoint. - Updated processOrphanedQueues method to processPendingQueues with improved session handling and return detailed results. - Added new API endpoints for managing pending queues: GET /api/pending-queue and POST /api/pending-queue/process. - Introduced a new script (check-pending-queue.ts) for checking and processing pending observation queues interactively or automatically. - Enhanced logging and error handling for better monitoring of session processing. * updated agent sdk * feat: Add manual recovery guide and queue management endpoints to documentation --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
242 lines
7.3 KiB
TypeScript
242 lines
7.3 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Check and process pending observation queue
|
|
*
|
|
* Usage:
|
|
* bun scripts/check-pending-queue.ts # Check status and prompt to process
|
|
* bun scripts/check-pending-queue.ts --process # Auto-process without prompting
|
|
* bun scripts/check-pending-queue.ts --limit 5 # Process up to 5 sessions
|
|
*/
|
|
|
|
const WORKER_URL = 'http://localhost:37777';
|
|
|
|
interface QueueMessage {
|
|
id: number;
|
|
session_db_id: number;
|
|
message_type: string;
|
|
tool_name: string | null;
|
|
status: 'pending' | 'processing' | 'failed';
|
|
retry_count: number;
|
|
created_at_epoch: number;
|
|
project: string | null;
|
|
}
|
|
|
|
interface QueueResponse {
|
|
queue: {
|
|
messages: QueueMessage[];
|
|
totalPending: number;
|
|
totalProcessing: number;
|
|
totalFailed: number;
|
|
stuckCount: number;
|
|
};
|
|
recentlyProcessed: QueueMessage[];
|
|
sessionsWithPendingWork: number[];
|
|
}
|
|
|
|
interface ProcessResponse {
|
|
success: boolean;
|
|
totalPendingSessions: number;
|
|
sessionsStarted: number;
|
|
sessionsSkipped: number;
|
|
startedSessionIds: number[];
|
|
}
|
|
|
|
async function checkWorkerHealth(): Promise<boolean> {
|
|
try {
|
|
const res = await fetch(`${WORKER_URL}/api/health`);
|
|
return res.ok;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function getQueueStatus(): Promise<QueueResponse> {
|
|
const res = await fetch(`${WORKER_URL}/api/pending-queue`);
|
|
if (!res.ok) {
|
|
throw new Error(`Failed to get queue status: ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
async function processQueue(limit: number): Promise<ProcessResponse> {
|
|
const res = await fetch(`${WORKER_URL}/api/pending-queue/process`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ sessionLimit: limit })
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error(`Failed to process queue: ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
function formatAge(epochMs: number): string {
|
|
const ageMs = Date.now() - epochMs;
|
|
const minutes = Math.floor(ageMs / 60000);
|
|
const hours = Math.floor(minutes / 60);
|
|
const days = Math.floor(hours / 24);
|
|
|
|
if (days > 0) return `${days}d ${hours % 24}h ago`;
|
|
if (hours > 0) return `${hours}h ${minutes % 60}m ago`;
|
|
return `${minutes}m ago`;
|
|
}
|
|
|
|
async function prompt(question: string): Promise<string> {
|
|
// Check if we have a TTY for interactive input
|
|
if (!process.stdin.isTTY) {
|
|
console.log(question + '(no TTY, use --process flag for non-interactive mode)');
|
|
return 'n';
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
process.stdout.write(question);
|
|
process.stdin.setRawMode(false);
|
|
process.stdin.resume();
|
|
process.stdin.once('data', (data) => {
|
|
process.stdin.pause();
|
|
resolve(data.toString().trim());
|
|
});
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
|
|
// Help flag
|
|
if (args.includes('--help') || args.includes('-h')) {
|
|
console.log(`
|
|
Claude-Mem Pending Queue Manager
|
|
|
|
Check and process pending observation queue backlog.
|
|
|
|
Usage:
|
|
bun scripts/check-pending-queue.ts [options]
|
|
|
|
Options:
|
|
--help, -h Show this help message
|
|
--process Auto-process without prompting
|
|
--limit N Process up to N sessions (default: 10)
|
|
|
|
Examples:
|
|
# Check queue status interactively
|
|
bun scripts/check-pending-queue.ts
|
|
|
|
# Auto-process up to 10 sessions
|
|
bun scripts/check-pending-queue.ts --process
|
|
|
|
# Process up to 5 sessions
|
|
bun scripts/check-pending-queue.ts --process --limit 5
|
|
|
|
What is this for?
|
|
If the claude-mem worker crashes or restarts, pending observations may
|
|
be left unprocessed. This script shows the backlog and lets you trigger
|
|
processing. The worker no longer auto-recovers on startup to give you
|
|
control over when processing happens.
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
|
|
const autoProcess = args.includes('--process');
|
|
const limitArg = args.find((_, i) => args[i - 1] === '--limit');
|
|
const limit = limitArg ? parseInt(limitArg, 10) : 10;
|
|
|
|
console.log('\n=== Claude-Mem Pending Queue Status ===\n');
|
|
|
|
// Check worker health
|
|
const healthy = await checkWorkerHealth();
|
|
if (!healthy) {
|
|
console.log('Worker is not running. Start it with:');
|
|
console.log(' cd ~/.claude/plugins/marketplaces/thedotmack && npm run worker:start\n');
|
|
process.exit(1);
|
|
}
|
|
console.log('Worker status: Running\n');
|
|
|
|
// Get queue status
|
|
const status = await getQueueStatus();
|
|
const { queue, sessionsWithPendingWork } = status;
|
|
|
|
// Display summary
|
|
console.log('Queue Summary:');
|
|
console.log(` Pending: ${queue.totalPending}`);
|
|
console.log(` Processing: ${queue.totalProcessing}`);
|
|
console.log(` Failed: ${queue.totalFailed}`);
|
|
console.log(` Stuck: ${queue.stuckCount} (processing > 5 min)`);
|
|
console.log(` Sessions: ${sessionsWithPendingWork.length} with pending work\n`);
|
|
|
|
// Check if there's any backlog
|
|
const hasBacklog = queue.totalPending > 0 || queue.totalFailed > 0;
|
|
const hasStuck = queue.stuckCount > 0;
|
|
|
|
if (!hasBacklog && !hasStuck) {
|
|
console.log('No backlog detected. Queue is healthy.\n');
|
|
|
|
// Show recently processed if any
|
|
if (status.recentlyProcessed.length > 0) {
|
|
console.log(`Recently processed: ${status.recentlyProcessed.length} messages in last 30 min\n`);
|
|
}
|
|
process.exit(0);
|
|
}
|
|
|
|
// Show details about pending messages
|
|
if (queue.messages.length > 0) {
|
|
console.log('Pending Messages:');
|
|
console.log('─'.repeat(80));
|
|
|
|
// Group by session
|
|
const bySession = new Map<number, QueueMessage[]>();
|
|
for (const msg of queue.messages) {
|
|
const list = bySession.get(msg.session_db_id) || [];
|
|
list.push(msg);
|
|
bySession.set(msg.session_db_id, list);
|
|
}
|
|
|
|
for (const [sessionId, messages] of bySession) {
|
|
const project = messages[0].project || 'unknown';
|
|
const oldest = Math.min(...messages.map(m => m.created_at_epoch));
|
|
const statuses = {
|
|
pending: messages.filter(m => m.status === 'pending').length,
|
|
processing: messages.filter(m => m.status === 'processing').length,
|
|
failed: messages.filter(m => m.status === 'failed').length
|
|
};
|
|
|
|
console.log(` Session ${sessionId} (${project})`);
|
|
console.log(` Messages: ${messages.length} total`);
|
|
console.log(` Status: ${statuses.pending} pending, ${statuses.processing} processing, ${statuses.failed} failed`);
|
|
console.log(` Age: ${formatAge(oldest)}`);
|
|
}
|
|
console.log('─'.repeat(80));
|
|
console.log('');
|
|
}
|
|
|
|
// Offer to process
|
|
if (autoProcess) {
|
|
console.log(`Auto-processing up to ${limit} sessions...\n`);
|
|
} else {
|
|
const answer = await prompt(`Process pending queue? (up to ${limit} sessions) [y/N]: `);
|
|
if (answer.toLowerCase() !== 'y') {
|
|
console.log('\nSkipped. Run with --process to auto-process.\n');
|
|
process.exit(0);
|
|
}
|
|
console.log('');
|
|
}
|
|
|
|
// Process the queue
|
|
const result = await processQueue(limit);
|
|
|
|
console.log('Processing Result:');
|
|
console.log(` Sessions started: ${result.sessionsStarted}`);
|
|
console.log(` Sessions skipped: ${result.sessionsSkipped} (already active)`);
|
|
console.log(` Remaining: ${result.totalPendingSessions - result.sessionsStarted}`);
|
|
|
|
if (result.startedSessionIds.length > 0) {
|
|
console.log(` Started IDs: ${result.startedSessionIds.join(', ')}`);
|
|
}
|
|
|
|
console.log('\nProcessing started in background. Check status again in a few minutes.\n');
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Error:', err.message);
|
|
process.exit(1);
|
|
});
|