---
name: Hook
order: 8
---

## AI Hook

Hook 允许在对话关键节点注入自定义逻辑。对用户可配置部分，支持三级来源：

1. **用户级**：`~/.jdata/agent/hooks/<hook_name>/HOOK.yaml`（或 `HOOK.yml`）— 全局生效
2. **项目级**：`.jcli/hooks/<hook_name>/HOOK.yaml`（或 `HOOK.yml`）— 项目目录下生效
3. **Session 级**：通过 `register_hook` 工具由 AI 动态注册 — 仅当前会话

> 运行时实际还存在**内置 hook**，执行顺序是：内置 -> 用户级 -> 项目级 -> Session 级。
> 同一事件按链式执行，前一个 hook 的输出会成为后一个 hook 的输入。

### 事件生命周期总览

下面按一条消息从「用户输入」到「AI 回复完成」的完整流程，标注所有 Hook 事件的触发位置：

```
[会话启动] ──→ session_start
[会话退出] ──→ session_end

一轮对话的完整流程（循环）：

  用户输入
    │
    ▼
  ┌─────────────────────┐
  │  pre_send_message   │  ← 可修改/拦截用户消息
  └────────┬────────────┘
           ▼
  ┌─────────────────────┐
  │  post_send_message  │  ← 仅通知，不可修改
  └────────┬────────────┘
           ▼
  ┌─────────────────────┐
  │  pre_llm_request    │  ← 可修改 system_prompt / messages
  └────────┬────────────┘
           ▼
       LLM 请求
           │
           ▼
  ┌─────────────────────┐
  │  post_llm_response  │  ← 可修改 AI 回复内容
  └────────┬────────────┘
           │
           ├── AI 回复中包含工具调用？
           │       │
           │       ▼
           │   ┌──────────────────────────┐
           │   │  pre_tool_execution      │  ← 可修改/跳过工具参数
           │   └────────────┬─────────────┘
           │                ▼
           │           执行工具
           │                │
           │        ┌───────┴────────┐
           │        ▼                ▼
           │   成功:                失败:
           │   post_tool_execution   post_tool_execution_failure
           │   (可修改结果)          (可修改错误信息)
           │        │                │
           │        └───────┬────────┘
           │                ▼
           │       工具结果返回 LLM → 回到 pre_llm_request
           │       (LLM 基于工具结果继续生成)
           │
           ├── AI 回复中不再有工具调用？
           │       │
           │       ▼
           │   ┌─────────────────────┐
           │   │  stop               │  ← LLM 即将结束回复
           │   └────────┬────────────┘
           │            ▼
           │       回到顶部，等待下一轮用户输入
           │
           └── 上下文接近上限时触发压缩：
                   │
                   ├── micro_compact（轮次级压缩）
                   │       │
                   │   pre_micro_compact → 执行压缩 → post_micro_compact
                   │
                   └── auto_compact（全量压缩）
                           │
                       pre_auto_compact → 执行压缩 → post_auto_compact
```

### 事件详解

**一、会话级事件**（每个会话触发一次）

| 事件 | 触发时机 | 可读字段 | 可写字段 |
|------|----------|----------|----------|
| `session_start` | 会话启动时 | `messages` | 仅通知，返回值被忽略 |
| `session_end` | 会话退出时 | `messages` | 仅通知，返回值被忽略 |

**二、消息发送阶段**

| 事件 | 触发时机 | 可读字段 | 可写字段 |
|------|----------|----------|----------|
| `pre_send_message` | 用户发送消息前 | `user_input`, `messages` | `user_input`, `action=stop`, `retry_feedback` |
| `post_send_message` | 用户发送消息后 | `user_input`, `messages` | 仅通知，返回值被忽略 |

**三、LLM 请求/回复阶段**

| 事件 | 触发时机 | 可读字段 | 可写字段 |
|------|----------|----------|----------|
| `pre_llm_request` | LLM API 请求前 | `messages`, `system_prompt`, `model` | `messages`, `system_prompt`, `inject_messages`, `additional_context`, `action=stop`, `retry_feedback` |
| `post_llm_response` | LLM 回复完成后 | `assistant_output`, `messages`, `model` | `assistant_output`, `action=stop`, `retry_feedback`, `system_message` |

**四、工具执行阶段**

