> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trae.cn/llms.txt
> Use this file to discover all available pages before exploring further.

本文档介绍企业 Hook 的配置方式、执行环境、输入输出规范，以及各类事件的触发与处理机制。

## Hook 配置 {#hpYc1MTXR}

### Hook 配置格式 {#heInRRDLu}

每个 Hook 事件配置为一个 JSON 数组。数组中的每个对象表示一个 Hook 组，`matcher` 用于匹配工具名称或通知类型；`SessionStart`、`UserPromptSubmit`、`Stop` 三类非工具匹配事件无需配置 `matcher`。

```JSON
[
  {
    "matcher": "{ToolPattern}",
    "loop_limit": 5,
    "hooks": [
      {
        "type": "http",
        "url": "{URL to send the POST request to}",
        "timeout": 30
      }
    ]
  }
]
```

### 字段说明 {#hkYlOV7N4}

Hook 相关字段的说明如下：

* **Hook 组层**
   <!-- @cols-width: 149,122,100,437 -->
   |**字段** |**类型** |**是否必填** |**描述** |
   |---|---|---|---|
   |`matcher` |`string` |否 |匹配规则，支持正则表达式（如 `Edit\|Write`、`mcp.*`）。配置为 `*`、空字符串或省略时，表示匹配所有工具或通知类型。 |\
   | | | | |\
   | | | |***提示***：`matcher` 字段仅对 `PreToolUse`、`PostToolUse` 和 `Notification` 事件有效。 |
   |`loop_limit` |`number` |否 |`Stop` 事件的循环次数限制。 |\
   | | | | |\
   | | | |当 `loop_count` ≥ `loop_limit` 时，该 Hook 组将被跳过。 |\
   | | | | |\
   | | | |该字段仅支持正整数；未配置或配置的值小于等于 `0` 时，使用默认值 `5`。 |\
   | | | | |\
   | | | |***提示***：`loop_limit` 字段仅对 `Stop` 事件有效。 |
   |`hooks` |`array` |是 |该 Hook 组下要执行的 Hook 列表。 |
* **Hook 定义层**
   <!-- @cols-width: 147,122,100,444 -->
   |**字段** |**类型** |**是否必填** |**描述** |
   |---|---|---|---|
   |`type` |`string` |否 |Hook 类型，企业 Hook 当前仅支持 HTTP 类型，取值为 `http`。 |
   |`url` |`string` |是 |接收 Hook 请求的企业服务端 URL。TRAE 会向该 URL 发送 HTTP POST 请求。 |
   |`timeout` |`number` |否 |请求超时时间，单位为秒，默认值为 `30`。 |   


## HTTP **调用约定** {#hrqHYdmOu}

企业 Hook 通过 HTTP 请求调用企业自有服务。TRAE 会向配置的 `url` 发送 `POST` 请求，并通过 HTTP 状态码和响应体判断后续执行行为。

* **请求方法**：`POST`
* **请求头**：`Content-Type: application/json`
* **请求体**：当前 Hook 事件的 JSON payload。通用字段见 “请求体通用字段” 部分，事件专有字段见 “Hook 事件” 部分。
* **超时策略**：超过 `timeout` 配置的时长仍未收到响应时，按执行失败处理。
* **状态码**：
   <!-- @cols-width: 127,101,663 -->
   |**HTTP 状态码** |**响应体** |**行为** |
   |---|---|---|
   |`2xx` |空 |继续执行，无附加行为。 |
   |`2xx` |纯文本 |在 `SessionStart` 和 `UserPromptSubmit` 事件中，将文本作为上下文附加给模型。若为其他事件，则继续执行。 |
   |`2xx` |JSON |继续执行，并按 JSON 内容执行流程控制；若返回 `continue: false`，则让智能体停止执行。 |
   |非 `2xx` |任意 |阻断当前问答。 |
   |超时 / 连接失败 |— |阻断当前问答。 |
   :::tip 提示
   企业 Hook 依赖企业服务端的可用性。建议为 Hook 服务配置超时、监控和降级策略，避免因服务异常影响用户问答流程。
   :::   


