> ## 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

Hooks 允许你在 AI 会话的关键生命周期节点注入自定义逻辑，支持审计日志、安全控制、上下文注入和动态行为修改。

<div id="事件概览" />

## 事件概览

| 事件                   | 触发时机          | 可控行为              |
| -------------------- | ------------- | ----------------- |
| `PreToolUse`         | 工具调用前         | 拦截 / 放行 / 修改输入    |
| `PostToolUse`        | 工具执行成功后       | 审计 / 注入上下文 / 覆盖输出 |
| `PostToolUseFailure` | 工具执行失败后       | 错误处理 / 日志记录       |
| `UserPromptSubmit`   | 用户 prompt 发送前 | 注入上下文 / 拦截        |
| `SessionStart`       | 会话开始时         | 初始化 / 注入上下文       |
| `SessionEnd`         | 会话结束时         | 清理 / 日志记录         |
| `Stop`               | AI 停止生成时      | 阻止停止，强制继续         |
| `SubagentStart`      | 子 Agent 启动时   | 观察 / 日志记录         |
| `SubagentStop`       | 子 Agent 停止时   | 观察 / 日志记录         |
| `PreCompact`         | 上下文压缩前        | 观察 / 日志记录         |
| `PostCompact`        | 上下文压缩后        | 观察 / 日志记录         |
| `CwdChanged`         | 工作目录变更时       | 观察 / 日志记录         |
| `InstructionsLoaded` | 指令文件加载时       | 观察 / 日志记录         |
| `FileChanged`        | 文件创建/修改/删除时   | 观察 / 日志记录         |
| `PermissionRequest`  | 权限申请时         | 自动批准 / 拒绝权限请求     |

完整事件类型定义见 [Hooks Reference](/zh/cli/sdk/references#hooks-reference)。

<div id="配置" />

## 配置

在 `QueryOptions.hooks` 中配置 hooks：

```typescript theme={null}
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
}
```

<div id="matcher" />

### Matcher

`matcher` 字段为正则表达式，只有工具名称匹配时 hook 才会触发：

```typescript theme={null}
const result = query({
  prompt: 'perform task',
  options: {
    hooks: {
      PreToolUse: [
        { matcher: 'Bash', hooks: [bashAuditHook] },           // 仅 Bash
        { matcher: 'File.*|Write|Edit', hooks: [fileAuditHook] },  // 文件操作
        { hooks: [generalLogHook] },                            // 所有工具（无 matcher）
      ],
    },
  },
});
```

<div id="回调函数" />

### 回调函数

每个 hook 回调接收事件输入、工具调用 ID 和 abort signal：

```typescript theme={null}
type HookCallback = (
  input: HookInput,
  toolUseID: string | undefined,
  options: { signal: AbortSignal },
) => Promise<HookJSONOutput>;
```

<div id="输入" />

#### 输入

所有事件共享通用字段：`hook_event_name`（事件类型）、`session_id`（会话 ID）、`transcript_path`（记录文件路径）、`cwd`（工作目录）。每个事件还有专属字段，如 `PreToolUse` 的 `tool_name` 和 `tool_input`。

完整输入类型定义见 [Hooks Reference](/zh/cli/sdk/references#basehookinput)。

<div id="输出" />

#### 输出

回调返回一个对象，通过以下字段控制行为：

* `continue: false` — 终止会话
* `decision: "block"` + `reason` — 阻止工具执行或阻止 AI 停止
* `hookSpecificOutput` — 事件专属输出，如修改工具输入（`updatedInput`）、覆盖工具输出（`updatedToolOutput`）、注入上下文（`additionalContext`）

完整输出类型定义见 [Hooks Reference](/zh/cli/sdk/references#hookjsonoutput)。

<div id="示例" />

## 示例

<div id="安全拦截pretooluse" />

### 安全拦截（PreToolUse）

拦截危险的 shell 命令：

```typescript theme={null}
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 {};
};
```

<div id="脱敏敏感信息posttooluse" />

### 脱敏敏感信息（PostToolUse）

覆盖工具输出，替换 AK/Token 等敏感信息：

```typescript theme={null}
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,
    },
  };
};
```

<div id="裁剪过长输出posttooluse" />

### 裁剪过长输出（PostToolUse）

截断超长的 Bash 输出，保留头尾：

```typescript theme={null}
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}`,
    },
  };
};
```

<div id="强制继续stop" />

### 强制继续（Stop）

阻止 AI 在任务未完成时停止：

```typescript theme={null}
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 {};
};
```

<div id="自动批准权限permissionrequest" />

### 自动批准权限（PermissionRequest）

自动放行 Read 工具的权限请求：

```typescript theme={null}
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 {};
};
```

> 完整权限模型请参见[权限文档](/zh/cli/sdk/permissions)。

<div id="审计与安全控制综合" />

### 审计与安全控制（综合）

组合审计日志和安全拦截：

```typescript theme={null}
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
}
```

***

<div id="注意事项" />

## 注意事项

* Hook 回调应尽快返回，避免阻塞 AI 执行。
* `matcher` 使用 JavaScript 正则语法，匹配 `tool_name` 字段。
* `continue: false` 可终止会话——仅对 `PreToolUse`、`PostToolUse`、`PostToolUseFailure`、`UserPromptSubmit`、`Stop`、`SubagentStop` 事件有效，观察类事件（如 `SessionEnd`、`CwdChanged`）会忽略此字段。
* 当多个 hook 返回冲突的 `decision` 值时，`"deny"` / `"block"` 优先（最严格的规则生效）。
* 当多个 hook 都设置了 `updatedToolOutput` 时，**最后一个非空值**生效。如需链式执行多个转换（如先脱敏再裁剪），请在单个回调内按顺序执行。