| 事件 | 触发时机 | 可读字段 | 可写字段 |
|------|----------|----------|----------|
| `pre_tool_execution` | 工具执行前 | `tool_name`, `tool_arguments` | `tool_arguments`, `action=skip` |
| `post_tool_execution` | 工具执行成功后 | `tool_name`, `tool_result` | `tool_result` |
| `post_tool_execution_failure` | 工具执行失败后 | `tool_name`, `tool_error` | `tool_error`, `additional_context` |

**五、回复结束阶段**

| 事件 | 触发时机 | 可读字段 | 可写字段 |
|------|----------|----------|----------|
| `stop` | LLM 即将结束回复时（无更多工具调用） | `user_input`, `messages`, `system_prompt`, `model` | `retry_feedback`, `additional_context`, `action=stop` |

**六、上下文压缩阶段**

| 事件 | 触发时机 | 可读字段 | 可写字段 |
|------|----------|----------|----------|
| `pre_micro_compact` | 轮次级压缩前 | `messages`, `model` | `action=stop` |
| `post_micro_compact` | 轮次级压缩后 | `messages` | `messages` |
| `pre_auto_compact` | 全量压缩前 | `messages`, `system_prompt`, `model` | `additional_context`, `action=stop` |
| `post_auto_compact` | 全量压缩后 | `messages` | `messages` |

### Hook 类型

#### bash（默认）
通过 `sh -c` 子进程执行 Shell 命令。
- 参数：`command`（必填）、`timeout`（默认 10s）、`on_error`、`retry`（默认 0）

#### llm
通过 prompt 模板调用 LLM，LLM 返回 HookResult JSON。
- 参数：`prompt`（必填，支持 `{{variable}}` 模板变量）、`model`（可选，覆盖当前模型）、`timeout`（默认 30s）、`retry`（默认 1）、`on_error`
- LLM 输出必须为合法 HookResult JSON，系统会自动提取 JSON 并解析
- 解析失败或网络错误 → Err → 按 retry 重试 → 重试耗尽按 on_error 处理
- 可用模板变量：`{{event}}`、`{{user_input}}`、`{{assistant_output}}`、`{{tool_name}}`、`{{tool_arguments}}`、`{{tool_result}}`、`{{model}}`、`{{cwd}}`

### Bash Hook 脚本协议

- 执行方式：`sh -c "<command>"`
- 工作目录：用户当前目录
- PATH：目录布局下，hook 目录前置到 PATH，脚本可直接用文件名调用（如 `script.sh`）
- 环境变量：`JCLI_HOOK_EVENT`（事件名）、`JCLI_CWD`（用户当前目录）、`JCLI_HOOK_DIR`（hook 目录，目录布局下有值）
- stdin：HookContext JSON
- stdout：HookResult JSON（只返回要修改的字段，空/`{}` 表示无修改）
- exit 0 = 成功，非零 = 失败（按 on_error 策略处理：skip=记录日志继续，stop=中止整条链）
- on_error 默认 "skip"：脚本失败时不中断操作，仅记录错误日志
- retry 默认 0：失败后不重试；设置 >0 则重试指定次数（受链总超时 30s 约束）

### LLM Hook 协议

- 系统自动在 prompt 末尾追加 JSON 格式指令，LLM 需返回 HookResult JSON
- 使用当前活跃 provider 的 API（或通过 model 参数覆盖模型名）
- JSON 提取逻辑：从 LLM 输出中找第一个 `{` 到最后一个 `}` 之间的内容
- 解析失败 → 视为 Err → 按 retry 重试
- retry 默认 1：LLM 返回非法 JSON 或网络失败时重试

### HookResult JSON 结构

```json
{
  "user_input": "修改后的用户消息",
  "assistant_output": "修改后的 AI 回复",
  "messages": [{"role":"user","content":"..."}],
  "system_prompt": "修改后的提示词",
  "tool_arguments": "修改后的工具参数",
  "tool_result": "修改后的工具结果",
  "tool_error": "修改后的错误信息",
  "inject_messages": [{"role":"user","content":"注入消息"}],
  "action": "stop",
  "retry_feedback": "审查反馈：请修正XX问题",
  "additional_context": "追加到 system_prompt 的额外上下文",
  "system_message": "展示给用户的提示消息"
}
```

### 关键字段说明