## Hook 输入和输出 {#hnKa5QHcx}

企业 Hook 的输入输出由 HTTP 请求体和响应体承载：请求体用于传递当前事件上下文，响应体用于返回流程控制结果或附加上下文。

### **请求体通用字段** {#hg8obZh6J}

每个 Hook 事件的请求体 JSON 均包含以下通用字段：

```JSON
{
  "session_id": "session_id",
  "cwd": "/path/to/workspace",
  "hook_event_name": "PreToolUse",
  "workspace_roots": ["/path/to/workspace"],
  "agent_id": "agent_id",
  "agent_type": "agent_type",
  "email": "user@example.com",
  "model": "model"
}
```

<!-- @cols-width: 274,238,401 -->
|**字段** |**类型** |**描述** |
|---|---|---|
|`session_id` |string |当前会话 ID。 |
|`cwd` |string |当前会话所在的工作区目录。 |
|`hook_event_name` |string |当前 Hook 事件的名称。 |
|`workspace_roots` |string[] |如果存在多个工作区，此字段将包含所有工作区的根目录。 |
|`agent_id` |string |当前执行 Hook 的智能体 ID。 |
|`agent_type` |string |当前执行 Hook 的智能体类型。 |
|`email` |string |触发本次 Hook 的成员邮箱。 |
|`model` |string |当前会话使用的模型标识。 |

### **响应体通用字段** {#hC4VRds8c}

端点可在响应体中返回两种格式的数据：

* **JSON** ：用于结构化地控制智能体的执行流程。
* **纯文本**：输出内容将作为附加上下文提供给模型。此格式仅适用于 `SessionStart` 和 `UserPromptSubmit` 事件。

对于 JSON 格式的输出，所有事件均支持以下通用流程控制字段：

```JSON
{
  "continue": true,
  "stopReason": "string"
}
```

<!-- @cols-width: 181,159,575 -->
|**字段** |**类型** |**描述** |
|---|---|---|
|continue |boolean |智能体是否在 Hook 执行完毕后继续执行。默认值为 `true`。若设置为 `false`，智能体将停止执行。该字段优先于任何事件特定的 `decision` 字段。 |
|stopReason |string |当 `continue` 为 `false` 时，展示给用户的智能体停止执行的原因。 |

:::tip 提示
`Notification` 是非阻断事件，不使用响应体进行流程控制。
:::

## Hook 事件 {#hdWvxt1k1}

### SessionStart {#hc6qUfnp5}

* **触发时机**：创建 Session 后、发起第一个对话之前触发。
* **Hook 的作用**：在会话开始时向模型注入上下文信息。
* **请求体**：
   ```JSON
   {
     "session_id": "...",
     "hook_event_name": "SessionStart",
     "source": "startup"
   }
   ```
   该事件的专有字段如下：
   <!-- @cols-width: 172,135,388 -->
   |**字段** |**类型** |**描述** |
   |---|---|---|
   |`source` |`string` |会话的来源。目前仅支持 `startup`（新建会话）。 |
* **响应体**：
   * **格式一：纯文本**
      直接返回纯文本内容，将其作为附加上下文提供给模型。
   * **格式二：JSON**
      ```JSON
      {
        "hookSpecificOutput": {
          "hookEventName": "SessionStart",
          "additionalContext": "文本内容"
        }
      }
      ```
      <!-- @cols-width: 190,143,350 -->
      |**字段** |**类型** |**描述** |
      |---|---|---|
      |`additionalContext` |`string` |附加给模型的上下文。 |
* **失败行为**：如果 Hook 服务返回非 `2xx` 状态码、请求超时或连接失败，按 HTTP 调用约定处理。

### UserPromptSubmit {#hbQz4wglt}


