Skip to content

Hooks 系统设计方案 ​

参考 Claude Code Hooks 系统,结合 ChainlessChain 现有架构设计

1. 系统概述 ​

1.1 设计目标 ​

  • 可扩展性: 支持第三方插件和自定义钩子
  • 非侵入性: 不修改现有核心代码逻辑
  • 类型安全: 提供完整的钩子类型定义
  • 性能优化: 异步执行,支持超时和取消
  • 调试友好: 完整的日志和错误追踪

1.2 核心概念 ​

┌─────────────────────────────────────────────────────────────┐
│                      Hooks System                           │
├─────────────────────────────────────────────────────────────┤
│  HookRegistry          │  管理所有钩子注册和查找            │
│  HookExecutor          │  执行钩子,处理异步和错误          │
│  HookContext           │  钩子执行上下文,支持数据传递      │
│  HookMiddleware        │  中间件包装器,集成到 IPC 系统     │
│  HookConfigLoader      │  从配置文件加载钩子定义            │
├─────────────────────────────────────────────────────────────┤
│  配置文件位置:                                              │
│  - .chainlesschain/hooks.json     (项目级)                  │
│  - ~/.chainlesschain/hooks.json   (用户级)                  │
│  - .chainlesschain/hooks/*.js     (脚本钩子)                │
└─────────────────────────────────────────────────────────────┘

2. 钩子事件定义 ​

2.1 完整事件列表 ​

事件名称触发时机用途可阻止
IPC 相关
PreIPCCallIPC 调用前验证、权限检查、日志✅
PostIPCCallIPC 调用后结果处理、日志、统计❌
IPCErrorIPC 调用出错错误处理、通知、诊断❌
工具调用相关
PreToolUse工具执行前参数验证、权限检查✅
PostToolUse工具执行后结果格式化、日志❌
ToolError工具执行出错错误处理、重试逻辑❌
会话相关
SessionStart会话创建时初始化、环境设置❌
SessionEnd会话结束时清理、保存状态❌
PreCompact上下文压缩前保存重要数据✅
PostCompact上下文压缩后压缩统计、通知❌
用户交互相关
UserPromptSubmit用户提交输入前输入验证、上下文注入✅
AssistantResponseAI 响应生成后响应处理、格式化❌
Agent 相关
AgentStartAgent 启动时初始化、资源分配❌
AgentStopAgent 停止时清理、结果汇总❌
TaskAssigned任务分配时任务验证、日志✅
TaskCompleted任务完成时结果处理、通知❌
文件操作相关
PreFileAccess文件访问前权限检查、路径验证✅
PostFileAccess文件访问后审计日志、统计❌
FileModified文件被修改时备份、通知、索引更新❌
内存系统相关
MemorySave保存到永久记忆前数据处理、去重✅
MemoryLoad加载永久记忆后数据处理、过滤❌

2.2 事件数据结构 ​

javascript
/**
 * 钩子事件基础结构
 */
const HookEvent = {
  // 事件标识
  eventName: 'PreToolUse',      // 事件名称
  eventId: 'uuid-xxx',          // 唯一事件ID
  timestamp: Date.now(),        // 触发时间戳

  // 上下文信息
  context: {
    sessionId: 'session-123',   // 当前会话ID
    agentId: 'agent-456',       // 当前Agent ID (如果有)
    userId: 'user-789',         // 用户标识
    source: 'ipc',              // 触发来源
  },

  // 事件数据 (因事件类型而异)
  data: {
    toolName: 'file_reader',
    parameters: { filePath: '/path/to/file' },
    // ...
  },

  // 控制字段
  prevented: false,             // 是否被阻止
  preventReason: null,          // 阻止原因
  modifications: {},            // 钩子修改的数据
}

3. 核心类设计 ​

3.1 HookRegistry (钩子注册表) ​

javascript
// 文件: src/main/hooks/hook-registry.js

const { EventEmitter } = require('events');
const path = require('path');
const fs = require('fs').promises;

/**
 * 钩子优先级
 */
const HookPriority = {
  SYSTEM: 0,      // 系统级 (最先执行)
  HIGH: 100,      // 高优先级
  NORMAL: 500,    // 普通优先级
  LOW: 900,       // 低优先级
  MONITOR: 1000,  // 监控级 (最后执行, 不可阻止)
};

/**
 * 钩子类型
 */
const HookType = {
  SYNC: 'sync',           // 同步钩子
  ASYNC: 'async',         // 异步钩子
  COMMAND: 'command',     // Shell 命令钩子
  SCRIPT: 'script',       // 脚本文件钩子
};

/**
 * 钩子注册表
 */
class HookRegistry extends EventEmitter {
  constructor(options = {}) {
    super();

    this.hooks = new Map();           // eventName -> Hook[]
    this.hookById = new Map();        // hookId -> Hook
    this.enabled = true;              // 全局开关
    this.configPath = options.configPath || null;

    // 统计信息
    this.stats = {
      totalRegistered: 0,
      totalExecutions: 0,
      totalErrors: 0,
      executionsByEvent: {},
    };

    // 初始化所有事件类型
    this._initializeEventTypes();
  }

  /**
   * 初始化事件类型
   */
  _initializeEventTypes() {
    const eventTypes = [
      'PreIPCCall', 'PostIPCCall', 'IPCError',
      'PreToolUse', 'PostToolUse', 'ToolError',
      'SessionStart', 'SessionEnd', 'PreCompact', 'PostCompact',
      'UserPromptSubmit', 'AssistantResponse',
      'AgentStart', 'AgentStop', 'TaskAssigned', 'TaskCompleted',
      'PreFileAccess', 'PostFileAccess', 'FileModified',
      'MemorySave', 'MemoryLoad',
    ];

    eventTypes.forEach(event => {
      this.hooks.set(event, []);
      this.stats.executionsByEvent[event] = 0;
    });
  }

