feat: Enhance session management with prompt tracking

- Added prompt_number to observations and session summaries for better tracking.
- Implemented prompt counter in SDK sessions to manage user prompts effectively.
- Updated database schema to include prompt tracking columns and removed unique constraints on session summaries.
- Modified hooks to utilize prompt_number in observations and summaries.
- Changed worker service to handle summarize requests instead of finalize, keeping the SDK agent active.
- Improved logging for better debugging and tracking of prompt numbers across sessions.
This commit is contained in:
Alex Newman
2025-10-17 16:23:11 -04:00
parent 372854948c
commit d4a71c994d
13 changed files with 480 additions and 265 deletions
+8 -3
View File
@@ -42,10 +42,10 @@ export function contextHook(input?: SessionStartInput): void {
output.push('');
// Group observations by type
const byType: Record<string, Array<{text: string; created_at: string}>> = {};
const byType: Record<string, Array<{text: string; prompt_number: number | null; created_at: string}>> = {};
for (const obs of observations) {
if (!byType[obs.type]) byType[obs.type] = [];
byType[obs.type].push({ text: obs.text, created_at: obs.created_at });
byType[obs.type].push({ text: obs.text, prompt_number: obs.prompt_number, created_at: obs.created_at });
}
// Display each type
@@ -54,7 +54,8 @@ export function contextHook(input?: SessionStartInput): void {
if (byType[type] && byType[type].length > 0) {
output.push(`### ${type.charAt(0).toUpperCase() + type.slice(1)}s`);
for (const obs of byType[type]) {
output.push(`- ${obs.text}`);
const promptLabel = obs.prompt_number ? ` (prompt #${obs.prompt_number})` : '';
output.push(`- ${obs.text}${promptLabel}`);
}
output.push('');
}
@@ -76,6 +77,10 @@ export function contextHook(input?: SessionStartInput): void {
output.push('---');
output.push('');
const promptLabel = summary.prompt_number ? ` (Prompt #${summary.prompt_number})` : '';
output.push(`**Summary${promptLabel}**`);
output.push('');
if (summary.request) {
output.push(`**Request:** ${summary.request}`);
}
+33 -25
View File
@@ -48,26 +48,31 @@ export async function newHook(input?: UserPromptSubmitInput): Promise<void> {
// Check for any existing session (active, failed, or completed)
let existing = db.findActiveSDKSession(session_id);
let sessionDbId: number;
let isNewSession = false;
if (existing) {
// Session already active, just continue
// Session already active, increment prompt counter
sessionDbId = existing.id;
console.log(createHookResponse('UserPromptSubmit', true));
return;
}
// Check for inactive sessions we can reuse
const inactive = db.findAnySDKSession(session_id);
if (inactive) {
// Reactivate the existing session
sessionDbId = inactive.id;
db.reactivateSession(sessionDbId, prompt);
console.error(`[new-hook] Reactivated session ${sessionDbId} for Claude session ${session_id}`);
const promptNumber = db.incrementPromptCounter(sessionDbId);
console.error(`[new-hook] Continuing session ${sessionDbId}, prompt #${promptNumber}`);
} else {
// Create new session
sessionDbId = db.createSDKSession(session_id, project, prompt);
console.error(`[new-hook] Created new session ${sessionDbId} for Claude session ${session_id}`);
// Check for inactive sessions we can reuse
const inactive = db.findAnySDKSession(session_id);
if (inactive) {
// Reactivate the existing session
sessionDbId = inactive.id;
db.reactivateSession(sessionDbId, prompt);
const promptNumber = db.incrementPromptCounter(sessionDbId);
isNewSession = true;
console.error(`[new-hook] Reactivated session ${sessionDbId}, prompt #${promptNumber}`);
} else {
// Create new session
sessionDbId = db.createSDKSession(session_id, project, prompt);
const promptNumber = db.incrementPromptCounter(sessionDbId);
isNewSession = true;
console.error(`[new-hook] Created new session ${sessionDbId}, prompt #${promptNumber}`);
}
}
// Find worker service port
@@ -78,16 +83,19 @@ export async function newHook(input?: UserPromptSubmitInput): Promise<void> {
return;
}
// Initialize session via HTTP
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ project, userPrompt: prompt }),
signal: AbortSignal.timeout(5000)
});
// Only initialize worker on new sessions
if (isNewSession) {
// Initialize session via HTTP
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ project, userPrompt: prompt }),
signal: AbortSignal.timeout(5000)
});
if (!response.ok) {
console.error('[new-hook] Failed to init session:', await response.text());
if (!response.ok) {
console.error('[new-hook] Failed to init session:', await response.text());
}
}
console.log(createHookResponse('UserPromptSubmit', true));
+8 -2
View File
@@ -34,19 +34,24 @@ export async function saveHook(input?: PostToolUseInput): Promise<void> {
const db = new HooksDatabase();
const session = db.findActiveSDKSession(session_id);
db.close();
if (!session) {
db.close();
console.log(createHookResponse('PostToolUse', true));
return;
}
if (!session.worker_port) {
db.close();
console.error('[save-hook] No worker port for session', session.id);
console.log(createHookResponse('PostToolUse', true));
return;
}
// Get current prompt number for this session
const promptNumber = db.getPromptCounter(session.id);
db.close();
try {
const response = await fetch(`http://127.0.0.1:${session.worker_port}/sessions/${session.id}/observations`, {
method: 'POST',
@@ -54,7 +59,8 @@ export async function saveHook(input?: PostToolUseInput): Promise<void> {
body: JSON.stringify({
tool_name,
tool_input: JSON.stringify(tool_input),
tool_output: JSON.stringify(tool_output)
tool_output: JSON.stringify(tool_output),
prompt_number: promptNumber
}),
signal: AbortSignal.timeout(2000)
});
+10 -4
View File
@@ -9,7 +9,7 @@ export interface StopInput {
/**
* Summary Hook - Stop
* Sends FINALIZE message to worker via HTTP POST
* Sends SUMMARIZE message to worker via HTTP POST (not finalize - keeps SDK agent running)
*/
export async function summaryHook(input?: StopInput): Promise<void> {
if (!input) {
@@ -19,28 +19,34 @@ export async function summaryHook(input?: StopInput): Promise<void> {
const { session_id } = input;
const db = new HooksDatabase();
const session = db.findActiveSDKSession(session_id);
db.close();
if (!session) {
db.close();
console.log(createHookResponse('Stop', true));
return;
}
if (!session.worker_port) {
db.close();
console.error('[summary-hook] No worker port for session', session.id);
console.log(createHookResponse('Stop', true));
return;
}
// Get current prompt number
const promptNumber = db.getPromptCounter(session.id);
db.close();
try {
const response = await fetch(`http://127.0.0.1:${session.worker_port}/sessions/${session.id}/finalize`, {
const response = await fetch(`http://127.0.0.1:${session.worker_port}/sessions/${session.id}/summarize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt_number: promptNumber }),
signal: AbortSignal.timeout(2000)
});
if (!response.ok) {
console.error('[summary-hook] Failed to finalize:', await response.text());
console.error('[summary-hook] Failed to generate summary:', await response.text());
}
} catch (error: any) {
console.error('[summary-hook] Error:', error.message);