* **触发时机**：用户发送消息后、智能体开始处理前。
* **Hook 的作用**：拦截不允许的请求，或向模型附加上下文。
* **请求体**：
   ```JSON
   {
     "session_id": "...",
     "hook_event_name": "UserPromptSubmit",
     "prompt": "用户输入的 Prompt"
   }
   ```
   该事件的专有字段如下：
   <!-- @cols-width: 265,242,244 -->
   |**字段** |**类型** |**描述** |
   |---|---|---|
   |`prompt` |`string` |用户提交的 Prompt 文本。 |
* **响应体**：
   * **格式一：纯文本**
      直接返回非 JSON 格式的纯文本内容，将其作为附加上下文提供给模型。
   * **格式二：JSON**
      ```JSON
      {
        "decision": "block",
        "reason": "该请求不被允许的原因",
        "hookSpecificOutput": {
          "hookEventName": "UserPromptSubmit",
          "additionalContext": "附加给模型的上下文"
        }
      }
      ```
      <!-- @cols-width: 175,173,515 -->
      |**字段** |**类型** |**描述** |
      |---|---|---|
      |`decision` |`string` |该字段仅支持设为 `block`。设置后，将禁止智能体执行该 Prompt。如需允许智能体执行该 Prompt，请将该字段留空。 |
      |`reason` |`string` |当 `decision` 字段的值为 `block` 时，此字段的内容将作为错误信息展示给用户。否则，该字段将被忽略。 |
      |`additionalContext` |`string` |附加给模型的上下文文本。 |
* **失败行为**：如果 Hook 服务返回非 `2xx` 状态码、请求超时或连接失败，按 HTTP 调用约定处理。

### PreToolUse {#hLajk7S6u}


* **触发时机**：智能体发起工具调用后、实际执行前。
* **Hook 的作用**：校验或拦截工具调用、修改工具参数，或要求用户确认后再执行。
* **`matcher` 字段配置**：可通过 `matcher` 字段配置正则表达式，从而匹配特定的工具名。
* **请求体**：
   ```JSON
   {
     "session_id": "...",
     "hook_event_name": "PreToolUse",
     "tool_use_id": "toolcall-id-string",
     "tool_name": "RunCommand",
     "llm_tool_name": "RunCommand",
     "tool_input": { ... }
   }
   ```
   该事件的专有字段如下：
   <!-- @cols-width: 244,150,498 -->
   |**字段** |**类型** |**描述** |
   |---|---|---|
   |`tool_use_id` |`string` |工具调用的唯一 ID。 |
   |`tool_name` |`string` |标准化的工具名称。详见 “PreToolUse 和 PostToolUse 事件支持的工具” 部分。 |
   |`llm_tool_name` |`string` |传递给大语言模型的原始工具名称。 |
   |`tool_input` |`object` |工具输入参数。 |
* **响应体**：
   ```JSON
   {
     "hookSpecificOutput": {
       "hookEventName": "PreToolUse",
       "permissionDecision": "allow", 
       "permissionDecisionReason": "决策原因说明",
       "updatedInput": { ... },
       "additionalContext": "附加给模型的上下文"
     }
   }
   ```
   <!-- @cols-width: 225,100,564 -->
   |**字段** |**类型** |**说明** |
   |---|---|---|
   |`permissionDecision` |`string` |权限决策，用于决定是否执行本次工具调用。可选值包括： |\
   | | | |\
   | | |* `allow`：允许执行。 |\
   | | |* `deny`：拒绝执行。 |\
   | | |* `ask`：弹出确认框，由用户决定是否执行。 |\
   | | | |\
   | | |***特殊情况说明***： |\
   | | | |\
   | | |* 如果多个 `PreToolUse` 事件的 Hook 正并行执行，`permissionDecision` 只会返回一个最终值。取值优先级为： `deny` -> `ask` -> `allow`。 |\
   | | |* 如果返回值为 `allow`，但是该工具的运行模式为手动确认，则仍以工具运行模式为准，需要用户确认。 |
   |`permissionDecisionReason` |`string` |权限决策的原因。 |
   |`updatedInput` |`object` |修改后的工具输入参数，将整体覆盖替换原始参数（非合并更新）。 |
   |`additionalContext` |`string` |附加给模型的上下文文本。 |
