feat: consolidate course into 21 lessons

This commit is contained in:
Haoran
2026-07-31 03:15:58 +08:00
parent 4bc33ec858
commit 2d69019342
200 changed files with 7338 additions and 10829 deletions

View File

@@ -2,7 +2,7 @@
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → s02 → s03 → `s04` → [s05](../s05_todo_write/) → s06 → ... → s20 → s21 → s22
s01 → s02 → s03 → `s04` → [s05](../s05_todo_write/) → s06 → ... → s20 → s21
> *"挂在循环上, 不写进循环里"* — hook 在工具执行前后注入扩展逻辑。
>
@@ -48,7 +48,7 @@ s03 的循环和权限逻辑完全保留。唯一的变动是把 `check_permissi
| UserPromptSubmit | 用户输入提交后、进入 LLM 前 | 输入验证、注入上下文 |
| PreToolUse | 工具执行前 | 权限检查、日志记录 |
| PostToolUse | 工具执行后 | 副作用(自动 git add 等)、输出检查 |
| Stop | 循环即将退出时 | 收尾清理CC 还支持强制续跑) |
| Stop | 循环即将退出时 | 收尾清理、决定是否继续循环 |
扩展通过 `register_hook()` 添加,循环只调用 `trigger_hooks()`
@@ -77,9 +77,9 @@ def trigger_hooks(event: str, *args):
return None
```
教学版中,PreToolUse 的非 None 返回值会阻止本次工具执行Stop 的非 None 返回值会强制续跑。UserPromptSubmit 和 PostToolUse 的返回值未被使用
`PreToolUse` 返回非 `None` 时,本次工具执行被阻止;`Stop` 返回非 `None` 时,循环继续。`UserPromptSubmit``PostToolUse` 的返回值不参与控制流
**UserPromptSubmit**用户输入提交后、进入 LLM 前触发。CC 中可以拦截或修改输入,教学版只做日志演示
**UserPromptSubmit**用户输入提交后、进入 LLM 前触发。以下 hook 记录当前工作目录
```python
def context_inject_hook(query: str) -> str | None:
@@ -130,7 +130,7 @@ register_hook("PreToolUse", log_hook)
register_hook("PostToolUse", large_output_hook)
```
**Stop**循环即将退出时触发(`stop_reason != "tool_use"`)。教学版用于打印收尾统计:
**Stop**循环即将退出时触发(`stop_reason != "tool_use"`)。以下 hook 打印收尾统计:
```python
def summary_hook(messages: list) -> str | None:
@@ -220,64 +220,5 @@ Agent 现在能安全执行操作了。但它有没有停下来想过"我应该
s05 TodoWrite → 给 Agent 一个计划工具。先列清单,再做。
<details>
<summary>深入 CC 源码</summary>
> 以下基于 CC 源码 `toolHooks.ts`650 行)、`hooks.ts`、`stopHooks.ts`、`coreTypes.ts` 的完整分析。
### 一、Hook 事件:不止这 4 个,而是 27 个
教学版只讲了 PreToolUse 和 PostToolUse。CC 实际有 27 个 hook 事件(`coreTypes.ts:25-53`
| 类别 | 事件 |
|------|------|
| 工具相关 | `PreToolUse`, `PostToolUse`, `PostToolUseFailure` |
| 会话相关 | `SessionStart`, `SessionEnd`, `Stop`, `StopFailure`, `Setup` |
| 用户交互 | `UserPromptSubmit`, `Notification`, `PermissionRequest`, `PermissionDenied` |
| 子 Agent | `SubagentStart`, `SubagentStop` |
| 压缩相关 | `PreCompact`, `PostCompact` |
| 团队相关 | `TeammateIdle`, `TaskCreated`, `TaskCompleted` |
| 其他 | `Elicitation`, `ElicitationResult`, `ConfigChange`, `WorktreeCreate`, `WorktreeRemove`, `InstructionsLoaded`, `CwdChanged`, `FileChanged` |
教学版只讲 4 个核心事件UserPromptSubmit、PreToolUse、PostToolUse、Stop因为它们覆盖了一个完整 agent cycle 的关键节点。其他 23 个都是同样的模式。
### 二、HookResult 常用字段摘录
CC 的 `HookResult``types/hooks.ts:260-275`)有 14 个字段,以下是常用字段:
| 字段 | 类型 | 用途 |
|------|------|------|
| `message` | Message | 可选 UI 消息 |
| `blockingError` | HookBlockingError | 阻塞错误 → 注入对话让模型自纠 |
| `outcome` | success/blocking/non_blocking_error/cancelled | 执行结果 |
| `preventContinuation` | boolean | 阻止后续执行 |
| `stopReason` | string | 停止原因描述 |
| `permissionBehavior` | allow/deny/ask/passthrough | hook 返回权限决策 |
| `updatedInput` | Record | 修改工具输入 |
| `additionalContext` | string | 附加上下文 |
| `updatedMCPToolOutput` | unknown | MCP 工具输出修改 |
### 三、关键不变式Hook 'allow' 不能绕过 deny/ask 规则
这是 CC 权限系统最重要的安全设计(`toolHooks.ts:325-331`**hook 返回 allow 时,仍然要检查 settings.json 的 deny/ask 规则**。即使用户的 hook 脚本说"允许",如果在 settings.json 中禁用了这个工具,操作仍然会被阻止。
教学版没有这个层次,只把 PreToolUse 的非 None 返回值解释为阻止本次工具执行。这在教学场景中够了,但在生产环境中会形成安全漏洞。
### 四、stopHookActive 机制
CC 的 Stop hooks 有一个防无限循环机制(`query.ts:212,1300``stopHookActive` 状态字段。当 stop hooks 产生 blockingError 时,循环带 `stopHookActive: true` 重入下一轮。后续迭代中 stop hooks 看到这个标志就不会再次触发。这防止了一个永不停机的 bug模型自纠后 stop hook 再次报错 → 模型再自纠 → stop hook 再报错...
### 五、hook_stopped_continuation
PostToolUse hooks 返回 `preventContinuation: true` 时,会产生一个 `hook_stopped_continuation` 附件(`toolHooks.ts:117-130`。query.tsL1388-1393检测到后设置 `shouldPreventContinuation = true`,循环退出。这是 "hook 优雅地让 Agent 停机" 的机制,不是崩溃,是完成。
### 教学版的简化是刻意的
- 27 个事件 → 4 个UserPromptSubmit/PreToolUse/PostToolUse/Stop覆盖 agent cycle 关键节点
- 14 个字段 → 简单的返回值None = 继续,非 None = 阻止/续跑):心智负担降到最低
- Hook allow vs deny/ask 不变式 → 省略:教学版没有 settings.json 层
- stopHookActive → 省略:教学版 Stop hook 只做简单续跑,不涉及防无限循环机制
</details>
<!-- translation-sync: zh@v1, en@v0, ja@v0 -->