Files
claude-mem/src/hooks/summary-hook.ts
T
Alex Newman 7fdf5e75ab refactor: replace happy_path_error__with_fallback with logger.happyPathError (#313)
- Removed all instances of happy_path_error__with_fallback from various hooks, services, and utilities.
- Introduced logger.happyPathError for consistent logging of unexpected nulls and fallback values.
- Updated the logger utility to include a new happyPathError method with enhanced context and stack trace.
- Deprecated silent-debug utility as all logging functionality has been migrated to the logger.
2025-12-14 16:56:31 -05:00

112 lines
3.5 KiB
TypeScript

/**
* Summary Hook - Stop
*
* Pure HTTP client - sends data to worker, worker handles all database operations
* including privacy checks. This allows the hook to run under any runtime
* (Node.js or Bun) since it has no native module dependencies.
*
* Transcript parsing stays in the hook because only the hook has access to
* the transcript file path.
*/
import { stdin } from 'process';
import { createHookResponse } from './hook-response.js';
import { logger } from '../utils/logger.js';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
import { handleWorkerError } from '../shared/hook-error-handler.js';
import { handleFetchError } from './shared/error-handler.js';
import { extractLastMessage } from '../shared/transcript-parser.js';
export interface StopInput {
session_id: string;
cwd: string;
transcript_path: string;
}
/**
* Summary Hook Main Logic - Fire-and-forget HTTP client
*/
async function summaryHook(input?: StopInput): Promise<void> {
// Ensure worker is running before any other logic
await ensureWorkerRunning();
if (!input) {
throw new Error('summaryHook requires input');
}
const { session_id } = input;
const port = getWorkerPort();
// Extract last user AND assistant messages from transcript
const transcriptPath = input.transcript_path || logger.happyPathError(
'HOOK',
'Missing transcript_path in Stop hook input',
undefined,
{ session_id },
''
);
const lastUserMessage = extractLastMessage(transcriptPath, 'user');
const lastAssistantMessage = extractLastMessage(transcriptPath, 'assistant', true);
logger.dataIn('HOOK', 'Stop: Requesting summary', {
workerPort: port,
hasLastUserMessage: !!lastUserMessage,
hasLastAssistantMessage: !!lastAssistantMessage
});
try {
// Send to worker - worker handles privacy check and database operations
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/summarize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: session_id,
last_user_message: lastUserMessage,
last_assistant_message: lastAssistantMessage
}),
signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT)
});
if (!response.ok) {
const errorText = await response.text();
handleFetchError(response, errorText, {
hookName: 'summary',
operation: 'Summary generation',
sessionId: session_id,
port
});
}
logger.debug('HOOK', 'Summary request sent successfully');
} catch (error: any) {
handleWorkerError(error);
} finally {
// Stop processing spinner
try {
const spinnerResponse = await fetch(`http://127.0.0.1:${port}/api/processing`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ isProcessing: false }),
signal: AbortSignal.timeout(2000)
});
if (!spinnerResponse.ok) {
logger.warn('HOOK', 'Failed to stop spinner', { status: spinnerResponse.status });
}
} catch (error: any) {
logger.warn('HOOK', 'Could not stop spinner', { error: error.message });
}
}
console.log(createHookResponse('Stop', true));
}
// Entry Point
let input = '';
stdin.on('data', (chunk) => input += chunk);
stdin.on('end', async () => {
const parsed = input ? JSON.parse(input) : undefined;
await summaryHook(parsed);
});