* **失败行为**：如果 Hook 服务返回非 `2xx` 状态码、请求超时或连接失败，按 HTTP 调用约定处理。

### PostToolUse {#hi9FVzBqc}


* **触发时机**：工具调用实际执行完成后。
* **Hook 的作用**：校验执行结果或附加上下文。
* **`matcher` 字段配置**：可通过 `matcher` 字段配置正则表达式，从而匹配特定的工具名。
* **请求体**：
   ```JSON
   {
     "session_id": "...",
     "hook_event_name": "PostToolUse",
     "tool_use_id": "toolcall-id-string",
     "tool_name": "RunCommand",
     "llm_tool_name": "RunCommand",
     "tool_input": { ... },
     "tool_response": { ... }
   }
   ```
   该事件的专有字段如下：
   <!-- @cols-width: 244,132,518 -->
   |**字段** |**类型** |**描述** |
   |---|---|---|
   |`tool_use_id` |`string` |工具调用的唯一 ID。 |
   |`tool_name` |`string` |标准化的工具名称。详见 “PreToolUse 和 PostToolUse 事件支持的工具” 部分。 |
   |`llm_tool_name` |`string` |传递给大语言模型的原始工具名称。 |
   |`tool_input` |`object` |工具输入参数。 |
   |`tool_response` |`object` |工具调用的结果。 |
* **响应体**：
   ```JSON
   {
     "decision": "block",
     "reason": "阻断原因",
     "hookSpecificOutput": {
       "hookEventName": "PostToolUse",
       "additionalContext": "附加给模型的上下文"
     }
   }
   ```
   <!-- @cols-width: 186,123,583 -->
   |**字段** |**类型** |**描述** |
   |---|---|---|
   |`decision` |`string` |该字段仅支持设为 `block`。设置后，会向模型传递一条阻断信息，表示工具已执行且无法撤销。如需允许智能体继续处理工具调用的结果，将该字段留空。 |
   |`reason` |`string` |当 `decision` 字段的值为 `block` 时，此字段的内容将作为阻断原因展示给用户。否则，该字段将被忽略。 |
   |`additionalContext` |`string` |附加给模型的上下文文本。 |
* **失败行为**：如果 Hook 服务返回非 `2xx` 状态码、请求超时或连接失败，按 HTTP 调用约定处理。

### Stop {#hwn0daaUW}


* **触发时机**：智能体完成输出、准备结束当前查询时。此时，你可以检查智能体的输出是否达标；若不达标，可以阻止智能体结束任务并要求其继续处理。
* **Hook 的作用**：阻止智能体结束当前任务，并要求其继续执行。
* **请求体**：
   ```JSON
   {
     "session_id": "...",
     "hook_event_name": "Stop",
     "stop_hook_active": false,
     "loop_count": 0, 
     "last_assistant_message": "大语言模型最终输出的文本内容"
   }
   ```
   该事件的专有字段如下：
   <!-- @cols-width: 191,146,553 -->
   |**字段** |**类型** |**描述** |
   |---|---|---|
   |`stop_hook_active` |`boolean` |当前查询是否已经被 `Stop` 事件的 Hook 至少阻断过一次。 |
   |`loop_count` |`number` |当前查询的 `Stop` 事件被 Hook 阻断的次数计数。从 `0` 开始累加。 |\
   | | | |\
   | | |***循环限制***：你可以通过 `loop_limit` 字段配置该 Hook 组允许阻断 `Stop` 事件的最大次数。当 `loop_count` ≥ `loop_limit` 时，该 Hook 组将被跳过，使智能体不再执行，以避免无限循环。`loop_limit` 的默认值为 `5`。 |
   |`last_assistant_message` |`string` |大语言模型最终输出的文本内容。 |