  /**
   * 注册钩子
   * @param {Object} hookConfig 钩子配置
   * @returns {string} 钩子ID
   */
  register(hookConfig) {
    const {
      id = this._generateId(),
      event,
      name = 'unnamed-hook',
      type = HookType.ASYNC,
      priority = HookPriority.NORMAL,
      handler,                    // 函数类型
      command,                    // 命令类型
      script,                     // 脚本类型
      matcher = null,             // 匹配器 (正则或函数)
      timeout = 30000,            // 超时时间 (ms)
      enabled = true,
      description = '',
      metadata = {},
    } = hookConfig;

    // 验证
    if (!event || !this.hooks.has(event)) {
      throw new Error(`Invalid hook event: ${event}`);
    }

    if (type === HookType.SYNC && !handler) {
      throw new Error('Sync hooks require a handler function');
    }

    if (type === HookType.COMMAND && !command) {
      throw new Error('Command hooks require a command string');
    }

    const hook = {
      id,
      event,
      name,
      type,
      priority,
      handler,
      command,
      script,
      matcher,
      timeout,
      enabled,
      description,
      metadata,
      registeredAt: Date.now(),
      executionCount: 0,
      errorCount: 0,
      lastExecutedAt: null,
      avgExecutionTime: 0,
    };

    // 添加到注册表
    const eventHooks = this.hooks.get(event);
    eventHooks.push(hook);

    // 按优先级排序
    eventHooks.sort((a, b) => a.priority - b.priority);

    this.hookById.set(id, hook);
    this.stats.totalRegistered++;

    this.emit('hook-registered', { hook });

    return id;
  }

  /**
   * 批量注册钩子
   */
  registerMultiple(hookConfigs) {
    return hookConfigs.map(config => this.register(config));
  }

  /**
   * 注销钩子
   */
  unregister(hookId) {
    const hook = this.hookById.get(hookId);
    if (!hook) return false;

    const eventHooks = this.hooks.get(hook.event);
    const index = eventHooks.findIndex(h => h.id === hookId);
    if (index !== -1) {
      eventHooks.splice(index, 1);
    }

    this.hookById.delete(hookId);
    this.emit('hook-unregistered', { hookId, hook });

    return true;
  }

  /**
   * 获取事件的所有钩子
   */
  getHooks(eventName, options = {}) {
    const { enabledOnly = true, matchContext = null } = options;

    let hooks = this.hooks.get(eventName) || [];

    if (enabledOnly) {
      hooks = hooks.filter(h => h.enabled);
    }

    if (matchContext) {
      hooks = hooks.filter(h => this._matchHook(h, matchContext));
    }

    return hooks;
  }

  /**
   * 检查钩子是否匹配上下文
   */
  _matchHook(hook, context) {
    if (!hook.matcher) return true;

    if (typeof hook.matcher === 'function') {
      return hook.matcher(context);
    }

    if (hook.matcher instanceof RegExp) {
      // 用于匹配工具名、通道名等
      const target = context.toolName || context.channel || '';
      return hook.matcher.test(target);
    }

    if (typeof hook.matcher === 'string') {
      // 支持通配符
      const pattern = hook.matcher
        .replace(/\*/g, '.*')
        .replace(/\?/g, '.');
      const regex = new RegExp(`^${pattern}$`);
      const target = context.toolName || context.channel || '';
      return regex.test(target);
    }

    return true;
  }

  /**
   * 启用/禁用钩子
   */
  setEnabled(hookId, enabled) {
    const hook = this.hookById.get(hookId);
    if (hook) {
      hook.enabled = enabled;
      this.emit('hook-status-changed', { hookId, enabled });
      return true;
    }
    return false;
  }

  /**
   * 获取钩子信息
   */
  getHook(hookId) {
    return this.hookById.get(hookId);
  }

  /**
   * 获取所有钩子列表
   */
  listHooks(options = {}) {
    const { event = null, enabledOnly = false } = options;

    let hooks = Array.from(this.hookById.values());

    if (event) {
      hooks = hooks.filter(h => h.event === event);
    }

    if (enabledOnly) {
      hooks = hooks.filter(h => h.enabled);
    }

    return hooks.map(h => ({
      id: h.id,
      name: h.name,
      event: h.event,
      type: h.type,
      priority: h.priority,
      enabled: h.enabled,
      description: h.description,
      executionCount: h.executionCount,
      avgExecutionTime: h.avgExecutionTime,
    }));
  }

  /**
   * 更新钩子统计
   */
  updateStats(hookId, { executionTime, success }) {
    const hook = this.hookById.get(hookId);
    if (!hook) return;

    hook.executionCount++;
    hook.lastExecutedAt = Date.now();

    if (!success) {
      hook.errorCount++;
    }

    // 更新平均执行时间
    const totalTime = hook.avgExecutionTime * (hook.executionCount - 1) + executionTime;
    hook.avgExecutionTime = totalTime / hook.executionCount;

    // 更新全局统计
    this.stats.totalExecutions++;
    this.stats.executionsByEvent[hook.event]++;
    if (!success) {
      this.stats.totalErrors++;
    }
  }

  /**
   * 获取统计信息
   */
  getStats() {
    return {
      ...this.stats,
      hookCount: this.hookById.size,
      enabledCount: Array.from(this.hookById.values()).filter(h => h.enabled).length,
    };
  }

