| 1 | import { buildPageMetadata } from "@/lib/page-meta"; |
| 2 | |
| 3 | export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { |
| 4 | const { locale } = await params; |
| 5 | const isZh = locale === "zh"; |
| 6 | return buildPageMetadata({ |
| 7 | path: "/docs/hooks", |
| 8 | locale, |
| 9 | title: isZh ? "钩子 · Codewhale 文档" : "Hooks · Codewhale Docs", |
| 10 | description: isZh |
| 11 | ? "已发布的生命周期钩子:可变 message_submit、tool_call_before 决策、turn_end 与子 Agent 观察事件。" |
| 12 | : "The shipped lifecycle hooks: mutable message_submit, tool_call_before decisions, turn_end, and sub-agent observer events.", |
| 13 | }); |
| 14 | } |
| 15 | |
| 16 | export default async function HooksPage({ params }: { params: Promise<{ locale: string }> }) { |
| 17 | const { locale } = await params; |
| 18 | const isZh = locale === "zh"; |
| 19 | const bodyClass = isZh |
| 20 | ? "text-ink-soft leading-[1.9] tracking-wide" |
| 21 | : "text-ink-soft leading-relaxed"; |
| 22 | const events = isZh |
| 23 | ? [ |
| 24 | { |
| 25 | name: "message_submit(可变)", |
| 26 | detail: |
| 27 | "在用户消息进入历史或发给模型之前运行。钩子从 stdin 收到 JSON;exit 0 且 stdout 打印含非空 text 字段的 JSON 时替换提交文本;exit 2 在回合开始前阻止提交。多个钩子按配置顺序串行执行,每个钩子收到上一个钩子的输出文本。标记 background = true 的钩子只能观察,不能改写或阻止。", |
| 28 | }, |
| 29 | { |
| 30 | name: "tool_call_before(决策)", |
| 31 | detail: |
| 32 | "在每次工具调用执行前运行。除 exit 2 硬拒绝(始终生效)外,前台钩子可在 exit 0 时用 stdout JSON 给出决策:allow / deny / ask,并可附带 updatedInput 改写工具输入、additionalContext 追加进给模型的工具结果。多个钩子命中时优先级为 deny > ask > allow;tool_name 条件支持 * 通配(如 mcp__* 匹配所有 MCP 工具)。Full Access 不打开工具审批提示,因此 ask 不会降低该姿态。", |
| 33 | }, |
| 34 | { |
| 35 | name: "turn_end(观察)", |
| 36 | detail: |
| 37 | "在每个模型回合结束后触发,此时用量、成本、通知、收据和队列恢复状态都已更新。stdin 收到包含 status、duration_ms、usage、totals、queued_message_count 等字段的 JSON。stdout 被忽略,失败只记警告——不能阻止输入、改写 transcript 或改变下一个排队消息。", |
| 38 | }, |
| 39 | { |
| 40 | name: "subagent_spawn / subagent_complete(观察)", |
| 41 | detail: |
| 42 | "观察子 Agent 的启动与完成,stdin 收到有界的 JSON 元数据(agent_id、状态、截断后的 prompt/result 预览)。失败只记警告,不阻塞调度、不改 prompt 或结果;需要完整细节时使用 agent 返回的 transcript 句柄。", |
| 43 | }, |
| 44 | ] |
| 45 | : [ |
| 46 | { |
| 47 | name: "message_submit (mutable)", |
| 48 | detail: |
| 49 | "Runs before a submitted message is added to history or sent to the model. The hook receives JSON on stdin; exit 0 with stdout JSON carrying a non-empty text field replaces the submitted text, and exit 2 blocks the submission before the turn starts. Multiple hooks run serially in config order, each receiving the previous hook's output. Hooks marked background = true are observer-only and cannot transform or block.", |
| 50 | }, |
| 51 | { |
| 52 | name: "tool_call_before (decision)", |
| 53 | detail: |
| 54 | "Runs before each tool call executes. Beyond the exit-2 hard deny (which always wins), a foreground hook may print a JSON decision on stdout with exit 0: allow / deny / ask, plus updatedInput to rewrite the tool input and additionalContext appended to the tool result the model sees. When several hooks match, precedence is deny > ask > allow; tool_name conditions support * globs (mcp__* matches every MCP tool). Full Access does not open tool-approval prompts, so ask does not downgrade that posture.", |
| 55 | }, |
| 56 | { |
| 57 | name: "turn_end (observer)", |
| 58 | detail: |
| 59 | "Fires after each model turn ends, once usage, cost, notifications, receipts, and queue-recovery state have settled. The stdin JSON carries fields such as status, duration_ms, usage, totals, and queued_message_count. Stdout is ignored and failures are warn-only — the hook cannot block input, mutate the transcript, or change the next queued follow-up.", |
| 60 | }, |
| 61 | { |
| 62 | name: "subagent_spawn / subagent_complete (observer)", |
| 63 | detail: |
| 64 | "Observe sub-agent start and completion with bounded JSON metadata on stdin (agent_id, status, truncated prompt/result previews). Failures are warn-only and never block scheduling or change prompts or results; use the transcript handle returned by agent when full detail is needed.", |
| 65 | }, |
| 66 | ]; |
| 67 | |
| 68 | return ( |
| 69 | <section className="space-y-10"> |
| 70 | <section id="overview" className="scroll-mt-32"> |
| 71 | <h2 className="font-display text-3xl mb-1">{isZh ? "钩子" : "Hooks"}</h2> |
| 72 | <p className={`${bodyClass} mt-3`}> |
| 73 | {isZh |
| 74 | ? "钩子让你把自己的命令挂进 Codewhale 的生命周期:在消息提交前注入上下文、在工具调用前执行策略、在回合结束或子 Agent 启停时做审计。本页描述当前已发布的行为;docs/rfcs/1364-hooks-lifecycle.md 是这组能力的设计 RFC,完整配置 schema 见 docs/CONFIGURATION.md。" |
| 75 | : "Hooks attach your own commands to Codewhale's lifecycle: inject context before a message is submitted, enforce policy before a tool call, and audit turns or sub-agent activity. This page describes what currently ships; docs/rfcs/1364-hooks-lifecycle.md is the design RFC for this surface, and docs/CONFIGURATION.md carries the full configuration schema."} |
| 76 | </p> |
| 77 | <p className={`${bodyClass} mt-3`}> |
| 78 | {isZh ? ( |
| 79 | <> |
| 80 | 钩子配置在 config.toml 的 <code className="inline">[[hooks.hooks]]</code> 条目下;TUI 里运行{" "} |
| 81 | <code className="inline">/hooks</code> 可以按事件分组查看每个钩子的名称、命令预览、超时和条件,以及{" "} |
| 82 | <code className="inline">[hooks].enabled</code> 的全局开关状态。 |
| 83 | </> |
| 84 | ) : ( |
| 85 | <> |
| 86 | Hooks are configured under <code className="inline">[[hooks.hooks]]</code> entries in |
| 87 | config.toml; run <code className="inline">/hooks</code> in the TUI to see every configured |
| 88 | hook grouped by event — name, command preview, timeout, and condition — plus the global{" "} |
| 89 | <code className="inline">[hooks].enabled</code> state. |
| 90 | </> |
| 91 | )} |
| 92 | </p> |
| 93 | <div className="hairline-t mt-6"> |
| 94 | {events.map((row) => ( |
| 95 | <section key={row.name} className="py-4 hairline-b"> |
| 96 | <h3 className="font-display text-lg">{row.name}</h3> |
| 97 | <p className={`${bodyClass} mt-1 text-sm`}>{row.detail}</p> |
| 98 | </section> |
| 99 | ))} |
| 100 | </div> |
| 101 | </section> |
| 102 | |
| 103 | <section id="project" className="scroll-mt-32"> |
| 104 | <h2 className="font-display text-2xl mb-1">{isZh ? "项目级钩子" : "Project-local hooks"}</h2> |
| 105 | <p className={`${bodyClass} mt-3`}> |
| 106 | {isZh |
| 107 | ? "仓库可以在 <workspace>/.codewhale/hooks.toml 中携带策略。因为项目钩子是可执行的 shell 配置,Codewhale 只有在工作区通过信任提示或用户配置中的 trust_level = \"trusted\" 被信任后才加载它们——会话内的 /trust on 和旧版 .deepseek/trusted 标记都不会单独启用项目钩子。受信任后,项目钩子追加在 config.toml 的全局钩子之后运行,因此对 updatedInput 而言最后生效。格式错误但已受信任的项目文件会记警告并回退到只用全局钩子。" |
| 108 | : "Repositories can ship policy in <workspace>/.codewhale/hooks.toml. Because project hooks are executable shell configuration, Codewhale loads them only after the workspace is trusted through the trust prompt or a trust_level = \"trusted\" entry in user-owned config — session /trust on and legacy .deepseek/trusted markers do not enable project hooks by themselves. Once trusted, project hooks are appended after the global hooks from config.toml, so they run last and win updatedInput ties. A malformed trusted project file logs a warning and startup falls back to global hooks only."} |
| 109 | </p> |
| 110 | </section> |
| 111 | |
| 112 | <section id="source" className="hairline-t pt-8"> |
| 113 | <p className="text-sm text-ink-mute"> |
| 114 | {isZh |
| 115 | ? "来源文档:docs/rfcs/1364-hooks-lifecycle.md(设计 RFC), docs/CONFIGURATION.md(配置 schema)· 更新时请同步修改 docs-map.ts。" |
| 116 | : "Source documents: docs/rfcs/1364-hooks-lifecycle.md (design RFC), docs/CONFIGURATION.md (configuration schema) · Update docs-map.ts when changing."} |
| 117 | </p> |
| 118 | </section> |
| 119 | </section> |
| 120 | ); |
| 121 | } |
| 122 |