* **响应体**：
   ```JSON
   {
     "decision": "block",
     "reason": "请继续检查测试是否通过"
   }
   ```
   <!-- @cols-width: 133,123,637 -->
   |**字段** |**类型** |**描述** |
   |---|---|---|
   |`decision` |`string` |该字段仅支持设为 `block`。设置后，将阻断智能体停止执行。如需让智能体停止执行，将该字段留空。 |
   |`reason` |`string` |当 `decision` 字段的值为 `block` 时，此字段的内容将作为新的用户请求让智能体继续执行。否则，该字段将被忽略。 |
* **失败行为**：如果 Hook 服务返回非 `2xx` 状态码、请求超时或连接失败，按 HTTP 调用约定处理。
* **决策控制流程：**
   `Stop` 事件的决策控制逻辑如下：
   ```Plain Text
   智能体准备停止
       │
       ▼
   检查 loop_count 是否大于等于 loop_limit？──── 是 ──► 跳过 Hook，允许智能体停止
       │
       否
       │
       ▼
   调用 Stop 事件的企业 Hook 服务
       │
       ├── 返回 2xx 状态码，且 decision 字段为空 ───────► 允许停止
       │
       ├── 返回 2xx 状态码，且 decision 字段的值为 block ──► 阻断停止，将 reason 字段作为新 Query
       │
       └── 非 2xx 状态码 / 超时 / 连接失败 ───────────► 按 HTTP 调用约定处理
   ```   


### Notification {#hC6Vnzhd3}


* **触发时机**：智能体的工具调用等待用户确认时，或智能体完成任务时。该事件异步执行，不会阻塞智能体的主流程。
* **Hook 的作用**：发送通知，不改变智能体的执行流程。
* **`matcher` 字段配置**：基于通知类型（`notification_type`）匹配，而不是基于工具名匹配。未配置 `matcher`，或将其配置为空字符串或 `*` 时，表示匹配所有通知类型。
* **请求体**：
   ```JSON
   {
     "session_id": "...",
     "hook_event_name": "Notification",
     "notification_type": "idle_prompt",
     "message": "智能体已完成任务",
     "tool_use_id": "toolu_xxx"
   }
   ```
   该事件的专有字段如下：
   <!-- @cols-width: 161,110,621 -->
   |**字段** |**类型** |**描述** |
   |---|---|---|
   |`notification_type` |`string` |通知类别，用于标识通知场景，也用于匹配 `matcher` 字段的配置。可选值见下表。 |
   |`message` |`string` |通知的正文。 |
   |`tool_use_id` |`string?` |关联的工具调用 ID。 |\
   | | | |\
   | | |仅工具调用相关的通知类型携带该 ID，例如 `permission_prompt`、`document_review` 等。任务完成时发送的 `idle_prompt` 类通知不携带该 ID。 |
   `notification_type` 字段的值和相应触发时机如下：
   <!-- @cols-width: 202,689 -->
   |**值** |**触发时机** |
   |---|---|
   |`idle_prompt` |智能体完成当前任务。 |
   |`permission_prompt` |工具调用需要用户确认后才能继续执行，例如当 `PreToolUse` 事件的 Hook 返回 `ask` 决策，或工具本身需要手动确认时。 |
   |`document_review` |Plan 或 Spec 工作流中的文档审阅流程。 |
   |`ask_user_question` |智能体需要用户补充信息时，进行提问的通知。 |
   |`browser_interaction` |浏览器交互等待通知。 |
* **响应体**：该事件不使用响应体进行流程控制。即使服务端返回 JSON，也不会改变智能体的行为。
* **失败行为**：`Notification` 为非阻断事件。即使 Hook 服务返回非 `2xx` 状态码、请求超时或连接失败，也不会改变智能体的执行流程。

## PreToolUse 和 PostToolUse 事件支持的工具 {#hVL37mbtD}

在 `PreToolUse` 和 `PostToolUse` 事件中，你可以通过 `matcher` 字段匹配 `tool_name`。

`tool_name` 为标准化工具名称，取值如下：

