Refactor hooks to standardize response format and improve error handling
- Introduced a new `hook-response.ts` module to create standardized hook responses. - Updated `context-hook.ts`, `new.ts`, `save.ts`, and `summary.ts` to utilize the new response format. - Enhanced error handling in `context-hook.ts` to check for input from stdin. - Refactored database interaction in hooks to ensure consistent session management. - Improved readability and maintainability of hook implementations by restructuring code. - Updated database queries to use consistent variable naming and formatting. - Modified the handling of socket connections in `save.ts` and `summary.ts` to ensure proper response on close and error events.
This commit is contained in:
@@ -7,12 +7,14 @@
|
||||
|
||||
import { contextHook } from '../../hooks/context.js';
|
||||
|
||||
// Read input from stdin
|
||||
const input = await Bun.stdin.text();
|
||||
|
||||
try {
|
||||
const parsed = input.trim() ? JSON.parse(input) : undefined;
|
||||
contextHook(parsed);
|
||||
if (process.stdin.isTTY) {
|
||||
contextHook();
|
||||
} else {
|
||||
const input = await Bun.stdin.text();
|
||||
const parsed = input.trim() ? JSON.parse(input) : undefined;
|
||||
contextHook(parsed);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`[claude-mem context-hook error: ${error.message}]`);
|
||||
process.exit(0);
|
||||
|
||||
+18
-13
@@ -1,12 +1,13 @@
|
||||
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
|
||||
import path from 'path';
|
||||
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
|
||||
export interface SessionStartInput {
|
||||
session_id: string;
|
||||
transcript_path: string;
|
||||
cwd: string;
|
||||
hook_event_name: string;
|
||||
source: "startup" | "resume" | "clear" | "compact";
|
||||
session_id?: string;
|
||||
transcript_path?: string;
|
||||
cwd?: string;
|
||||
hook_event_name?: string;
|
||||
source?: "startup" | "resume" | "clear" | "compact";
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -15,18 +16,19 @@ export interface SessionStartInput {
|
||||
* Shows user what happened in recent sessions
|
||||
*/
|
||||
export function contextHook(input?: SessionStartInput): void {
|
||||
if (!input) {
|
||||
throw new Error('contextHook requires input');
|
||||
}
|
||||
const cwd = input?.cwd ?? process.cwd();
|
||||
const project = cwd ? path.basename(cwd) : 'unknown-project';
|
||||
|
||||
const project = input.cwd ? path.basename(input.cwd) : path.basename(path.dirname(input.transcript_path));
|
||||
const db = new HooksDatabase();
|
||||
|
||||
try {
|
||||
const summaries = db.getRecentSummaries(project, 5);
|
||||
|
||||
if (summaries.length === 0) {
|
||||
console.log('# Recent Session Context\n\nNo previous sessions found for this project yet.');
|
||||
const response = createHookResponse('SessionStart', true, {
|
||||
context: '# Recent Session Context\n\nNo previous sessions found for this project yet.'
|
||||
});
|
||||
console.log(response);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,8 +89,11 @@ export function contextHook(input?: SessionStartInput): void {
|
||||
output.push('');
|
||||
}
|
||||
|
||||
console.log(output.join('\n'));
|
||||
const response = createHookResponse('SessionStart', true, {
|
||||
context: output.join('\n')
|
||||
});
|
||||
console.log(response);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
export type HookType = 'PreCompact' | 'SessionStart' | 'UserPromptSubmit' | 'PostToolUse' | 'Stop' | string;
|
||||
|
||||
export interface HookResponseOptions {
|
||||
reason?: string;
|
||||
context?: string;
|
||||
}
|
||||
|
||||
export interface HookResponse {
|
||||
continue?: boolean;
|
||||
suppressOutput?: boolean;
|
||||
stopReason?: string;
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'SessionStart';
|
||||
additionalContext: string;
|
||||
};
|
||||
}
|
||||
|
||||
function buildHookResponse(
|
||||
hookType: HookType,
|
||||
success: boolean,
|
||||
options: HookResponseOptions
|
||||
): HookResponse {
|
||||
if (hookType === 'PreCompact') {
|
||||
if (success) {
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
continue: false,
|
||||
stopReason: options.reason || 'Pre-compact operation failed',
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
if (hookType === 'SessionStart') {
|
||||
if (success && options.context) {
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true,
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'SessionStart',
|
||||
additionalContext: options.context
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
if (hookType === 'UserPromptSubmit' || hookType === 'PostToolUse') {
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
if (hookType === 'Stop') {
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
continue: success,
|
||||
suppressOutput: true,
|
||||
...(options.reason && !success ? { stopReason: options.reason } : {})
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a standardized hook response using the HookTemplates system.
|
||||
*/
|
||||
export function createHookResponse(
|
||||
hookType: HookType,
|
||||
success: boolean,
|
||||
options: HookResponseOptions = {}
|
||||
): string {
|
||||
const response = buildHookResponse(hookType, success, options);
|
||||
return JSON.stringify(response);
|
||||
}
|
||||
+5
-4
@@ -1,6 +1,7 @@
|
||||
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
|
||||
import path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import path from 'path';
|
||||
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
|
||||
export interface UserPromptSubmitInput {
|
||||
session_id: string;
|
||||
@@ -26,7 +27,7 @@ export function newHook(input?: UserPromptSubmitInput): void {
|
||||
const existing = db.findActiveSDKSession(session_id);
|
||||
|
||||
if (existing) {
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
console.log(createHookResponse('UserPromptSubmit', true));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ export function newHook(input?: UserPromptSubmitInput): void {
|
||||
|
||||
child.unref();
|
||||
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
console.log(createHookResponse('UserPromptSubmit', true));
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
|
||||
+14
-5
@@ -1,6 +1,7 @@
|
||||
import net from 'net';
|
||||
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
|
||||
import { getWorkerSocketPath } from '../shared/paths.js';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
|
||||
export interface PostToolUseInput {
|
||||
session_id: string;
|
||||
@@ -29,7 +30,7 @@ export function saveHook(input?: PostToolUseInput): void {
|
||||
const { session_id, tool_name, tool_input, tool_output } = input;
|
||||
|
||||
if (SKIP_TOOLS.has(tool_name)) {
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
console.log(createHookResponse('PostToolUse', true));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -38,7 +39,7 @@ export function saveHook(input?: PostToolUseInput): void {
|
||||
db.close();
|
||||
|
||||
if (!session) {
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
console.log(createHookResponse('PostToolUse', true));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,7 +56,15 @@ export function saveHook(input?: PostToolUseInput): void {
|
||||
client.end();
|
||||
});
|
||||
|
||||
client.on('close', () => {
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
});
|
||||
let responded = false;
|
||||
const respond = () => {
|
||||
if (responded) {
|
||||
return;
|
||||
}
|
||||
responded = true;
|
||||
console.log(createHookResponse('PostToolUse', true));
|
||||
};
|
||||
|
||||
client.on('close', respond);
|
||||
client.on('error', respond);
|
||||
}
|
||||
|
||||
+13
-4
@@ -1,6 +1,7 @@
|
||||
import net from 'net';
|
||||
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
|
||||
import { getWorkerSocketPath } from '../shared/paths.js';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
|
||||
export interface StopInput {
|
||||
session_id: string;
|
||||
@@ -23,7 +24,7 @@ export function summaryHook(input?: StopInput): void {
|
||||
db.close();
|
||||
|
||||
if (!session) {
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
console.log(createHookResponse('Stop', true));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,7 +38,15 @@ export function summaryHook(input?: StopInput): void {
|
||||
client.end();
|
||||
});
|
||||
|
||||
client.on('close', () => {
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
});
|
||||
let responded = false;
|
||||
const respond = () => {
|
||||
if (responded) {
|
||||
return;
|
||||
}
|
||||
responded = true;
|
||||
console.log(createHookResponse('Stop', true));
|
||||
};
|
||||
|
||||
client.on('close', respond);
|
||||
client.on('error', respond);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user