- `action`：控制流动作，字符串 `"stop"` 或 `"skip"`
  - `"stop"`：中止当前步骤及其所属子管线
  - `"skip"`：跳过当前步骤，同级步骤继续（仅 `pre_tool_execution` 中使用）
- `retry_feedback`：与 stop 配合使用。在 stop/pre_send_message/post_llm_response 中，stop+retry_feedback 会中止当前操作并将反馈注入为新消息，LLM 带反馈重新生成。这是实现"宪法 AI/纠查官"的核心机制。
- `additional_context`：追加文本到 system_prompt 末尾，不占消息位。适用于注入规则、约束等。
- `system_message`：在 UI 上以 toast/提示形式展示给用户，不影响 LLM 输入。

### action 语义

- `pre_send_message` / `pre_llm_request` / `stop` / `post_llm_response`：`action=stop` 中止当前操作
- `pre_tool_execution`：`action=skip` 跳过该工具调用（其他工具继续执行）
- `pre_micro_compact`：`action=stop` 中止整个 compact 子管线
- `pre_auto_compact`：`action=stop` 中止 auto_compact

### 压缩 Hook 说明

两层压缩各有独立的 Pre/Post hook，构成一个 compact 子管线：
1. `pre_micro_compact` → micro_compact → `post_micro_compact`
2. `pre_auto_compact` → auto_compact → `post_auto_compact`

### 配置示例

Hook 采用目录布局，每个 hook 是一个目录：

```
~/.jdata/agent/hooks/<hook_name>/
├── HOOK.yaml      # hook 定义（也支持 HOOK.yml；两者共存时取 HOOK.yaml）
└── script.sh      # 可选脚本，command 里直接用文件名引用（hook 目录在 PATH 中）
```

**HOOK.yaml 示例 (bash)**：

```yaml
# ~/.jdata/agent/hooks/inject_time/HOOK.yaml
events: [pre_send_message]
type: bash
command: inject_time.sh
timeout: 5
on_error: skip
```

**HOOK.yaml 示例 (llm)**：

```yaml
# ~/.jdata/agent/hooks/safety_check/HOOK.yaml
events: [pre_tool_execution]
type: llm
prompt: |
  审查工具调用是否安全：工具={{tool_name}} 参数={{tool_arguments}}
  如果不安全，返回 {"action":"skip"}，否则返回 {}
filter:
  tool_matcher: "Bash|Shell"
```

**多事件绑定**：

```yaml
# 同一个 hook 绑定多个事件
events: [pre_send_message, post_send_message]
type: bash
command: log.sh
```

### 更多示例

#### 示例 1：LLM 纠查官（推荐，type=llm）

```yaml
# ~/.jdata/agent/hooks/censor/HOOK.yaml
events: [post_llm_response]
type: llm
prompt: |
  检查以下 AI 回复是否包含敏感信息（密码、密钥、token）：
  {{assistant_output}}
  如果包含敏感信息，返回 action=stop + retry_feedback 说明问题。
  如果没有问题，返回空 JSON {}。
timeout: 30
retry: 1
on_error: skip
```

#### 示例 2：LLM 消息审查（pre_send_message）

```yaml
# ~/.jdata/agent/hooks/msg_review/HOOK.yaml
events: [pre_send_message]
type: llm
prompt: |
  审查用户消息是否合规：{{user_input}}
  如有违规返回 action=stop 和 retry_feedback。
model: gpt-4o-mini
timeout: 15
retry: 1
```

#### 示例 3：Bash 脚本 - 给消息加时间戳（pre_send_message）

```bash
#!/bin/bash
# ~/.jdata/agent/hooks/inject_time/inject_time.sh
input=$(cat)
msg=$(echo "$input" | python3 -c "import sys,json; print(json.load(sys.stdin).get('user_input',''))")
echo "{\"user_input\": \"[$(date '+%H:%M')] $msg\"}"
```

```yaml
# ~/.jdata/agent/hooks/inject_time/HOOK.yaml
events: [pre_send_message]
type: bash
command: inject_time.sh
```

#### 示例 4：Bash 脚本 - 跳过危险命令（pre_tool_execution）