<!-- @cols-width: 147,290,477 -->
|**分类** |**工具名称** |**描述** |
|---|---|---|
|文件读取 |`Read` |读取文件内容。 |
|文件写入 |`Write` |写入文件。 |
|文件编辑 |`Edit` |单次查找并替换文件内容。 |
|搜索 |`Glob` |基于文件路径模式进行匹配搜索。 |
|^^| | | \
| |`Grep` |基于正则表达式进行内容搜索。 |
|^^| | | \
| |`LS` |列出目录下的文件与子目录。 |
|终端 |`RunCommand` |执行终端命令。 |
|网络 |`WebSearch` |网络搜索。 |
|^^| | | \
| |`WebFetch` |获取网页内容。 |
|交互 |`AskUserQuestion` |向用户提问。 |
|Skill |`Skill` |加载 Skill。 |
|MCP |`mcp__<serverName>__<toolName>` |MCP 工具。 |\
| | | |\
| | |**MCP 工具匹配说明**：在 Hook 中，MCP 工具的标准化名称格式为 `mcp__<serverName>__<toolName>`（例如 `mcp__Git__iCube__git_status`）。你可以在 `matcher` 字段中使用 `mcp__.*` 来匹配所有 MCP 工具，或使用具体工具的名称进行精确匹配。 |

## 示例 {#hsw0pL8zc}

### 事件配置 {#hMCyP1Cjm}

::::tabs
@tab SessionStart
```JSON
[
  {
    "hooks": [
      {
        "type": "http",
        "url": "http://localhost:9000/session-start"
      }
    ]
  }
]
```

@tab UserPromptSubmit
```JSON
[
  {
    "hooks": [
      {
        "type": "http",
        "url": "http://localhost:9000/user-prompt-submit"
      }
    ]
  }
]
```

@tab PreToolUse
```JSON
[
  {
    "matcher": "Read",
    "hooks": [
      {
        "type": "http",
        "url": "http://localhost:9000/pre-tool-use/block-read"
      }
    ]
  }
]
```

@tab PostToolUse
```JSON
[
  {
    "hooks": [
      {
        "type": "http",
        "url": "http://localhost:9000/post-tool-use"
      }
    ]
  }
]
```

@tab Stop
```JSON
[
  {
    "hooks": [
      {
        "type": "http",
        "url": "http://localhost:9000/stop"
      }
    ]
  }
]
```

@tab Notification
```JSON
[
  {
    "hooks": [
      {
        "type": "http",
        "url": "http://localhost:9000/notification"
      }
    ]
  }
]
```
::::

### 请求体示例 {#hPYyEgzSS}

::::tabs
@tab SessionStart
```JSON
{
  "session_id": "6a321771xxxxxx1312bfebd8",
  "cwd": "/Users/example/Documents/code/hooks_test",
  "hook_event_name": "SessionStart",
  "workspace_roots": [
    "/Users/example/Documents/code/hooks_test"
  ],
  "agent_id": "dev_agent",
  "agent_type": "dev_agent",
  "source": "startup",
  "email": "zhangsan@example.com",
  "model": "auto/Trae"
}
```

@tab UserPromptSubmit
```JSON
{
  "session_id": "6a321771xxxxxx1312bfebd8",
  "cwd": "/Users/example/Documents/code/hooks_test",
  "hook_event_name": "UserPromptSubmit",
  "workspace_roots": [
    "/Users/example/Documents/code/hooks_test"
  ],
  "agent_id": "dev_agent",
  "agent_type": "dev_agent",
  "prompt": "介绍当前项目",
  "email": "zhangsan@example.com",
  "model": "auto/Trae"
}
```

@tab PreToolUse
```JSON
{
  "session_id": "6a321771xxxxxx1312bfebd8",
  "cwd": "/Users/example/Documents/code/hooks_test",
  "hook_event_name": "PreToolUse",
  "workspace_roots": [
    "/Users/example/Documents/code/hooks_test"
  ],
  "agent_id": "dev_agent",
  "agent_type": "dev_agent",
  "tool_use_id": "call_rDEJn2bhxxxxxx78d9O8Jb7e",
  "tool_name": "LS",
  "llm_tool_name": "LS",
  "tool_input": {
    "path": "/Users/example/Documents/code/hooks_test"
  },
  "email": "zhangsan@example.com",
  "model": "auto/Trae"
}
```

