本文档介绍企业 Hook 的配置方式、执行环境、输入输出规范,以及各类事件的触发与处理机制。
每个 Hook 事件配置为一个 JSON 数组。数组中的每个对象表示一个 Hook 组,matcher 用于匹配工具名称或通知类型;SessionStart、UserPromptSubmit、Stop 三类非工具匹配事件无需配置 matcher。
[
{
"matcher": "{ToolPattern}",
"loop_limit": 5,
"hooks": [
{
"type": "http",
"url": "{URL to send the POST request to}",
"timeout": 30
}
]
}
]
Hook 相关字段的说明如下:
| 字段 | 类型 | 是否必填 | 描述 |
|---|---|---|---|
|
|
|
否 |
匹配规则,支持正则表达式(如 提示: |
|
|
|
否 |
当 该字段仅支持正整数;未配置或配置的值小于等于 提示: |
hooks |
array |
是 | 该 Hook 组下要执行的 Hook 列表。 |
| 字段 | 类型 | 是否必填 | 描述 |
|---|---|---|---|
type |
string |
否 | Hook 类型,企业 Hook 当前仅支持 HTTP 类型,取值为 http。 |
url |
string |
是 | 接收 Hook 请求的企业服务端 URL。TRAE 会向该 URL 发送 HTTP POST 请求。 |
timeout |
number |
否 | 请求超时时间,单位为秒,默认值为 30。 |
企业 Hook 通过 HTTP 请求调用企业自有服务。TRAE 会向配置的 url 发送 POST 请求,并通过 HTTP 状态码和响应体判断后续执行行为。
POSTContent-Type: application/jsontimeout 配置的时长仍未收到响应时,按执行失败处理。| HTTP 状态码 | 响应体 | 行为 |
|---|---|---|
2xx |
空 | 继续执行,无附加行为。 |
2xx |
纯文本 | 在 SessionStart 和 UserPromptSubmit 事件中,将文本作为上下文附加给模型。若为其他事件,则继续执行。 |
2xx |
JSON | 继续执行,并按 JSON 内容执行流程控制;若返回 continue: false,则让智能体停止执行。 |
非 2xx |
任意 | 阻断当前问答。 |
| 超时 / 连接失败 | — | 阻断当前问答。 |
提示
企业 Hook 依赖企业服务端的可用性。建议为 Hook 服务配置超时、监控和降级策略,避免因服务异常影响用户问答流程。
企业 Hook 的输入输出由 HTTP 请求体和响应体承载:请求体用于传递当前事件上下文,响应体用于返回流程控制结果或附加上下文。
每个 Hook 事件的请求体 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"
}
| 字段 | 类型 | 描述 |
|---|---|---|
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 | 当前会话使用的模型标识。 |
端点可在响应体中返回两种格式的数据:
SessionStart 和 UserPromptSubmit 事件。对于 JSON 格式的输出,所有事件均支持以下通用流程控制字段:
{
"continue": true,
"stopReason": "string"
}
| 字段 | 类型 | 描述 |
|---|---|---|
| continue | boolean | 智能体是否在 Hook 执行完毕后继续执行。默认值为 true。若设置为 false,智能体将停止执行。该字段优先于任何事件特定的 decision 字段。 |
| stopReason | string | 当 continue 为 false 时,展示给用户的智能体停止执行的原因。 |
提示
Notification 是非阻断事件,不使用响应体进行流程控制。
{
"session_id": "...",
"hook_event_name": "SessionStart",
"source": "startup"
}
| 字段 | 类型 | 描述 |
|---|---|---|
source |
string |
会话的来源。目前仅支持 startup(新建会话)。 |
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "文本内容"
}
}
| 字段 | 类型 | 描述 |
|---|---|---|
additionalContext |
string |
附加给模型的上下文。 |
2xx 状态码、请求超时或连接失败,按 HTTP 调用约定处理。{
"session_id": "...",
"hook_event_name": "UserPromptSubmit",
"prompt": "用户输入的 Prompt"
}
| 字段 | 类型 | 描述 |
|---|---|---|
prompt |
string |
用户提交的 Prompt 文本。 |
{
"decision": "block",
"reason": "该请求不被允许的原因",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "附加给模型的上下文"
}
}
| 字段 | 类型 | 描述 |
|---|---|---|
decision |
string |
该字段仅支持设为 block。设置后,将禁止智能体执行该 Prompt。如需允许智能体执行该 Prompt,请将该字段留空。 |
reason |
string |
当 decision 字段的值为 block 时,此字段的内容将作为错误信息展示给用户。否则,该字段将被忽略。 |
additionalContext |
string |
附加给模型的上下文文本。 |
2xx 状态码、请求超时或连接失败,按 HTTP 调用约定处理。matcher 字段配置:可通过 matcher 字段配置正则表达式,从而匹配特定的工具名。{
"session_id": "...",
"hook_event_name": "PreToolUse",
"tool_use_id": "toolcall-id-string",
"tool_name": "RunCommand",
"llm_tool_name": "RunCommand",
"tool_input": { ... }
}
| 字段 | 类型 | 描述 |
|---|---|---|
tool_use_id |
string |
工具调用的唯一 ID。 |
tool_name |
string |
标准化的工具名称。详见 “PreToolUse 和 PostToolUse 事件支持的工具” 部分。 |
llm_tool_name |
string |
传递给大语言模型的原始工具名称。 |
tool_input |
object |
工具输入参数。 |
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "决策原因说明",
"updatedInput": { ... },
"additionalContext": "附加给模型的上下文"
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
|
|
|
权限决策,用于决定是否执行本次工具调用。可选值包括:
特殊情况说明:
|
permissionDecisionReason |
string |
权限决策的原因。 |
updatedInput |
object |
修改后的工具输入参数,将整体覆盖替换原始参数(非合并更新)。 |
additionalContext |
string |
附加给模型的上下文文本。 |
2xx 状态码、请求超时或连接失败,按 HTTP 调用约定处理。matcher 字段配置:可通过 matcher 字段配置正则表达式,从而匹配特定的工具名。{
"session_id": "...",
"hook_event_name": "PostToolUse",
"tool_use_id": "toolcall-id-string",
"tool_name": "RunCommand",
"llm_tool_name": "RunCommand",
"tool_input": { ... },
"tool_response": { ... }
}
| 字段 | 类型 | 描述 |
|---|---|---|
tool_use_id |
string |
工具调用的唯一 ID。 |
tool_name |
string |
标准化的工具名称。详见 “PreToolUse 和 PostToolUse 事件支持的工具” 部分。 |
llm_tool_name |
string |
传递给大语言模型的原始工具名称。 |
tool_input |
object |
工具输入参数。 |
tool_response |
object |
工具调用的结果。 |
{
"decision": "block",
"reason": "阻断原因",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "附加给模型的上下文"
}
}
| 字段 | 类型 | 描述 |
|---|---|---|
decision |
string |
该字段仅支持设为 block。设置后,会向模型传递一条阻断信息,表示工具已执行且无法撤销。如需允许智能体继续处理工具调用的结果,将该字段留空。 |
reason |
string |
当 decision 字段的值为 block 时,此字段的内容将作为阻断原因展示给用户。否则,该字段将被忽略。 |
additionalContext |
string |
附加给模型的上下文文本。 |
2xx 状态码、请求超时或连接失败,按 HTTP 调用约定处理。{
"session_id": "...",
"hook_event_name": "Stop",
"stop_hook_active": false,
"loop_count": 0,
"last_assistant_message": "大语言模型最终输出的文本内容"
}
| 字段 | 类型 | 描述 |
|---|---|---|
stop_hook_active |
boolean |
当前查询是否已经被 Stop 事件的 Hook 至少阻断过一次。 |
|
|
|
当前查询的 循环限制:你可以通过 |
last_assistant_message |
string |
大语言模型最终输出的文本内容。 |
{
"decision": "block",
"reason": "请继续检查测试是否通过"
}
| 字段 | 类型 | 描述 |
|---|---|---|
decision |
string |
该字段仅支持设为 block。设置后,将阻断智能体停止执行。如需让智能体停止执行,将该字段留空。 |
reason |
string |
当 decision 字段的值为 block 时,此字段的内容将作为新的用户请求让智能体继续执行。否则,该字段将被忽略。 |
2xx 状态码、请求超时或连接失败,按 HTTP 调用约定处理。Stop 事件的决策控制逻辑如下:
智能体准备停止
│
▼
检查 loop_count 是否大于等于 loop_limit?──── 是 ──► 跳过 Hook,允许智能体停止
│
否
│
▼
调用 Stop 事件的企业 Hook 服务
│
├── 返回 2xx 状态码,且 decision 字段为空 ───────► 允许停止
│
├── 返回 2xx 状态码,且 decision 字段的值为 block ──► 阻断停止,将 reason 字段作为新 Query
│
└── 非 2xx 状态码 / 超时 / 连接失败 ───────────► 按 HTTP 调用约定处理
matcher 字段配置:基于通知类型(notification_type)匹配,而不是基于工具名匹配。未配置 matcher,或将其配置为空字符串或 * 时,表示匹配所有通知类型。{
"session_id": "...",
"hook_event_name": "Notification",
"notification_type": "idle_prompt",
"message": "智能体已完成任务",
"tool_use_id": "toolu_xxx"
}
| 字段 | 类型 | 描述 |
|---|---|---|
notification_type |
string |
通知类别,用于标识通知场景,也用于匹配 matcher 字段的配置。可选值见下表。 |
message |
string |
通知的正文。 |
|
|
|
关联的工具调用 ID。 仅工具调用相关的通知类型携带该 ID,例如 |
notification_type 字段的值和相应触发时机如下:
| 值 | 触发时机 |
|---|---|
idle_prompt |
智能体完成当前任务。 |
permission_prompt |
工具调用需要用户确认后才能继续执行,例如当 PreToolUse 事件的 Hook 返回 ask 决策,或工具本身需要手动确认时。 |
document_review |
Plan 或 Spec 工作流中的文档审阅流程。 |
ask_user_question |
智能体需要用户补充信息时,进行提问的通知。 |
browser_interaction |
浏览器交互等待通知。 |
Notification 为非阻断事件。即使 Hook 服务返回非 2xx 状态码、请求超时或连接失败,也不会改变智能体的执行流程。在 PreToolUse 和 PostToolUse 事件中,你可以通过 matcher 字段匹配 tool_name。
tool_name 为标准化工具名称,取值如下:
| 分类 | 工具名称 | 描述 |
|---|---|---|
| 文件读取 | Read |
读取文件内容。 |
| 文件写入 | Write |
写入文件。 |
| 文件编辑 | Edit |
单次查找并替换文件内容。 |
| 搜索 | Glob |
基于文件路径模式进行匹配搜索。 |
|
|
基于正则表达式进行内容搜索。 |
|
|
|
列出目录下的文件与子目录。 |
|
| 终端 | RunCommand |
执行终端命令。 |
| 网络 | WebSearch |
网络搜索。 |
|
|
获取网页内容。 |
|
| 交互 | AskUserQuestion |
向用户提问。 |
| Skill | Skill |
加载 Skill。 |
|
MCP |
|
MCP 工具。 MCP 工具匹配说明:在 Hook 中,MCP 工具的标准化名称格式为 |
[
{
"hooks": [
{
"type": "http",
"url": "http://localhost:9000/session-start"
}
]
}
]
[
{
"hooks": [
{
"type": "http",
"url": "http://localhost:9000/user-prompt-submit"
}
]
}
]
[
{
"matcher": "Read",
"hooks": [
{
"type": "http",
"url": "http://localhost:9000/pre-tool-use/block-read"
}
]
}
]
[
{
"hooks": [
{
"type": "http",
"url": "http://localhost:9000/post-tool-use"
}
]
}
]
[
{
"hooks": [
{
"type": "http",
"url": "http://localhost:9000/stop"
}
]
}
]
[
{
"hooks": [
{
"type": "http",
"url": "http://localhost:9000/notification"
}
]
}
]
{
"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"
}
{
"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"
}
{
"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"
}
{
"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"
}
{
"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"
}
{
"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"
}
这段示例代码提供了一个可在本地运行的 TRAE 企业 HTTP Hook 调试服务。服务启动后会监听 9000 端口,并为 SessionStart、UserPromptSubmit、PreToolUse、PostToolUse、Stop 和 Notification 这些 Hook 事件分别提供 HTTP endpoint。TRAE 触发 Hook 时,会将事件请求体发送到对应 endpoint;服务端会在终端打印完整的请求体和响应体,便于你检查字段结构、事件触发时机和流程控制结果。你可以直接使用该示例验证企业 Hook 配置是否生效,也可以基于它扩展企业内部的权限校验、审计记录、上下文注入或通知逻辑。
#!/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()