676a3d175e
Address PR #1125 review feedback - both fetches now start simultaneously via Promise.all instead of sequential-then-parallel. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
/**
|
|
* Context Handler - SessionStart
|
|
*
|
|
* Extracted from context-hook.ts - calls worker to generate context.
|
|
* Returns context as hookSpecificOutput for Claude Code to inject.
|
|
*/
|
|
|
|
import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
|
|
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
|
|
import { getProjectContext } from '../../utils/project-name.js';
|
|
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';
|
|
import { logger } from '../../utils/logger.js';
|
|
|
|
export const contextHandler: EventHandler = {
|
|
async execute(input: NormalizedHookInput): Promise<HookResult> {
|
|
// Ensure worker is running before any other logic
|
|
const workerReady = await ensureWorkerRunning();
|
|
if (!workerReady) {
|
|
// Worker not available - return empty context gracefully
|
|
return {
|
|
hookSpecificOutput: {
|
|
hookEventName: 'SessionStart',
|
|
additionalContext: ''
|
|
},
|
|
exitCode: HOOK_EXIT_CODES.SUCCESS
|
|
};
|
|
}
|
|
|
|
const cwd = input.cwd ?? process.cwd();
|
|
const context = getProjectContext(cwd);
|
|
const port = getWorkerPort();
|
|
|
|
// Pass all projects (parent + worktree if applicable) for unified timeline
|
|
const projectsParam = context.allProjects.join(',');
|
|
const url = `http://127.0.0.1:${port}/api/context/inject?projects=${encodeURIComponent(projectsParam)}`;
|
|
|
|
// Note: Removed AbortSignal.timeout due to Windows Bun cleanup issue (libuv assertion)
|
|
// Worker service has its own timeouts, so client-side timeout is redundant
|
|
try {
|
|
// Fetch both markdown (for Claude context) and colored (for user display) truly in parallel
|
|
const colorUrl = `${url}&colors=true`;
|
|
const [response, colorResponse] = await Promise.all([
|
|
fetch(url),
|
|
fetch(colorUrl).catch(() => null)
|
|
]);
|
|
|
|
if (!response.ok) {
|
|
// Log but don't throw — context fetch failure should not block session start
|
|
logger.warn('HOOK', 'Context generation failed, returning empty', { status: response.status });
|
|
return {
|
|
hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: '' },
|
|
exitCode: HOOK_EXIT_CODES.SUCCESS
|
|
};
|
|
}
|
|
|
|
const [contextResult, colorResult] = await Promise.all([
|
|
response.text(),
|
|
colorResponse?.ok ? colorResponse.text() : Promise.resolve('')
|
|
]);
|
|
|
|
const additionalContext = contextResult.trim();
|
|
const coloredTimeline = colorResult.trim();
|
|
const systemMessage = coloredTimeline
|
|
? `${coloredTimeline}\n\nView Observations Live @ http://localhost:${port}`
|
|
: undefined;
|
|
|
|
return {
|
|
hookSpecificOutput: {
|
|
hookEventName: 'SessionStart',
|
|
additionalContext
|
|
},
|
|
systemMessage
|
|
};
|
|
} catch (error) {
|
|
// Worker unreachable — return empty context gracefully
|
|
logger.warn('HOOK', 'Context fetch error, returning empty', { error: error instanceof Error ? error.message : String(error) });
|
|
return {
|
|
hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: '' },
|
|
exitCode: HOOK_EXIT_CODES.SUCCESS
|
|
};
|
|
}
|
|
}
|
|
};
|