Refactor new-hook to initialize sessions via HTTP and improve privacy handling

- Removed direct database operations in new-hook.ts and replaced them with an HTTP call to initialize sessions.
- Added error handling for HTTP requests and improved logging for session initialization.
- Updated SessionRoutes to handle new session initialization and privacy checks.
- Enhanced privacy tag stripping logic to prevent saving fully private prompts.
- Improved overall error handling and debugging messages throughout the session management process.
This commit is contained in:
Alex Newman
2025-12-09 13:43:11 -05:00
parent fc5c2d5e07
commit a2f7a4dc5a
5 changed files with 734 additions and 519 deletions
+41 -34
View File
@@ -35,11 +35,9 @@
import path from 'path';
import { stdin } from 'process';
import { SessionStore } from '../services/sqlite/SessionStore.js';
import { createHookResponse } from './hook-response.js';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { silentDebug } from '../utils/silent-debug.js';
import { stripMemoryTagsFromPrompt } from '../utils/tag-stripping.js';
export interface UserPromptSubmitInput {
session_id: string;
@@ -82,40 +80,49 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
cwd_was: cwd
});
const db = new SessionStore();
// CRITICAL: Use session_id from hook as THE source of truth
// createSDKSession is idempotent - creates new or returns existing
// This is how ALL hooks stay connected to the same session
const sessionDbId = db.createSDKSession(session_id, project, prompt);
const promptNumber = db.incrementPromptCounter(sessionDbId);
// Strip memory tags before saving user prompt to prevent privacy leaks
// Tags like <private> and <claude-mem-context> should not be stored or searchable
const cleanedUserPrompt = stripMemoryTagsFromPrompt(prompt);
// Skip memory operations for fully private prompts
// If the entire prompt was wrapped in <private> tags, don't create any observations
if (!cleanedUserPrompt || cleanedUserPrompt.trim() === '') {
silentDebug('[new-hook] Prompt entirely private, skipping memory operations', {
session_id,
promptNumber,
originalLength: prompt.length
});
db.close();
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber} (fully private - skipped)`);
console.log(createHookResponse('UserPromptSubmit', true));
return;
}
db.saveUserPrompt(session_id, promptNumber, cleanedUserPrompt);
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber}`);
db.close();
const port = getWorkerPort();
// Initialize session via HTTP - handles DB operations and privacy checks
let sessionDbId: number;
let promptNumber: number;
try {
const initResponse = await fetch(`http://127.0.0.1:${port}/api/sessions/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: session_id,
project,
prompt
}),
signal: AbortSignal.timeout(5000)
});
if (!initResponse.ok) {
const errorText = await initResponse.text();
throw new Error(`Failed to initialize session: ${initResponse.status} ${errorText}`);
}
const initResult = await initResponse.json();
sessionDbId = initResult.sessionDbId;
promptNumber = initResult.promptNumber;
// Check if prompt was entirely private (worker performs privacy check)
if (initResult.skipped && initResult.reason === 'private') {
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber} (fully private - skipped)`);
console.log(createHookResponse('UserPromptSubmit', true));
return;
}
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber}`);
} catch (error: any) {
// Only show restart message for connection errors, not HTTP errors
if (error.cause?.code === 'ECONNREFUSED' || error.name === 'TimeoutError' || error.message.includes('fetch failed')) {
throw new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue");
}
throw error;
}
// Strip leading slash from commands for memory agent
// /review 101 → review 101 (more semantic for observations)
const cleanedPrompt = prompt.startsWith('/') ? prompt.substring(1) : prompt;