@tab PostToolUse
```JSON
{
  "session_id": "6a321771xxxxxx1312bfebd8",
  "cwd": "/Users/example/Documents/code/hooks_test",
  "hook_event_name": "PostToolUse",
  "workspace_roots": [
    "/Users/example/Documents/code/hooks_test"
  ],
  "agent_id": "dev_agent",
  "agent_type": "dev_agent",
  "tool_use_id": "call_fNyUXHCOCsxxxxxx4YUtPMdc",
  "tool_name": "Glob",
  "llm_tool_name": "Glob",
  "tool_input": {
    "path": "/Users/example/Documents/code/hooks_test",
    "pattern": "**/*"
  },
  "tool_response": {
    "reach_limit": false,
    "files": [
      {
        "absPath": "/Users/example/Documents/code/hooks_test/hooks_server.py",
        "size": 3755,
        "mtime": 1781665658642,
        "lineCount": null
      }
    ]
  },
  "email": "zhangsan@example.com",
  "model": "auto/Trae"
}
```

@tab Stop
```JSON
{
  "session_id": "6a321771xxxxxx1312bfebd8",
  "cwd": "/Users/example/Documents/code/hooks_test",
  "hook_event_name": "Stop",
  "workspace_roots": [
    "/Users/example/Documents/code/hooks_test"
  ],
  "agent_id": "dev_agent",
  "agent_type": "dev_agent",
  "stop_hook_active": false,
  "loop_count": 0,
  "last_assistant_message": "项目介绍",
  "email": "zhangsan@example.com",
  "model": "auto/Trae"
}
```

@tab Notification
```JSON
{
  "session_id": "6a321771xxxxxx1312bfebd8",
  "cwd": "/Users/example/Documents/code/hooks_test",
  "hook_event_name": "Notification",
  "workspace_roots": [
    "/Users/example/Documents/code/hooks_test"
  ],
  "agent_id": "dev_agent",
  "agent_type": "dev_agent",
  "notification_type": "idle_prompt",
  "message": "智能体已完成任务",
  "email": "zhangsan@example.com",
  "model": "auto/Trae"
}
```
::::

### 服务端点 {#htWjoOB38}

这段示例代码提供了一个可在本地运行的 TRAE 企业 HTTP Hook 调试服务。服务启动后会监听 `9000` 端口，并为 `SessionStart`、`UserPromptSubmit`、`PreToolUse`、`PostToolUse`、`Stop` 和 `Notification` 这些 Hook 事件分别提供 HTTP endpoint。TRAE 触发 Hook 时，会将事件请求体发送到对应 endpoint；服务端会在终端打印完整的请求体和响应体，便于你检查字段结构、事件触发时机和流程控制结果。你可以直接使用该示例验证企业 Hook 配置是否生效，也可以基于它扩展企业内部的权限校验、审计记录、上下文注入或通知逻辑。

