tasq/node_modules/agentic-flow/dist/sdk/hooks-bridge.js

235 lines
8.3 KiB
JavaScript

/**
* SDK Hooks Bridge - Connects agentic-flow intelligence layer to Claude Agent SDK hooks
*
* Bridges our custom hooks (intelligence-bridge.ts) with the native SDK hook system
* enabling seamless integration with Claude Code's event loop.
*/
import { logger } from "../utils/logger.js";
// Lazy import intelligence bridge to avoid circular dependencies
let intelligenceBridge = null;
async function getIntelligenceBridge() {
if (!intelligenceBridge) {
try {
intelligenceBridge = await import("../mcp/fastmcp/tools/hooks/intelligence-bridge.js");
}
catch (e) {
logger.warn('Intelligence bridge not available', { error: e.message });
return null;
}
}
return intelligenceBridge;
}
// Active trajectory tracking with TTL (5 minutes max)
const TRAJECTORY_TTL_MS = 5 * 60 * 1000;
const activeTrajectories = new Map();
// Cleanup stale trajectories periodically
function cleanupStaleTrajectories() {
const now = Date.now();
for (const [key, value] of activeTrajectories.entries()) {
if (now - value.timestamp > TRAJECTORY_TTL_MS) {
activeTrajectories.delete(key);
}
}
}
// Run cleanup every 2 minutes
setInterval(cleanupStaleTrajectories, 2 * 60 * 1000).unref();
/**
* PreToolUse hook - Called before tool execution
* Routes to best agent and starts trajectory tracking
*/
export const preToolUseHook = async (input, toolUseId, { signal }) => {
if (input.hook_event_name !== 'PreToolUse')
return {};
const { tool_name, tool_input, session_id } = input;
try {
const bridge = await getIntelligenceBridge();
if (!bridge)
return {};
// Start trajectory for edit operations
if (['Edit', 'Write', 'Bash'].includes(tool_name)) {
const filePath = tool_input?.file_path || tool_input?.command || 'unknown';
const result = await bridge.beginTaskTrajectory(`${tool_name}: ${filePath.substring(0, 100)}`, 'coder');
if (result.success && result.trajectoryId > 0) {
activeTrajectories.set(`${session_id}:${toolUseId}`, {
trajectoryId: result.trajectoryId,
timestamp: Date.now()
});
logger.debug('Trajectory started', { trajectoryId: result.trajectoryId, tool: tool_name });
}
}
return {};
}
catch (error) {
logger.warn('PreToolUse hook error', { error: error.message });
return {};
}
};
/**
* PostToolUse hook - Called after successful tool execution
* Records patterns and ends trajectories
*/
export const postToolUseHook = async (input, toolUseId, { signal }) => {
if (input.hook_event_name !== 'PostToolUse')
return {};
const { tool_name, tool_input, tool_response, session_id } = input;
try {
const bridge = await getIntelligenceBridge();
if (!bridge)
return {};
// End trajectory if one was started
const trajectoryKey = `${session_id}:${toolUseId}`;
const trajectoryEntry = activeTrajectories.get(trajectoryKey);
if (trajectoryEntry) {
await bridge.endTaskTrajectory(trajectoryEntry.trajectoryId, 'success');
activeTrajectories.delete(trajectoryKey);
logger.debug('Trajectory completed', { trajectoryId: trajectoryEntry.trajectoryId, tool: tool_name });
}
// Store successful pattern
if (['Edit', 'Write'].includes(tool_name)) {
const filePath = tool_input?.file_path || 'unknown';
await bridge.storePattern({
id: `sdk-${tool_name.toLowerCase()}-${Date.now()}`,
metadata: {
tool: tool_name,
file: filePath,
success: true,
timestamp: Date.now()
}
});
}
return {};
}
catch (error) {
logger.warn('PostToolUse hook error', { error: error.message });
return {};
}
};
/**
* PostToolUseFailure hook - Called when tool execution fails
* Ends trajectories as failures
*/
export const postToolUseFailureHook = async (input, toolUseId, { signal }) => {
if (input.hook_event_name !== 'PostToolUseFailure')
return {};
const { session_id } = input;
try {
const bridge = await getIntelligenceBridge();
if (!bridge)
return {};
// End trajectory as failure
const trajectoryKey = `${session_id}:${toolUseId}`;
const trajectoryEntry = activeTrajectories.get(trajectoryKey);
if (trajectoryEntry) {
await bridge.endTaskTrajectory(trajectoryEntry.trajectoryId, 'failure');
activeTrajectories.delete(trajectoryKey);
logger.debug('Trajectory failed', { trajectoryId: trajectoryEntry.trajectoryId });
}
return {};
}
catch (error) {
logger.warn('PostToolUseFailure hook error', { error: error.message });
return {};
}
};
/**
* SessionStart hook - Called when session begins
* Initializes intelligence layer
*/
export const sessionStartHook = async (input, toolUseId, { signal }) => {
if (input.hook_event_name !== 'SessionStart')
return {};
const { source, session_id } = input;
try {
const bridge = await getIntelligenceBridge();
if (!bridge) {
return {
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: 'Intelligence layer not available.'
}
};
}
const stats = await bridge.getIntelligenceStats();
const message = `RuVector Intelligence active. ` +
`Trajectories: ${stats.trajectoryCount}, ` +
`Features: ${stats.features?.join(', ') || 'none'}`;
logger.info('Session started', { sessionId: session_id, source, stats });
return {
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: message
}
};
}
catch (error) {
logger.warn('SessionStart hook error', { error: error.message });
return {};
}
};
/**
* SessionEnd hook - Called when session ends
* Persists learning data
*/
export const sessionEndHook = async (input, toolUseId, { signal }) => {
if (input.hook_event_name !== 'SessionEnd')
return {};
const { reason, session_id } = input;
try {
const bridge = await getIntelligenceBridge();
if (!bridge)
return {};
// Force learning cycle on session end
await bridge.forceLearningCycle();
logger.info('Session ended', { sessionId: session_id, reason });
return {};
}
catch (error) {
logger.warn('SessionEnd hook error', { error: error.message });
return {};
}
};
/**
* SubagentStart hook - Called when a subagent is spawned
*/
export const subagentStartHook = async (input, toolUseId, { signal }) => {
if (input.hook_event_name !== 'SubagentStart')
return {};
const { agent_id, agent_type } = input;
logger.info('Subagent started', { agentId: agent_id, agentType: agent_type });
return {};
};
/**
* SubagentStop hook - Called when a subagent completes
*/
export const subagentStopHook = async (input, toolUseId, { signal }) => {
if (input.hook_event_name !== 'SubagentStop')
return {};
logger.info('Subagent stopped');
return {};
};
/**
* Get SDK hooks configuration
* Returns hooks in the format expected by Claude Agent SDK query() options
*/
export function getSdkHooks() {
return {
PreToolUse: [{ hooks: [preToolUseHook] }],
PostToolUse: [{ hooks: [postToolUseHook] }],
PostToolUseFailure: [{ hooks: [postToolUseFailureHook] }],
SessionStart: [{ hooks: [sessionStartHook] }],
SessionEnd: [{ hooks: [sessionEndHook] }],
SubagentStart: [{ hooks: [subagentStartHook] }],
SubagentStop: [{ hooks: [subagentStopHook] }]
};
}
/**
* Get filtered hooks for specific tools
*/
export function getToolSpecificHooks(toolMatcher) {
return {
PreToolUse: [{ matcher: toolMatcher, hooks: [preToolUseHook] }],
PostToolUse: [{ matcher: toolMatcher, hooks: [postToolUseHook] }],
PostToolUseFailure: [{ matcher: toolMatcher, hooks: [postToolUseFailureHook] }]
};
}
//# sourceMappingURL=hooks-bridge.js.map