  /**
   * 从配置文件加载钩子
   */
  async loadFromConfig(configPath) {
    try {
      const content = await fs.readFile(configPath, 'utf-8');
      const config = JSON.parse(content);

      if (config.hooks) {
        for (const [eventName, hookConfigs] of Object.entries(config.hooks)) {
          for (const hookConfig of hookConfigs) {
            this.register({
              event: eventName,
              ...hookConfig,
              metadata: { ...hookConfig.metadata, source: 'config', configPath },
            });
          }
        }
      }

      this.emit('config-loaded', { configPath, hookCount: this.hookById.size });
      return true;
    } catch (error) {
      this.emit('config-error', { configPath, error });
      return false;
    }
  }

  /**
   * 清除所有钩子
   */
  clear() {
    this.hooks.forEach((_, key) => this.hooks.set(key, []));
    this.hookById.clear();
    this.stats.totalRegistered = 0;
    this.emit('hooks-cleared');
  }

  _generateId() {
    return `hook_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

module.exports = {
  HookRegistry,
  HookPriority,
  HookType,
};

3.2 HookExecutor (钩子执行器) ​

javascript
// 文件: src/main/hooks/hook-executor.js

const { spawn } = require('child_process');
const path = require('path');
const { EventEmitter } = require('events');

/**
 * 钩子执行结果
 */
const HookResult = {
  CONTINUE: 'continue',       // 继续执行
  PREVENT: 'prevent',         // 阻止后续操作
  MODIFY: 'modify',           // 修改数据后继续
  ERROR: 'error',             // 执行出错
};

/**
 * 钩子执行器
 */
class HookExecutor extends EventEmitter {
  constructor(registry, options = {}) {
    super();

    this.registry = registry;
    this.options = {
      defaultTimeout: 30000,
      maxConcurrent: 10,
      continueOnError: true,    // 单个钩子出错是否继续
      ...options,
    };

    this.runningHooks = new Map();  // hookId -> AbortController
  }

  /**
   * 触发钩子事件
   * @param {string} eventName 事件名称
   * @param {Object} eventData 事件数据
   * @param {Object} context 执行上下文
   * @returns {Object} 执行结果
   */
  async trigger(eventName, eventData = {}, context = {}) {
    if (!this.registry.enabled) {
      return { result: HookResult.CONTINUE, data: eventData };
    }

    const hooks = this.registry.getHooks(eventName, {
      enabledOnly: true,
      matchContext: { ...context, ...eventData },
    });

    if (hooks.length === 0) {
      return { result: HookResult.CONTINUE, data: eventData };
    }

    const event = {
      eventName,
      eventId: this._generateEventId(),
      timestamp: Date.now(),
      context,
      data: { ...eventData },
      prevented: false,
      preventReason: null,
      modifications: {},
    };

    this.emit('execution-start', { event, hookCount: hooks.length });

    let currentData = { ...eventData };
    const results = [];

    for (const hook of hooks) {
      // 检查是否已被阻止
      if (event.prevented && hook.priority !== 1000) {
        // 监控级钩子仍然执行
        continue;
      }

      const startTime = Date.now();
      let hookResult;

      try {
        hookResult = await this._executeHook(hook, event, currentData);

        // 处理结果
        if (hookResult.result === HookResult.PREVENT) {
          event.prevented = true;
          event.preventReason = hookResult.reason || `Prevented by hook: ${hook.name}`;
        }

        if (hookResult.result === HookResult.MODIFY && hookResult.data) {
          currentData = { ...currentData, ...hookResult.data };
          event.modifications = { ...event.modifications, ...hookResult.data };
        }

        this.registry.updateStats(hook.id, {
          executionTime: Date.now() - startTime,
          success: true,
        });

      } catch (error) {
        hookResult = {
          result: HookResult.ERROR,
          error: error.message,
          hookId: hook.id,
          hookName: hook.name,
        };

        this.registry.updateStats(hook.id, {
          executionTime: Date.now() - startTime,
          success: false,
        });

        this.emit('hook-error', { hook, error, event });

        if (!this.options.continueOnError) {
          throw error;
        }
      }

      results.push({
        hookId: hook.id,
        hookName: hook.name,
        ...hookResult,
        executionTime: Date.now() - startTime,
      });
    }

    const finalResult = {
      result: event.prevented ? HookResult.PREVENT : HookResult.CONTINUE,
      prevented: event.prevented,
      preventReason: event.preventReason,
      data: currentData,
      modifications: event.modifications,
      hookResults: results,
      totalHooks: hooks.length,
      executedHooks: results.length,
    };

    this.emit('execution-complete', { event, finalResult });

    return finalResult;
  }

  /**
   * 执行单个钩子
   */
  async _executeHook(hook, event, data) {
    const controller = new AbortController();
    this.runningHooks.set(hook.id, controller);

    const timeout = hook.timeout || this.options.defaultTimeout;
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    try {
      let result;

      switch (hook.type) {
        case 'sync':
        case 'async':
          result = await this._executeFunctionHook(hook, event, data, controller.signal);
          break;

        case 'command':
          result = await this._executeCommandHook(hook, event, data, controller.signal);
          break;

        case 'script':
          result = await this._executeScriptHook(hook, event, data, controller.signal);
          break;

        default:
          throw new Error(`Unknown hook type: ${hook.type}`);
      }

      return result;

    } finally {
      clearTimeout(timeoutId);
      this.runningHooks.delete(hook.id);
    }
  }

  /**
   * 执行函数类型钩子
   */
  async _executeFunctionHook(hook, event, data, signal) {
    if (signal.aborted) {
      throw new Error('Hook execution aborted (timeout)');
    }

    const result = await hook.handler({
      event,
      data,
      context: event.context,
      signal,
    });

    return this._normalizeResult(result);
  }

  /**
   * 执行命令类型钩子
   */
  async _executeCommandHook(hook, event, data, signal) {
    return new Promise((resolve, reject) => {
      if (signal.aborted) {
        return reject(new Error('Hook execution aborted (timeout)'));
      }

      // 设置环境变量
      const env = {
        ...process.env,
        HOOK_EVENT: event.eventName,
        HOOK_EVENT_ID: event.eventId,
        HOOK_DATA: JSON.stringify(data),
        HOOK_CONTEXT: JSON.stringify(event.context),
      };

      // 为特定事件设置额外环境变量
      if (data.toolName) env.TOOL_NAME = data.toolName;
      if (data.filePath) env.FILE_PATH = data.filePath;
      if (data.channel) env.IPC_CHANNEL = data.channel;

      const [cmd, ...args] = hook.command.split(' ');
      const child = spawn(cmd, args, {
        env,
        shell: true,
        cwd: event.context.workingDirectory || process.cwd(),
      });

      let stdout = '';
      let stderr = '';

      child.stdout.on('data', (data) => {
        stdout += data.toString();
      });

      child.stderr.on('data', (data) => {
        stderr += data.toString();
      });

      const abortHandler = () => {
        child.kill('SIGTERM');
        reject(new Error('Hook execution aborted (timeout)'));
      };
      signal.addEventListener('abort', abortHandler);

      child.on('close', (code) => {
        signal.removeEventListener('abort', abortHandler);

        if (code === 0) {
          // 尝试解析 JSON 输出
          const result = this._parseCommandOutput(stdout);
          resolve(result);
        } else if (code === 2) {
          // 退出码 2 表示阻止操作
          resolve({
            result: HookResult.PREVENT,
            reason: stderr || stdout || 'Prevented by hook command',
          });
        } else {
          reject(new Error(`Hook command exited with code ${code}: ${stderr}`));
        }
      });

      child.on('error', (error) => {
        signal.removeEventListener('abort', abortHandler);
        reject(error);
      });
    });
  }

  /**
   * 执行脚本类型钩子
   */
  async _executeScriptHook(hook, event, data, signal) {
    const scriptPath = hook.script;
    const ext = path.extname(scriptPath);

    let command;
    switch (ext) {
      case '.js':
        command = `node "${scriptPath}"`;
        break;
      case '.py':
        command = `python "${scriptPath}"`;
        break;
      case '.sh':
        command = `bash "${scriptPath}"`;
        break;
      case '.ps1':
        command = `powershell -File "${scriptPath}"`;
        break;
      default:
        command = `"${scriptPath}"`;
    }

    return this._executeCommandHook(
      { ...hook, command },
      event,
      data,
      signal
    );
  }

  /**
   * 解析命令输出
   */
  _parseCommandOutput(stdout) {
    const trimmed = stdout.trim();

    if (!trimmed) {
      return { result: HookResult.CONTINUE };
    }

    try {
      const json = JSON.parse(trimmed);
      return this._normalizeResult(json);
    } catch {
      // 非 JSON 输出,作为消息处理
      return {
        result: HookResult.CONTINUE,
        message: trimmed,
      };
    }
  }

  /**
   * 标准化钩子结果
   */
  _normalizeResult(result) {
    if (!result) {
      return { result: HookResult.CONTINUE };
    }

    if (typeof result === 'boolean') {
      return {
        result: result ? HookResult.CONTINUE : HookResult.PREVENT,
      };
    }

    if (typeof result === 'object') {
      // 支持多种返回格式
      if (result.prevent === true || result.blocked === true) {
        return {
          result: HookResult.PREVENT,
          reason: result.reason || result.message,
        };
      }

      if (result.modify || result.data) {
        return {
          result: HookResult.MODIFY,
          data: result.modify || result.data,
        };
      }

      if (result.result && Object.values(HookResult).includes(result.result)) {
        return result;
      }

      return { result: HookResult.CONTINUE, ...result };
    }

    return { result: HookResult.CONTINUE };
  }

  /**
   * 取消正在运行的钩子
   */
  cancelHook(hookId) {
    const controller = this.runningHooks.get(hookId);
    if (controller) {
      controller.abort();
      return true;
    }
    return false;
  }

  /**
   * 取消所有正在运行的钩子
   */
  cancelAll() {
    this.runningHooks.forEach((controller, hookId) => {
      controller.abort();
    });
    this.runningHooks.clear();
  }

  _generateEventId() {
    return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

module.exports = {
  HookExecutor,
  HookResult,
};

3.3 HookMiddleware (IPC 中间件集成) ​

javascript
// 文件: src/main/hooks/hook-middleware.js

const { HookResult } = require('./hook-executor');

/**
 * 创建 IPC 钩子中间件
 * 包装 IPC 处理器,自动触发 Pre/Post 钩子
 */
function createIPCHookMiddleware(hookSystem) {
  return {
    /**
     * 包装 IPC 处理器
     */
    wrap(channel, handler, options = {}) {
      const {
        skipPreHook = false,
        skipPostHook = false,
        contextExtractor = null,  // 自定义上下文提取器
      } = options;

      return async (event, ...args) => {
        const context = {
          channel,
          args,
          sender: event.sender?.id,
          timestamp: Date.now(),
          ...(contextExtractor ? contextExtractor(event, args) : {}),
        };

        // Pre 钩子
        if (!skipPreHook) {
          const preResult = await hookSystem.trigger('PreIPCCall', {
            channel,
            args,
          }, context);

          if (preResult.prevented) {
            throw new Error(`IPC call prevented: ${preResult.preventReason}`);
          }

          // 应用修改
          if (preResult.modifications && Object.keys(preResult.modifications).length > 0) {
            if (preResult.modifications.args) {
              args = preResult.modifications.args;
            }
          }
        }

        let result;
        let error;

        try {
          result = await handler(event, ...args);
        } catch (err) {
          error = err;

          // 错误钩子
          await hookSystem.trigger('IPCError', {
            channel,
            args,
            error: {
              message: err.message,
              stack: err.stack,
              code: err.code,
            },
          }, context);

          throw err;
        }

        // Post 钩子
        if (!skipPostHook && !error) {
          const postResult = await hookSystem.trigger('PostIPCCall', {
            channel,
            args,
            result,
            executionTime: Date.now() - context.timestamp,
          }, context);

          // Post 钩子可以修改返回值
          if (postResult.modifications && postResult.modifications.result !== undefined) {
            result = postResult.modifications.result;
          }
        }

        return result;
      };
    },

    /**
     * 批量包装 IPC 处理器
     */
    wrapAll(handlers) {
      const wrapped = {};
      for (const [channel, handler] of Object.entries(handlers)) {
        wrapped[channel] = this.wrap(channel, handler);
      }
      return wrapped;
    },
  };
}

/**
 * 创建工具调用钩子中间件
 */
function createToolHookMiddleware(hookSystem) {
  return {
    /**
     * 包装工具处理器
     */
    wrap(toolName, handler, options = {}) {
      return async (params, context = {}) => {
        const toolContext = {
          toolName,
          params,
          ...context,
        };

        // PreToolUse 钩子
        const preResult = await hookSystem.trigger('PreToolUse', {
          toolName,
          params,
        }, toolContext);

        if (preResult.prevented) {
          return {
            success: false,
            error: `Tool use prevented: ${preResult.preventReason}`,
            prevented: true,
          };
        }

        // 应用参数修改
        let finalParams = params;
        if (preResult.modifications && preResult.modifications.params) {
          finalParams = { ...params, ...preResult.modifications.params };
        }

        let result;
        let error;
        const startTime = Date.now();

        try {
          result = await handler(finalParams, context);
        } catch (err) {
          error = err;

          // ToolError 钩子
          await hookSystem.trigger('ToolError', {
            toolName,
            params: finalParams,
            error: {
              message: err.message,
              stack: err.stack,
            },
          }, toolContext);

          throw err;
        }

        // PostToolUse 钩子
        const postResult = await hookSystem.trigger('PostToolUse', {
          toolName,
          params: finalParams,
          result,
          executionTime: Date.now() - startTime,
        }, toolContext);

        // 允许修改结果
        if (postResult.modifications && postResult.modifications.result !== undefined) {
          result = postResult.modifications.result;
        }

        return result;
      };
    },
  };
}

/**
 * 创建会话钩子中间件
 */
function createSessionHookMiddleware(hookSystem) {
  return {
    /**
     * 绑定到 SessionManager
     */
    bindToSessionManager(sessionManager) {
      // 会话开始
      sessionManager.on('session-created', async ({ sessionId, metadata }) => {
        await hookSystem.trigger('SessionStart', {
          sessionId,
          metadata,
        }, { sessionId });
      });

      // 会话结束
      sessionManager.on('session-ended', async ({ sessionId, reason }) => {
        await hookSystem.trigger('SessionEnd', {
          sessionId,
          reason,
        }, { sessionId });
      });

      // 上下文压缩前
      const originalCompress = sessionManager.compressContext?.bind(sessionManager);
      if (originalCompress) {
        sessionManager.compressContext = async function(...args) {
          const preResult = await hookSystem.trigger('PreCompact', {
            sessionId: this.currentSessionId,
            messageCount: this.messages?.length,
          }, { sessionId: this.currentSessionId });

          if (preResult.prevented) {
            console.log(`Context compression prevented: ${preResult.preventReason}`);
            return null;
          }

          const result = await originalCompress(...args);

          await hookSystem.trigger('PostCompact', {
            sessionId: this.currentSessionId,
            compressionRatio: result?.compressionRatio,
          }, { sessionId: this.currentSessionId });

          return result;
        };
      }
    },
  };
}

module.exports = {
  createIPCHookMiddleware,
  createToolHookMiddleware,
  createSessionHookMiddleware,
};

3.4 HookSystem (统一入口) ​

javascript
// 文件: src/main/hooks/index.js

const { HookRegistry, HookPriority, HookType } = require('./hook-registry');
const { HookExecutor, HookResult } = require('./hook-executor');
const {
  createIPCHookMiddleware,
  createToolHookMiddleware,
  createSessionHookMiddleware,
} = require('./hook-middleware');
const path = require('path');
const fs = require('fs').promises;

/**
 * Hooks 系统主类
 */
class HookSystem {
  constructor(options = {}) {
    this.options = {
      configPaths: options.configPaths || [],
      autoLoadConfig: options.autoLoadConfig !== false,
      ...options,
    };

    // 核心组件
    this.registry = new HookRegistry(options);
    this.executor = new HookExecutor(this.registry, options);

    // 中间件
    this.ipcMiddleware = createIPCHookMiddleware(this);
    this.toolMiddleware = createToolHookMiddleware(this);
    this.sessionMiddleware = createSessionHookMiddleware(this);

    // 事件转发
    this._setupEventForwarding();
  }

  /**
   * 初始化
   */
  async initialize() {
    if (this.options.autoLoadConfig) {
      await this.loadDefaultConfigs();
    }

    // 注册内置钩子
    this._registerBuiltinHooks();

    return this;
  }

  /**
   * 加载默认配置文件
   */
  async loadDefaultConfigs() {
    const configPaths = [
      // 项目级配置
      path.join(process.cwd(), '.chainlesschain', 'hooks.json'),
      // 用户级配置
      path.join(process.env.HOME || process.env.USERPROFILE, '.chainlesschain', 'hooks.json'),
      // 自定义配置路径
      ...this.options.configPaths,
    ];

    for (const configPath of configPaths) {
      try {
        await fs.access(configPath);
        await this.registry.loadFromConfig(configPath);
        console.log(`[HookSystem] Loaded hooks from: ${configPath}`);
      } catch {
        // 配置文件不存在,跳过
      }
    }

    // 加载脚本钩子目录
    const scriptDirs = [
      path.join(process.cwd(), '.chainlesschain', 'hooks'),
      path.join(process.env.HOME || process.env.USERPROFILE, '.chainlesschain', 'hooks'),
    ];

    for (const scriptDir of scriptDirs) {
      await this._loadScriptHooks(scriptDir);
    }
  }

  /**
   * 加载脚本钩子
   */
  async _loadScriptHooks(dirPath) {
    try {
      const files = await fs.readdir(dirPath);

      for (const file of files) {
        if (!file.endsWith('.js')) continue;

        const scriptPath = path.join(dirPath, file);
        try {
          const hookModule = require(scriptPath);

          if (hookModule.hooks && Array.isArray(hookModule.hooks)) {
            this.registry.registerMultiple(hookModule.hooks);
            console.log(`[HookSystem] Loaded script hooks from: ${scriptPath}`);
          }
        } catch (error) {
          console.error(`[HookSystem] Failed to load script: ${scriptPath}`, error.message);
        }
      }
    } catch {
      // 目录不存在,跳过
    }
  }

  /**
   * 注册内置钩子
   */
  _registerBuiltinHooks() {
    // 性能监控钩子
    this.registry.register({
      event: 'PostIPCCall',
      name: 'builtin-performance-logger',
      type: HookType.ASYNC,
      priority: HookPriority.MONITOR,
      handler: async ({ data }) => {
        if (data.executionTime > 1000) {
          console.warn(`[Slow IPC] ${data.channel} took ${data.executionTime}ms`);
        }
        return { result: HookResult.CONTINUE };
      },
    });

    // 错误统计钩子
    this.registry.register({
      event: 'IPCError',
      name: 'builtin-error-counter',
      type: HookType.ASYNC,
      priority: HookPriority.MONITOR,
      handler: async ({ data }) => {
        // 可以在这里添加错误统计逻辑
        return { result: HookResult.CONTINUE };
      },
    });
  }

  /**
   * 设置事件转发
   */
  _setupEventForwarding() {
    // 将 registry 和 executor 的事件转发到 HookSystem
    this.registry.on('hook-registered', (data) => this.emit('hook-registered', data));
    this.registry.on('hook-unregistered', (data) => this.emit('hook-unregistered', data));
    this.executor.on('execution-start', (data) => this.emit('execution-start', data));
    this.executor.on('execution-complete', (data) => this.emit('execution-complete', data));
    this.executor.on('hook-error', (data) => this.emit('hook-error', data));
  }

  // ==================== 公共 API ====================

  /**
   * 注册钩子
   */
  register(hookConfig) {
    return this.registry.register(hookConfig);
  }

  /**
   * 批量注册
   */
  registerMultiple(hookConfigs) {
    return this.registry.registerMultiple(hookConfigs);
  }

  /**
   * 注销钩子
   */
  unregister(hookId) {
    return this.registry.unregister(hookId);
  }

  /**
   * 触发事件
   */
  async trigger(eventName, data = {}, context = {}) {
    return this.executor.trigger(eventName, data, context);
  }

  /**
   * 获取钩子列表
   */
  listHooks(options) {
    return this.registry.listHooks(options);
  }

  /**
   * 获取统计信息
   */
  getStats() {
    return this.registry.getStats();
  }

  /**
   * 启用/禁用全局钩子
   */
  setEnabled(enabled) {
    this.registry.enabled = enabled;
  }

  /**
   * 启用/禁用单个钩子
   */
  setHookEnabled(hookId, enabled) {
    return this.registry.setEnabled(hookId, enabled);
  }
}

// 使 HookSystem 继承 EventEmitter
const { EventEmitter } = require('events');
Object.setPrototypeOf(HookSystem.prototype, EventEmitter.prototype);

// 单例
let hookSystemInstance = null;

function getHookSystem(options) {
  if (!hookSystemInstance) {
    hookSystemInstance = new HookSystem(options);
  }
  return hookSystemInstance;
}

function initializeHookSystem(options) {
  hookSystemInstance = new HookSystem(options);
  return hookSystemInstance.initialize();
}

module.exports = {
  HookSystem,
  HookRegistry,
  HookExecutor,
  HookPriority,
  HookType,
  HookResult,
  getHookSystem,
  initializeHookSystem,
  createIPCHookMiddleware,
  createToolHookMiddleware,
  createSessionHookMiddleware,
};

4. 配置文件格式 ​

4.1 hooks.json 配置示例 ​

json
{
  "$schema": "./hooks-schema.json",
  "version": "1.0.0",
  "enabled": true,

  "hooks": {
    "PreToolUse": [
      {
        "name": "validate-file-paths",
        "description": "验证文件路径安全性",
        "type": "async",
        "priority": 100,
        "matcher": "file_*",
        "timeout": 5000,
        "handler": "validateFilePath"
      },
      {
        "name": "log-tool-usage",
        "description": "记录工具使用日志",
        "type": "command",
        "priority": 900,
        "command": "echo \"Tool: $TOOL_NAME\" >> /tmp/tool.log"
      }
    ],

    "PostToolUse": [
      {
        "name": "format-lint-results",
        "description": "格式化 lint 工具输出",
        "type": "async",
        "matcher": "tool_eslint|tool_prettier",
        "handler": "formatLintResults"
      }
    ],

    "PreCompact": [
      {
        "name": "save-to-permanent-memory",
        "description": "压缩前保存重要信息到永久记忆",
        "type": "async",
        "priority": 100,
        "handler": "saveToMemory"
      }
    ],

    "UserPromptSubmit": [
      {
        "name": "inject-project-context",
        "description": "注入项目上下文信息",
        "type": "async",
        "handler": "injectContext"
      },
      {
        "name": "validate-prompt-safety",
        "description": "验证提示安全性",
        "type": "command",
        "command": "./scripts/validate-prompt.sh",
        "timeout": 10000
      }
    ],

    "FileModified": [
      {
        "name": "auto-lint-on-save",
        "description": "保存时自动 lint",
        "type": "command",
        "matcher": "\\.(js|ts|vue)$",
        "command": "npm run lint -- $FILE_PATH"
      }
    ]
  },

  "settings": {
    "continueOnError": true,
    "defaultTimeout": 30000,
    "maxConcurrent": 10
  }
}

4.2 脚本钩子示例 ​

javascript
// 文件: .chainlesschain/hooks/custom-hooks.js

module.exports = {
  hooks: [
    {
      event: 'PreToolUse',
      name: 'custom-tool-validator',
      type: 'async',
      priority: 200,
      matcher: /^tool_/,
      handler: async ({ data, context }) => {
        const { toolName, params } = data;

        // 自定义验证逻辑
        if (toolName === 'tool_file_delete' && !params.confirmed) {
          return {
            result: 'prevent',
            reason: 'File deletion requires confirmation',
          };
        }

        return { result: 'continue' };
      },
    },

    {
      event: 'PostIPCCall',
      name: 'custom-response-logger',
      type: 'async',
      priority: 900,
      handler: async ({ data }) => {
        console.log(`[Custom Logger] ${data.channel}: ${data.executionTime}ms`);
        return { result: 'continue' };
      },
    },
  ],
};

5. 集成到现有系统 ​

5.1 Bootstrap 集成 ​

javascript
// 修改: src/main/bootstrap/index.js

const { initializeHookSystem } = require('../hooks');

async function bootstrapApplication(options = {}) {
  const { progressCallback } = options;

  // Phase 0: 初始化 Hooks 系统 (最先执行)
  progressCallback('Initializing hooks system...', 0);
  const hookSystem = await initializeHookSystem({
    configPaths: [
      options.hooksConfigPath,
    ],
  });

  // 将 hookSystem 添加到 dependencies
  const dependencies = {
    hookSystem,
    // ... 其他依赖
  };

  // ... 后续初始化阶段
}

5.2 IPC Registry 集成 ​

javascript
// 修改: src/main/ipc/ipc-registry.js

const { getHookSystem } = require('../hooks');

function registerAllIPC(dependencies) {
  const hookSystem = dependencies.hookSystem || getHookSystem();
  const ipcMiddleware = hookSystem.ipcMiddleware;

  // 包装所有 IPC 处理器
  const wrappedHandlers = {};

  // 示例: 包装 LLM IPC
  const llmHandlers = {
    'llm:send-message': async (event, params) => {
      // 原始处理逻辑
    },
  };

  // 使用中间件包装
  for (const [channel, handler] of Object.entries(llmHandlers)) {
    ipcMain.handle(channel, ipcMiddleware.wrap(channel, handler));
  }
}

5.3 FunctionCaller 集成 ​

javascript
// 修改: src/main/ai-engine/function-caller.js

const { getHookSystem } = require('../hooks');

class FunctionCaller {
  constructor(options = {}) {
    // ...
    this.hookSystem = options.hookSystem || getHookSystem();
    this.toolMiddleware = this.hookSystem.toolMiddleware;
  }

  registerTool(name, handler, schema) {
    // 使用中间件包装工具处理器
    const wrappedHandler = this.toolMiddleware.wrap(name, handler);
    this.tools.set(name, { handler: wrappedHandler, schema });
  }
}

5.4 SessionManager 集成 ​

javascript
// 修改: src/main/llm/session-manager.js

const { getHookSystem } = require('../hooks');

class SessionManager extends EventEmitter {
  constructor(options = {}) {
    super();
    // ...

    // 绑定钩子
    const hookSystem = options.hookSystem || getHookSystem();
    hookSystem.sessionMiddleware.bindToSessionManager(this);
  }
}

6. IPC 接口定义 ​

javascript
// 文件: src/main/hooks/hooks-ipc.js

const { ipcMain } = require('electron');
const { getHookSystem } = require('./index');

function registerHooksIPC() {
  const hookSystem = getHookSystem();

  // 获取钩子列表
  ipcMain.handle('hooks:list', async (event, options) => {
    return hookSystem.listHooks(options);
  });

  // 获取统计信息
  ipcMain.handle('hooks:stats', async () => {
    return hookSystem.getStats();
  });

  // 启用/禁用钩子
  ipcMain.handle('hooks:set-enabled', async (event, { hookId, enabled }) => {
    return hookSystem.setHookEnabled(hookId, enabled);
  });

  // 注册新钩子 (仅允许特定类型)
  ipcMain.handle('hooks:register', async (event, hookConfig) => {
    // 安全检查: 只允许注册命令类型钩子
    if (hookConfig.type === 'command' || hookConfig.type === 'script') {
      return hookSystem.register(hookConfig);
    }
    throw new Error('Only command and script hooks can be registered via IPC');
  });

  // 注销钩子
  ipcMain.handle('hooks:unregister', async (event, hookId) => {
    return hookSystem.unregister(hookId);
  });

  // 手动触发钩子 (调试用)
  ipcMain.handle('hooks:trigger', async (event, { eventName, data, context }) => {
    return hookSystem.trigger(eventName, data, context);
  });
}

module.exports = { registerHooksIPC };

7. 文件结构 ​

desktop-app-vue/src/main/hooks/
├── index.js                    # 主入口和单例管理
├── hook-registry.js            # 钩子注册表
├── hook-executor.js            # 钩子执行器
├── hook-middleware.js          # 中间件工厂
├── hooks-ipc.js               # IPC 接口
├── builtin-hooks/             # 内置钩子
│   ├── performance-hooks.js   # 性能监控钩子
│   ├── security-hooks.js      # 安全检查钩子
│   └── logging-hooks.js       # 日志钩子
└── types.d.ts                 # TypeScript 类型定义

配置文件位置:
.chainlesschain/
├── hooks.json                 # 项目级钩子配置
└── hooks/                     # 项目级脚本钩子
    ├── custom-hooks.js
    └── my-validator.js

~/.chainlesschain/
├── hooks.json                 # 用户级钩子配置
└── hooks/                     # 用户级脚本钩子

8. 使用示例 ​

8.1 注册函数钩子 ​

javascript
const { getHookSystem, HookPriority } = require('./hooks');

const hookSystem = getHookSystem();

// 注册验证钩子
hookSystem.register({
  event: 'PreToolUse',
  name: 'my-tool-validator',
  priority: HookPriority.HIGH,
  matcher: /^tool_file/,  // 匹配所有文件工具
  handler: async ({ data, context }) => {
    const { toolName, params } = data;

    // 检查危险操作
    if (toolName === 'tool_file_delete') {
      if (!params.path.startsWith('/tmp/')) {
        return {
          result: 'prevent',
          reason: 'Only /tmp files can be deleted',
        };
      }
    }

    return { result: 'continue' };
  },
});

8.2 注册命令钩子 ​

javascript
hookSystem.register({
  event: 'PostToolUse',
  name: 'lint-after-edit',
  type: 'command',
  matcher: 'Edit|Write',
  command: 'npm run lint:fix -- "$FILE_PATH"',
  timeout: 60000,
});

8.3 监听钩子事件 ​

javascript
hookSystem.on('execution-complete', ({ event, finalResult }) => {
  console.log(`[Hooks] ${event.eventName}: ${finalResult.executedHooks} hooks executed`);

  if (finalResult.prevented) {
    console.warn(`[Hooks] Operation prevented: ${finalResult.preventReason}`);
  }
});

hookSystem.on('hook-error', ({ hook, error }) => {
  console.error(`[Hooks] Error in hook ${hook.name}:`, error.message);
});

9. 安全考虑 ​

  1. 命令注入防护: 命令钩子的环境变量经过转义
  2. 超时保护: 所有钩子有默认超时
  3. 权限隔离: IPC 只能注册命令/脚本类型钩子
  4. 沙箱执行: 脚本钩子在隔离环境执行
  5. 审计日志: 所有钩子执行都有日志记录

10. 性能优化 ​

  1. 匹配器缓存: 正则表达式匹配器编译后缓存
  2. 并行执行: 同优先级钩子可并行执行
  3. 懒加载: 脚本钩子按需加载
  4. 统计采样: 高频事件使用采样统计

附录:规范章节补全(v5.0.3.108) ​

本文为设计文档。为对齐项目文档标准结构,下列章节以 见正文 指引或简述方式补齐若干视角,不重复正文细节。

1. 概述 ​

见正文头部。Hooks 系统设计方案:cc / settings.json hooks 引擎。

2. 核心特性 ​

PreToolUse/PostToolUse / matcher / command hook / settings.json。

3. 系统架构 ​

见正文架构 / 设计章节。

4. 系统定位 ​

ChainlessChain 的「Hooks 系统」。

5. 核心功能 ​

见正文功能 / 设计章节。

6. 技术架构 ​

见正文实现 / 技术章节。

7. 系统特点 ​

见正文(状态 / 版本 / 特性)。

8. 应用场景 ​

见正文应用场景 / 背景。

9. 竞品对比 ​

见正文对比 / 借鉴(如有)。

10. 配置参考 ​

见正文配置 / 参数章节。

11. 性能指标 ​

见正文性能 / 指标章节。

12. 测试覆盖 ​

见正文测试 / E2E 章节。

13. 安全考虑 ​

见正文安全 / 权限章节。

14. 故障排除 ​

见正文故障 / trap / 已知限制章节。

15. 关键文件 ​

见正文实现位置 / 关键文件章节。

16. 使用示例 ​

见正文使用 / 命令 / API 示例。

17. 相关文档 ​

系统设计主文档、相关设计文档。

基于 MIT 许可发布