```Python
#!/usr/bin/env python3
import json
import sys
from copy import deepcopy
from datetime import datetime
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse

PORT = 9000

ROUTES = {
    "/session-start": {
        "event": "SessionStart",
        "description": "会话开始时触发，默认继续执行。",
    },
    "/user-prompt-submit": {
        "event": "UserPromptSubmit",
        "description": "用户提交 Prompt 后触发，默认继续执行。",
    },
    "/user-prompt-submit/block": {
        "event": "UserPromptSubmit",
        "action": "block",
        "description": "示例：阻断用户请求。",
    },
    "/pre-tool-use": {
        "event": "PreToolUse",
        "description": "工具执行前触发，默认继续执行。",
    },
    "/pre-tool-use/block-read": {
        "event": "PreToolUse",
        "action": "block_read",
        "description": "示例：阻断 Read 工具。",
    },
    "/pre-tool-use/ask": {
        "event": "PreToolUse",
        "action": "ask",
        "description": "示例：要求用户确认后再执行工具。",
    },
    "/post-tool-use": {
        "event": "PostToolUse",
        "description": "工具执行后触发，默认继续执行。",
    },
    "/stop": {
        "event": "Stop",
        "description": "智能体准备结束当前查询时触发，默认允许停止。",
    },
    "/stop/continue": {
        "event": "Stop",
        "action": "continue",
        "description": "示例：阻止智能体停止，并要求继续处理。",
    },
    "/notification": {
        "event": "Notification",
        "description": "通知事件，非阻断，响应体不影响智能体流程。",
    },
}

DEFAULT_RESPONSES = {
    "SessionStart": {},
    "UserPromptSubmit": {},
    "PreToolUse": {},
    "PostToolUse": {},
    "Stop": {},
    "Notification": {},
}


def json_dumps(data: dict) -> str:
    return json.dumps(data, indent=2, ensure_ascii=False)


def print_event(event_name: str, path: str, payload: dict, response: dict) -> None:
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
    sep = "─" * 80

    print(f"\n{'━' * 80}")
    print(f"  TRAE Hook Event: {event_name}")
    print(f"  Time: {ts}")
    print(f"  POST http://localhost:{PORT}{path}")
    print(sep)
    print("[request]")
    print(json_dumps(payload))
    print(sep)
    print("[response]")
    print(json_dumps(response))
    print(sep)
    sys.stdout.flush()


def build_response(route: dict, payload: dict) -> dict:
    event_name = route["event"]
    action = route.get("action")

    if action == "block":
        return {
            "decision": "block",
            "reason": "该请求被企业 Hook 策略阻断。",
        }

    if action == "block_read":
        tool_name = payload.get("tool_name")
        if tool_name == "Read":
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": "Read 工具已被企业 Hook 策略阻断。",
                }
            }

        return {}

    if action == "ask":
        return {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "ask",
                "permissionDecisionReason": "该工具调用需要用户确认后再执行。",
            }
        }

    if action == "continue":
        return {
            "decision": "block",
            "reason": "请继续检查当前回答是否完整，并补充必要的验证结果。",
        }

    return deepcopy(DEFAULT_RESPONSES[event_name])


class HookHandler(BaseHTTPRequestHandler):
    def log_message(self, fmt, *args):
        return

    def send_json(self, data: dict, status: int = 200) -> None:
        body = json.dumps(data, ensure_ascii=False).encode("utf-8")

        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        path = urlparse(self.path).path

        if path == "/":
            self.send_json(
                {
                    "status": "ok",
                    "name": "TRAE Enterprise HTTP Hooks Server",
                    "port": PORT,
                    "routes": ROUTES,
                }
            )
            return

        self.send_json({"error": f"not found: {path}"}, status=404)

    def do_POST(self):
        path = urlparse(self.path).path
        route = ROUTES.get(path)

        if route is None:
            self.send_json({"error": f"unknown hook path: {path}"}, status=404)
            return

        length = int(self.headers.get("Content-Length", 0))
        raw = self.rfile.read(length) if length else b"{}"

        try:
            payload = json.loads(raw.decode("utf-8"))
        except json.JSONDecodeError as exc:
            self.send_json(
                {
                    "error": "invalid JSON request body",
                    "detail": str(exc),
                },
                status=400,
            )
            return

        response = build_response(route, payload)
        print_event(route["event"], path, payload, response)
        self.send_json(response)


def main():
    server = HTTPServer(("0.0.0.0", PORT), HookHandler)

    print(f"\nTRAE Enterprise HTTP Hooks Server  port {PORT}")
    print("─" * 80)
    print("Health check:")
    print(f"  GET   http://localhost:{PORT}/")
    print("\nHook endpoints:")
    for path, route in ROUTES.items():
        print(
            f"  POST  http://localhost:{PORT}{path:<32}  "
            f"{route['event']:<16}  {route.get('description', '')}"
        )
    print("─" * 80)
    print("Waiting for TRAE hook events...\n")
    sys.stdout.flush()

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nStopped.")


if __name__ == "__main__":
    main()
```
