Hook 机制
事件驱动钩子系统——React Hooks 状态管理、通知 Hooks、MCP Elicitation Hooks、Channel 权限钩子、工具合并与插件生命周期管理
职责概述
解决的问题:CC 的核心流程是"模型回复→执行工具→返回结果",但用户经常需要在特定时机插入自定义逻辑——比如工具执行前做安全检查、提交代码后自动格式化、MCP 断连时发通知。Hook 系统就是这些"自定义插入点"的统一框架。
应用场景:① 每次工具执行前运行自定义安全检查脚本 ② 代码提交后自动运行 lint 和格式化 ③ MCP 连接状态变化时弹出桌面通知 ④ 权限请求时的自定义审批逻辑 ⑤ UI 组件通过 React Hooks 订阅状态变化和用户事件。
一句话理解:就像 Git Hooks——你在 commit 前自动跑测试,push 前自动检查。CC 的 Hooks 是整个生命周期的可编程拦截点。
架构设计
核心数据流
Hook 生命周期(useNotifyAfterTimeout)
插件管理 Hook 数据流
工具合并决策树
关键类型与接口
useNotifyAfterTimeout Hook
// hooks/useNotifyAfterTimeout.ts:38-65
export function useNotifyAfterTimeout(
message: string,
notificationType: string,
): void {
const terminal = useTerminalNotification()
// 重置交互时间(防止长请求完成后立即通知)
useEffect(() => {
updateLastInteractionTime(true)
}, [])
useEffect(() => {
let hasNotified = false
const timer = setInterval(() => {
if (shouldNotify(DEFAULT_INTERACTION_THRESHOLD_MS) && !hasNotified) {
hasNotified = true
clearInterval(timer)
void sendNotification({ message, notificationType }, terminal)
}
}, DEFAULT_INTERACTION_THRESHOLD_MS) // 6秒轮询
return () => clearInterval(timer)
}, [message, notificationType, terminal])
}
useManagePlugins Hook 签名
// hooks/useManagePlugins.ts:37-39
export function useManagePlugins({
enabled = true,
}: {
enabled?: boolean
} = {})
// 返回 void — 通过 setAppState 副作用更新全局状态
// 主要效果:
// - 加载所有插件到 AppState.plugins
// - 运行 delist 检查和 flag 通知
// - 遥测 tengu_plugins_loaded 事件
useMergedTools Hook
// hooks/useMergedTools.ts:20-44
export function useMergedTools(
initialTools: Tools,
mcpTools: Tools,
toolPermissionContext: ToolPermissionContext,
): Tools {
return useMemo(() => {
// 组装工具池:内置 + MCP,应用 deny 规则和去重
const assembled = assembleToolPool(toolPermissionContext, mcpTools)
return mergeAndFilterTools(
initialTools, assembled, toolPermissionContext.mode,
)
}, [initialTools, mcpTools, toolPermissionContext,
replBridgeEnabled, replBridgeOutboundOnly])
}
通知 Hooks 列表
// hooks/notifs/ — 集中的通知 Hook 模块
useAutoModeUnavailableNotification // Auto Mode 不可用提示
useCanSwitchToExistingSubscription // 订阅切换建议
useDeprecationWarningNotification // 弃用警告
useFastModeNotification // 快速模式提示
useIDEStatusIndicator // IDE 连接状态指示
useInstallMessages // 安装消息显示
useLspInitializationNotification // LSP 初始化状态
useMcpConnectivityStatus // MCP 连接状态通知
useModelMigrationNotifications // 模型迁移通知
useNpmDeprecationNotification // npm 弃用通知
usePluginAutoupdateNotification // 插件自动更新通知
usePluginInstallationStatus // 插件安装进度
useRateLimitWarningNotification // 速率限制警告
useSettingsErrors // 设置错误提示
useStartupNotification // 启动消息
useTeammateShutdownNotification // 队友关闭通知
设计模式与亮点
集中式通知 Hook 模式
hooks/notifs/ 目录将所有通知逻辑集中管理,每个通知都是一个独立的 React Hook。这种模式的好处:(1) 通知逻辑与 UI 组件解耦;(2) 可以在任意组件树的任意层级启用/禁用通知;(3) 通知的状态(是否已显示、去重)封装在 Hook 内部。典型用法是顶层 App 组件同时挂载所有通知 Hook。
idle-detection 通知策略
useNotifyAfterTimeout 实现了智能的通知时机选择:只在用户空闲超过 6 秒时才发送桌面通知。这避免了用户正在打字时弹出干扰通知。交互时间追踪由 App.tsx 的 processKeysInBatch 函数更新,避免与主 stdin 监听器冲突。
useMemo 工具池缓存
useMergedTools 通过 useMemo 缓存工具池计算结果。依赖项包括 initialTools、mcpTools、toolPermissionContext。由于 MCP 工具列表在连接/断开时会变化(通过 AppState.mcp.tools),这个缓存确保只在工具列表实际变更时重新计算,避免每次渲染都重新组装。
错误隔离与收集
useManagePlugins(L70-109)对每个组件加载(commands、agents、hooks、MCP、LSP)都用独立的 try-catch 包裹。单个组件的加载失败不会阻止其他组件加载,错误被推入 errors[] 数组并在 Doctor UI 中显示。同时与已有的 LSP 错误合并,去重后写入 AppState。
开发者实践指南
添加新的通知 Hook
按照 hooks/notifs/ 目录中现有 Hook 的模式创建:
- 在
hooks/notifs/下创建新文件(如useMyNotification.tsx) - 使用
useNotifications()获取addNotification函数 - 用
useEffect或useAppState订阅触发条件 - 在顶层 App 组件中挂载新 Hook
// hooks/notifs/useMyNotification.tsx
import { useEffect } from 'react'
import { useNotifications } from '../../context/notifications.js'
import { useAppState } from '../../state/AppState.js'
export function useMyNotification() {
const someCondition = useAppState(s => s.someField)
const { addNotification } = useNotifications()
useEffect(() => {
if (someCondition) {
addNotification({
key: 'my-notification',
text: 'Something happened!',
color: 'warning',
priority: 'high',
})
}
}, [someCondition, addNotification])
}
理解工具合并链
工具池的组装链:getTools()(内置工具)+ mcpTools(MCP 发现的工具)→ assembleToolPool()(应用 deny 规则 + 去重)→ mergeAndFilterTools()(合并 initialTools 带优先级)。在添加自定义工具时,确保通过 initialTools 参数传入以获得优先级。
logForDebugging 输出关键状态变更。使用 --debug 标志查看详细的 Hook 执行日志。对于通知 Hook,关注 addNotification 调用时机和去重 key。
架构师决策指南
React Hook vs 事件系统
Claude Code 选择 React Hook 作为主要的状态管理机制而非独立的事件总线。好处是 Hook 天然与 React 渲染周期对齐,避免了事件监听器泄漏和状态不一致。代价是 Hook 无法在 React 组件树外使用——这就是为什么 MCP 层(useManageMCPConnections)和 Hook 层之间存在 Context 桥接(MCPConnectionManager)。
手动刷新 vs 自动刷新
插件状态变更采用手动刷新(/reload-plugins)而非自动刷新。决策原因是自动刷新曾导致缓存一致性 bug:loadAllPlugins 的 memoize 被清除了,但下游的 getPluginCommands、loadPluginAgents 等仍有旧缓存。手动刷新通过 refreshActivePlugins() 一次性清除所有缓存再重新加载。
通知系统架构
通知系统由三层组成:
- 通知生产者(hooks/notifs/)—— 16 个 Hook 监听各种状态变更,产生通知
- 通知中心(context/notifications.ts)——
addNotification()函数管理通知队列、去重、优先级 - 通知消费者(UI 组件)—— 渲染通知条,处理用户交互
通知有 priority(high/low)和 color(warning/suggestion/error)属性,key 用于去重。高优先级通知会立即显示,低优先级通知在空闲时显示。
setAppState。React 18 的自动批处理(automatic batching)确保同一事件循环内的多个 setState 不会触发多次渲染。但 useManagePlugins 中的异步操作(如插件加载完成后写状态)在 setTimeout 回调中执行,React 18 也会批处理这些更新。
◈ 可视化处理拓扑图
① 命令加载失败 → errors.push({type, source, error})
② Agent 加载失败 → 同上,继续执行
③ Hooks 加载失败 → 同上,继续执行
④ 所有步骤都失败 → 仍返回有效结果(计数为零)
Doctor UI(/doctor 命令)精确展示每个错误来源
⇉ 核心处理流程详解
Claude Code 的 hooks 系统是一个分层架构:底层是 Shell Hook 执行引擎(utils/hooks.ts),负责安全地执行用户定义的 shell 命令;中间层是 React Hooks(hooks/ 目录),负责将底层能力与 UI 状态绑定;顶层是 REPL 集成点,将 hooks 的结果注入到主循环中。整个流程围绕"插件 → 工具 → 键盘 → 通知"四个核心路径展开。
hooks/useManagePlugins.ts:37 组件挂载时执行 initialPluginLoad(L51)。并行调用 loadAllPlugins()(L54)加载所有插件,然后依次:检测并卸载已下架插件(L57)、加载插件命令/Agent/Hooks(L72-109)、预加载 MCP 和 LSP 服务器配置(L120-144)、更新 AppState(L148-180)。错误被收集到 errors 数组,合并到 AppState.plugins.errors(Dedup 逻辑在 L154-167)。hooks/useMergedTools.ts:20 通过 useMemo 组装完整的工具池。先调用 assembleToolPool(L30)合并内置工具和 MCP 工具,应用 deny 规则和去重。再通过 mergeAndFilterTools(L32)将 initialTools(启动时传入)和 assembled 池合并,按权限模式过滤。依赖数组包含 toolPermissionContext,权限变更时自动重计算。hooks/useGlobalKeybindings.tsx:36 注册全局快捷键:Ctrl+T 切换任务/队友视图(L51,三态循环:none → tasks → teammates → none)、Ctrl+O 切换 transcript 模式(L95)、Escape 取消操作。使用 useKeybinding hook 统一注册,通过 useAppState 读写全局状态。Brief 模式有专门的 Escape 处理(L107-114)。hooks/useTextInput.ts:73 处理所有键盘输入编辑:mapKey(L318)将按键映射到编辑操作(光标移动、删除、Kill ring);handleEnter(L247)区分 Enter 提交与 Shift+Enter 换行;历史导航(upOrHistoryUp,L269)在光标位于行首时切换历史记录;粘贴检测通过输入长度和速度判断。hooks/useIDEIntegration.tsx:15 检测并连接 IDE:addIde(L28)在检测到 IDE 时调用 initializeIdeIntegration,设置文件跳转、diff 展示、诊断信息等。使用 useEffect 确保只初始化一次,并传递 ScopedMcpServerConfig 让 IDE 可以使用 MCP 工具。hooks/useNotifyAfterTimeout.ts:38 实现桌面通知:首先重置交互时间戳(L49-51),然后启动轮询定时器(L53-64),每 DEFAULT_INTERACTION_THRESHOLD_MS(6 秒,L9)检查用户是否活跃。当用户空闲超过阈值时,通过 sendNotification 发送桌面通知和终端通知。交互时间由 App.tsx 的 processKeysInBatch 更新(L23-26 注释)。hooks/useDirectConnect.ts:39 管理 WebSocket 会话连接。创建 DirectConnectSessionManager(L66),监听三种事件:onMessage(L67)将 SDK 消息转换为内部消息格式并追加到消息列表、onPermissionRequest(L87)创建合成 AssistantMessage 和 ToolUseConfirm 注入权限队列。tools 通过 ref 保持最新(L53-56),避免 WebSocket 回调中的闭包过期问题。utils/hooks.ts 中实现 Shell Hook 的安全执行:spawn 子进程执行用户定义的命令,通过 subprocessEnv 注入会话环境变量。支持 pre-tool-use、post-tool-use、notification 等生命周期事件。Hook 输出通过 HookJSONOutput(types/hooks.ts)schema 验证,支持同步(阻塞)和异步(后台)两种模式。★ 设计精华
1. 插件加载的防御性错误处理
useManagePlugins(hooks/useManagePlugins.ts)的错误处理策略体现了"局部失败不影响全局"的设计原则。每个加载步骤(命令、Agent、Hooks、MCP、LSP)都有独立的 try-catch(L75-109),错误被收集到统一的 errors 数组而非抛出。即使所有步骤都失败,函数仍返回有效结果(L251-260),只是计数为零。
// hooks/useManagePlugins.ts:72-109
let commands: Command[] = []
let agents: AgentDefinition[] = []
try { commands = await getPluginCommands() }
catch (error) {
errors.push({
type: 'generic-error', source: 'plugin-commands',
error: `Failed to load plugin commands: ${errorMessage}`
})
}
try { agents = await loadPluginAgents() }
catch (error) {
errors.push({
type: 'generic-error', source: 'plugin-agents',
error: `Failed to load plugin agents: ${errorMessage}`
})
}
/doctor 命令)可以精确展示哪个插件的哪个组件出了什么问题。2. 工具池的去重与权限过滤
useMergedTools(hooks/useMergedTools.ts:20)虽然只有 45 行,但它封装了工具系统的核心逻辑。assembleToolPool 负责合并内置工具和 MCP 工具并应用 deny 规则,mergeAndFilterTools 负责将 initialTools 合入并按权限模式过滤。两层分离确保了 REPL 和 Agent(runAgent)共享相同的工具组装逻辑。
// hooks/useMergedTools.ts:20-44
export function useMergedTools(initialTools, mcpTools, ctx): Tools {
return useMemo(() => {
// assembleToolPool: 共享的纯函数,REPL 和 runAgent 都用
const assembled = assembleToolPool(ctx, mcpTools)
// mergeAndFilterTools: 按 permission mode 过滤
return mergeAndFilterTools(initialTools, assembled, ctx.mode)
}, [initialTools, mcpTools, ctx])
}
assembleToolPool 可以在任何上下文中调用,并通过 useMemo 在 React 中获得缓存优化。3. 交互时间戳的集中管理
空闲检测(useNotifyAfterTimeout.ts)和交互时间跟踪采用了集中管理策略:getLastInteractionTime/updateLastInteractionTime(bootstrap/state.js)是全局唯一的交互时间源。更新点在 App.tsx 的 processKeysInBatch 中,而非 useNotifyAfterTimeout 内部的 stdin 监听器。注释(L23-26)解释了原因:额外的 stdin listener 会与主 listener 竞争,导致输入字符丢失。
// hooks/useNotifyAfterTimeout.ts:23-26
// NOTE: User interaction tracking is now done in App.tsx's
// processKeysInBatch function. This avoids having a separate
// stdin 'data' listener that would compete with the main
// 'readable' listener and cause dropped input characters.
// L49-51 — 仅在 hook 挂载时重置时间戳
useEffect(() => {
updateLastInteractionTime(true) // true = 立即重置
}, [])
'data' 事件的多个 listener 可能导致字符被随机分配给不同的 listener。集中到一个 listener 并通过全局状态共享时间戳,彻底消除了这个竞态条件。4. WebSocket 的 ref 闭包修复
useDirectConnect(hooks/useDirectConnect.ts)面临 React Hook 的经典问题:WebSocket 回调中访问的 tools 可能过期。解决方案是使用 ref 桥接(L53-56):toolsRef 在每次渲染后同步最新的 tools,WebSocket 回调始终读取 toolsRef.current。这比重新创建 WebSocket 连接高效得多。
// hooks/useDirectConnect.ts:53-56
const toolsRef = useRef(tools)
useEffect(() => {
toolsRef.current = tools // 每次渲染后同步
}, [tools])
// WebSocket 回调中使用 ref
const tool = findToolByName(toolsRef.current, request.tool_name)
?? createToolStub(request.tool_name)
useEffect 的依赖数组决定了何时重新订阅事件,但 WebSocket 连接不应该因为 tools 变化而断开重连。Ref 提供了一个"逃生舱"——不触发重渲染,不触发重新订阅,只保证回调中读取的值是最新的。5. 错误合并的去重策略
在 useManagePlugins 的 setAppState 调用中(L148-180),新错误与现有错误(LSP 错误等)需要合并。去重逻辑(L154-167)通过构造唯一 key(type:source 或 type:source:error)避免同一错误被重复添加。这防止了插件刷新操作导致错误列表无限增长。
◈ Agent 实践借鉴 — 客服 Agent 事件钩子设计
1. 场景映射:客服 Agent 的钩子需求
客服场景有严格的合规要求。每次工具调用都需要审计、有些需要合规拦截、有些需要事后通知。这些需求恰好映射到 CC 的 Pre/Post Hook 模型:
// 客服场景的钩子需求
// ──────────────────────────────────
// 1. 审计日志(Pre Hook)
// 坐席查了客户身份证号 → 必须记录:
// "坐席张三在 2024-01-15 14:30
// 查询了客户李四的身份证号"
//
// 2. 合规检查(Pre Hook — 可 deny)
// 查询客户手机号 → 必须先记录查询理由
// 没填理由 → deny 操作
//
// 3. 异步通知(Post Hook)
// 退单操作完成 → 通知财务部门(异步不阻塞)
//
// 4. 数据脱敏(Pre Hook — modify)
// 客户手机号 138****5678
// 坐席级别不够 → modify 脱敏后返回
// Claude Code → 客服 Agent
// ────────────────────────────────────────────
Pre tool use hook → 工具调用前的审计 + 合规
Post tool use hook → 工具调用后的通知 + 日志
Hook 返回 allow/deny → 合规检查可拦截操作
Hook 返回 modify → 数据脱敏/权限过滤
防御性 try-catch → 审计日志失败不阻塞退单
Hook 优先级排序 → 合规 hook 先于审计 hook
2. 借鉴 CC + 客服改造:伪代码实现
以下是客服 Hook 系统的核心实现,借鉴 CC 的 pre/post 生命周期、防御性错误处理和 allow/deny/modify 三种决策:
// 客服 Hook 生命周期 — 借鉴 CC 的三阶段执行
async function executeToolWithCSHooks(
toolName: string,
input: any,
toolFn: (input: any) => Promise<any>,
preHooks: CSHook[],
postHooks: CSHook[]
): Promise<ToolResult> {
// 阶段 1: Pre-hooks — 审计 + 合规检查
let currentInput = input
for (const hook of preHooks) {
if (!matchPattern(hook.pattern, toolName)) continue
const result = await safeExecuteHook(hook, toolName, currentInput)
if (result.decision === "deny") {
// 合规检查不通过 → 记录 deny 事件并终止
logger.audit("TOOL_DENIED", { tool: toolName, reason: result.reason })
return { status: "denied", reason: result.reason }
}
if (result.decision === "modify") {
// 数据脱敏 → 替换输入中的敏感字段
currentInput = result.updatedInput
}
}
// 阶段 2: 执行工具
const output = await toolFn(currentInput)
// 阶段 3: Post-hooks — 通知 + 日志(异步,不阻塞)
for (const hook of postHooks) {
safeExecuteHook(hook, toolName, currentInput, output) // fire-and-forget
}
return { status: "success", output }
}
// Hook 返回类型 — 借鉴 CC 的 allow/deny/modify
type CSHookResult =
| { decision: "allow" } // 放行
| { decision: "deny"; reason: string } // 合规拦截
| { decision: "modify"; updatedInput: any } // 数据脱敏
// Hook 1: 合规检查 — deny 无理由的敏感操作
const complianceHook: CSHook = {
name: "compliance-check",
pattern: /query_id_card|query_phone|query_address/,
handler: async (toolName, input) => {
if (!input.queryReason || input.queryReason.trim() === "") {
return {
decision: "deny",
reason: `查询敏感信息(${toolName})必须填写查询理由`
}
}
return { decision: "allow" }
}
}
// Hook 2: 数据脱敏 — modify 手机号和身份证
const maskSensitiveDataHook: CSHook = {
name: "mask-sensitive-data",
pattern: /query_phone|query_id_card/,
handler: async (toolName, input, agentRole) => {
if (agentRole.level < 3) { // 级别不够
return {
decision: "modify",
updatedInput: {
...input,
fieldsToMask: ["phone", "idCard"] // 标记需要脱敏的字段
}
}
}
return { decision: "allow" }
}
}
// Hook 3: 审计日志 — 记录所有敏感操作
const auditLogHook: CSHook = {
name: "audit-log",
pattern: /.*/, // 匹配所有工具
handler: async (toolName, input) => {
await auditLog.write({
operator: input.operatorName,
action: toolName,
customerId: input.customerId,
timestamp: new Date(),
queryReason: input.queryReason
})
return { decision: "allow" }
}
}
// 防御性 Hook 执行器 — 借鉴 CC 的独立 try-catch
async function safeExecuteHook(
hook: CSHook, toolName: string, ...args: any[]
): Promise<CSHookResult> {
try {
return await hook.handler(toolName, ...args)
} catch (error) {
// 审计日志写入失败不能阻塞退单操作
logger.error(`Hook "${hook.name}" failed`, { error: error.message })
return { decision: "allow" } // 失败时放行,不阻塞主流程
}
}
// Hook 注册表 — 按优先级排序
class CSHookRegistry {
private preHooks: RegisteredHook[] = []
register(name: string, pattern: RegExp, handler: HookHandler, priority: number = 100) {
this.preHooks.push({ name, pattern, handler, priority })
this.preHooks.sort((a, b) => a.priority - b.priority) // 数字小先执行
}
}
// 注册 — 优先级保证执行顺序
const registry = new CSHookRegistry()
registry.register("compliance-check", /query_/, complianceHandler, 10) // 最先:合规
registry.register("mask-sensitive-data", /query_/, maskHandler, 20) // 其次:脱敏
registry.register("audit-log", /.*/, auditHandler, 50) // 最后:审计
3. 落地清单:必须抄 vs 不需要抄
- Pre/Post 生命周期 — 工具调用前做审计和合规检查(pre),调用后做通知和日志(post)。CC 的 pre→execute→post 模型完美适配
- 防御性错误处理 — 每个 hook 独立 try-catch,审计日志写入失败不能阻塞退单操作。CC 的 safeExecute 就是这个模式
- Hook 可 deny 操作 — 合规 hook 返回 deny 可以阻止敏感操作。CC 的 allow/deny/modify 三种决策就是为这种场景设计的
- Hook 优先级排序 — 合规 hook(priority=10)必须在审计 hook(priority=50)之前执行,确保不合规的请求不会被记录为"已执行"
- React Hooks — CC 代码中大量 useXxx 是 React 状态管理 hooks,和本节的"事件钩子"完全不是一回事。客服面板用标准 Web 框架的 hooks 就行
- 50+ 自定义 hooks — CC 有 useTextInput、useGlobalKeybindings 等 50+ hooks。客服场景 5-10 个 hook(审计、合规、通知、脱敏、限流)就够
- Hook 可修改工具输入路径 — CC 的 modify 可以改变工具调用的输入参数。客服场景主要用 deny 拦截和 modify 脱敏,不需要复杂参数变换
4. 常见坑
代码索引
| 文件 | 行数 | 说明 |
|---|---|---|
hooks/useManagePlugins.ts | ~305 | 插件管理 Hook:初始化加载、错误收集、刷新通知 |
hooks/useMergedTools.ts | ~45 | 工具合并 Hook:assembleToolPool + mergeAndFilterTools |
hooks/useNotifyAfterTimeout.ts | ~66 | 空闲通知 Hook:idle 检测 + 桌面/终端通知 |
hooks/useGlobalKeybindings.tsx | ~大型 | 全局键盘绑定:Escape/Ctrl+C/自定义快捷键 |
hooks/useTextInput.ts | ~大型 | 文本输入处理:stdin 读取、多行编辑、粘贴 |
hooks/useIDEIntegration.tsx | ~大型 | IDE 集成:文件跳转、diff 展示、诊断 |
hooks/useDirectConnect.ts | ~中型 | Direct Connect:WebSocket 会话连接管理 |
hooks/useSSHSession.ts | ~中型 | SSH 会话:远程连接状态管理 |
hooks/useRemoteSession.ts | ~中型 | 远程会话:Teleport 协议 |
hooks/useQueueProcessor.ts | ~中型 | 命令队列处理:异步命令调度 |
hooks/useTerminalSize.ts | ~小型 | 终端尺寸:resize 事件订阅 |
hooks/useElapsedTime.ts | ~小型 | 计时器:会话/请求耗时显示 |
hooks/useAfterFirstRender.ts | ~小型 | 首帧回调:跳过首次渲染的副作用 |
hooks/useTimeout.ts | ~小型 | 定时器 Hook:可取消的 setTimeout |
hooks/useDoublePress.ts | ~小型 | 双击检测:Esc 双击退出确认 |
hooks/toolPermission/interactiveHandler.ts | ~大型 | 交互式权限决策:用户确认对话框 |
hooks/toolPermission/coordinatorHandler.ts | ~中型 | 协调器权限决策:Swarm 模式 |
hooks/toolPermission/swarmWorkerHandler.ts | ~中型 | Worker 权限决策:Agent 模式 |
hooks/toolPermission/PermissionContext.ts | ~中型 | 权限上下文:决策链初始化 |
hooks/notifs/useMcpConnectivityStatus.tsx | ~中型 | MCP 连接状态通知 |
hooks/notifs/usePluginAutoupdateNotification.ts | ~中型 | 插件自动更新通知 |
hooks/notifs/useRateLimitWarningNotification.tsx | ~中型 | 速率限制警告通知 |
hooks/notifs/useStartupNotification.ts | ~小型 | 启动消息通知 |
hooks/notifs/useSettingsErrors.tsx | ~小型 | 设置错误通知 |