```bash
#!/bin/bash
# ~/.jdata/agent/hooks/rm_guard/guard.sh
input=$(cat)
tool=$(echo "$input" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))")
args=$(echo "$input" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_arguments',''))")
if [ "$tool" = "Bash" ] && echo "$args" | grep -q "rm -rf"; then
  echo '{"action": "skip"}'
else
  echo '{}'
fi
```

```yaml
# ~/.jdata/agent/hooks/rm_guard/HOOK.yaml
events: [pre_tool_execution]
command: guard.sh
```

#### 示例 5：带过滤器的工具审查

```yaml
# ~/.jdata/agent/hooks/tool_review/HOOK.yaml
events: [pre_tool_execution]
type: llm
prompt: |
  审查工具调用是否安全：工具={{tool_name}}, 参数={{tool_arguments}}
  如果不安全，返回 action=skip。
filter:
  tool_matcher: "Bash|Shell"
timeout: 15
retry: 1
```

### HookContext 字段（stdin JSON）

| 字段 | 类型 | 说明 |
|------|------|------|
| `event` | string | 当前触发的事件类型（如 `"pre_send_message"`） |
| `messages` | array | 当前对话消息列表（部分事件可读） |
| `system_prompt` | string | 当前系统提示词 |
| `model` | string | 当前使用的模型名称 |
| `user_input` | string | 本轮用户输入文本 |
| `assistant_output` | string | 本轮 AI 回复文本 |
| `tool_name` | string | 当前工具调用的工具名 |
| `tool_arguments` | string | 当前工具调用的参数 JSON |
| `tool_result` | string | 工具执行结果 |
| `tool_error` | string | 工具执行失败原因 |
| `session_id` | string | 当前会话 ID |
| `cwd` | string | 用户当前工作目录（JCLI_CWD 环境变量，与 hook 执行目录 JCLI_HOOK_DIR 可能不同） |

> 各字段按事件类型有选择性地填充，未填充的字段序列化时省略

### HookResult 字段（stdout JSON）

| 字段 | 生效事件 | 说明 |
|------|----------|------|
| `user_input` | PreSendMessage | 替换用户即将发送的消息 |
| `assistant_output` | PostLlmResponse | 替换 AI 最终展示的回复 |
| `messages` | PreLlmRequest, PostMicroCompact, PostAutoCompact | 替换消息列表 |
| `system_prompt` | PreLlmRequest | 替换系统提示词 |
| `tool_arguments` | PreToolExecution | 替换工具调用参数 |
| `tool_result` | PostToolExecution | 替换工具返回结果 |
| `tool_error` | PostToolExecutionFailure | 替换工具错误信息 |
| `inject_messages` | PreLlmRequest | 追加消息到消息列表末尾 |
| `retry_feedback` | Pre*/Stop/PostLlmResponse | 中止并带反馈重试（注入为 user message 重新请求 LLM） |
| `additional_context` | PreLlmRequest, Stop, PreAutoCompact | 纯文本追加到 system_prompt 末尾 |
| `system_message` | 所有事件 | 展示给用户的提示消息（toast） |
| `action` | 大部分事件 | `"stop"` 中止当前步骤及其子管线；`"skip"` 跳过当前步骤（同级继续） |

### HookFilter 条件过滤

所有字段可选，未设置不参与过滤；多字段同时设置取 AND 关系：

| 字段 | 说明 |
|------|------|
| `tool_name` | 工具名精确匹配（仅工具相关事件） |
| `tool_matcher` | 工具名模式匹配，管道分隔（如 `"Write\|Edit\|Bash"`），优先级低于 `tool_name` |
| `model_prefix` | 模型名前缀过滤（如 `"gpt-4"` 匹配 `"gpt-4o"`） |

### 注意事项

- LLM hook 使用当前活跃的 provider API（可通过 model 参数覆盖模型名）
- bash hook 必须从 stdin 读取（至少 `cat > /dev/null`），否则可能 SIGPIPE
- retry 只对 Err 路径生效（超时、非零退出、LLM JSON 解析失败、网络失败）
- 重试受链总超时（30s）约束
- 只有 session 级 hook 可通过 RegisterHook 工具管理；用户级/项目级需手动编辑 YAML 配置文件
- 移除 hook 时，使用 list 输出中的 session_idx 作为 index 参数

### Hook 执行指标

每个 hook 自动记录执行次数、成功次数、失败次数、跳过次数、累计耗时，可在配置界面 Hooks Tab 中查看
