Documentation Index
Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
Use this file to discover all available pages before exploring further.
Hooks 允许你在 AI 会话的关键生命周期节点注入自定义逻辑,支持审计日志、安全控制、上下文注入和动态行为修改。
事件概览
| 事件 | 触发时机 | 可控行为 |
|---|
PreToolUse | 工具调用前 | 拦截 / 放行 / 修改输入 |
PostToolUse | 工具执行成功后 | 审计 / 注入上下文 / 覆盖输出 |
PostToolUseFailure | 工具执行失败后 | 错误处理 / 日志记录 |
UserPromptSubmit | 用户 prompt 发送前 | 注入上下文 / 拦截 |
SessionStart | 会话开始时 | 初始化 / 注入上下文 |
SessionEnd | 会话结束时 | 清理 / 日志记录 |
Stop | AI 停止生成时 | 阻止停止,强制继续 |
SubagentStart | 子 Agent 启动时 | 观察 / 日志记录 |
SubagentStop | 子 Agent 停止时 | 观察 / 日志记录 |
PreCompact | 上下文压缩前 | 观察 / 日志记录 |
PostCompact | 上下文压缩后 | 观察 / 日志记录 |
CwdChanged | 工作目录变更时 | 观察 / 日志记录 |
InstructionsLoaded | 指令文件加载时 | 观察 / 日志记录 |
FileChanged | 文件创建/修改/删除时 | 观察 / 日志记录 |
PermissionRequest | 权限申请时 | 自动批准 / 拒绝权限请求 |
完整事件类型定义见 Hooks Reference。
在 QueryOptions.hooks 中配置 hooks:
import { query } from '@qoder-ai/qoder-agent-sdk';
import type { HookCallback, HookCallbackMatcher } from '@qoder-ai/qoder-agent-sdk';
const result = query({
prompt: 'perform task',
options: {
hooks: {
PreToolUse: [{ matcher: 'Bash', hooks: [myHook] }],
PostToolUse: [{ hooks: [auditHook] }],
SessionEnd: [{ hooks: [logHook] }],
},
},
});
for await (const message of result) {
// process messages
}
Matcher
matcher 字段为正则表达式,只有工具名称匹配时 hook 才会触发:
const result = query({
prompt: 'perform task',
options: {
hooks: {
PreToolUse: [
{ matcher: 'Bash', hooks: [bashAuditHook] }, // 仅 Bash
{ matcher: 'File.*|Write|Edit', hooks: [fileAuditHook] }, // 文件操作
{ hooks: [generalLogHook] }, // 所有工具(无 matcher)
],
},
},
});
回调函数
每个 hook 回调接收事件输入、工具调用 ID 和 abort signal:
type HookCallback = (
input: HookInput,
toolUseID: string | undefined,
options: { signal: AbortSignal },
) => Promise<HookJSONOutput>;
所有事件共享通用字段:hook_event_name(事件类型)、session_id(会话 ID)、transcript_path(记录文件路径)、cwd(工作目录)。每个事件还有专属字段,如 PreToolUse 的 tool_name 和 tool_input。
完整输入类型定义见 Hooks Reference。
回调返回一个对象,通过以下字段控制行为:
continue: false — 终止会话
decision: "block" + reason — 阻止工具执行或阻止 AI 停止
hookSpecificOutput — 事件专属输出,如修改工具输入(updatedInput)、覆盖工具输出(updatedToolOutput)、注入上下文(additionalContext)
完整输出类型定义见 Hooks Reference。
拦截危险的 shell 命令:
const securityHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'PreToolUse') return {};
if (input.tool_name === 'Bash') {
const cmd = String((input.tool_input as any)?.command ?? '');
if (cmd.includes('rm -rf')) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: 'Destructive delete operations are not allowed',
},
};
}
}
return {};
};
脱敏敏感信息(PostToolUse)
覆盖工具输出,替换 AK/Token 等敏感信息:
const secretRedactHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'PostToolUse') return {};
const content = typeof input.tool_response === 'string'
? input.tool_response
: JSON.stringify(input.tool_response);
const redacted = content
.replace(/(?:LTAI|AKID)[A-Za-z0-9]{16,}/g, '<REDACTED_AK>')
.replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/g, 'Bearer <REDACTED>');
if (redacted === content) return {};
return {
hookSpecificOutput: {
hookEventName: 'PostToolUse',
updatedToolOutput: redacted,
},
};
};
裁剪过长输出(PostToolUse)
截断超长的 Bash 输出,保留头尾:
const bashSummarizeHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'PostToolUse') return {};
if (input.tool_name !== 'Bash') return {};
const content = String(input.tool_response ?? '');
const THRESHOLD = 50 * 1024;
if (content.length <= THRESHOLD) return {};
const head = content.slice(0, 8 * 1024);
const tail = content.slice(-4 * 1024);
const omitted = content.length - head.length - tail.length;
return {
hookSpecificOutput: {
hookEventName: 'PostToolUse',
updatedToolOutput: `${head}\n\n[... OMITTED ${omitted} chars ...]\n\n${tail}`,
},
};
};
强制继续(Stop)
阻止 AI 在任务未完成时停止:
const keepGoingHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'Stop') return {};
if (!isTaskComplete()) {
return {
decision: 'block',
reason: 'Please continue completing the remaining tasks',
};
}
return {};
};
自动批准权限(PermissionRequest)
自动放行 Read 工具的权限请求:
const autoApproveRead: HookCallback = async (input) => {
if (input.hook_event_name !== 'PermissionRequest') return {};
if (input.tool_name === 'Read') {
return {
hookSpecificOutput: {
hookEventName: 'PermissionRequest',
decision: { behavior: 'allow' },
},
};
}
return {};
};
完整权限模型请参见权限文档。
审计与安全控制(综合)
组合审计日志和安全拦截:
import { query } from '@qoder-ai/qoder-agent-sdk';
import type { HookCallback } from '@qoder-ai/qoder-agent-sdk';
import * as fs from 'fs';
const auditLog = fs.createWriteStream('audit.log', { flags: 'a' });
const securityHook: HookCallback = async (input, toolUseID) => {
if (input.hook_event_name === 'PreToolUse') {
// Audit log
auditLog.write(JSON.stringify({
event: 'tool_call',
tool: input.tool_name,
input: input.tool_input,
timestamp: new Date().toISOString(),
}) + '\n');
// Security check: block curl to external domains
if (input.tool_name === 'Bash') {
const cmd = String((input.tool_input as any)?.command ?? '');
if (/curl\s+https?:\/\/(?!localhost)/.test(cmd)) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: 'HTTP requests to external domains are not allowed',
},
};
}
}
}
return {};
};
const result = query({
prompt: 'run deployment',
options: {
hooks: {
PreToolUse: [{ hooks: [securityHook] }],
},
},
});
for await (const message of result) {
// process messages
}
注意事项
- Hook 回调应尽快返回,避免阻塞 AI 执行。
matcher 使用 JavaScript 正则语法,匹配 tool_name 字段。
continue: false 可终止会话——仅对 PreToolUse、PostToolUse、PostToolUseFailure、UserPromptSubmit、Stop、SubagentStop 事件有效,观察类事件(如 SessionEnd、CwdChanged)会忽略此字段。
- 当多个 hook 返回冲突的
decision 值时,"deny" / "block" 优先(最严格的规则生效)。
- 当多个 hook 都设置了
updatedToolOutput 时,最后一个非空值生效。如需链式执行多个转换(如先脱敏再裁剪),请在单个回调内按顺序执行。