Hook 函数签名与 Yao Agent 完全相同。变化的是使用方式:
- **Create Hook** —— 在 Claude Code 启动前写入上下文文件
- **Next Hook** —— 读取 Claude Code 生成的文件,然后展示结果
## Create Hook:准备上下文
Claude Code 从 workspace 读取文件。在 Create Hook 返回之前,写入 Claude 理解任务所需的一切。
```typescript
// assistants/my-agent/src/index.ts
import { agent, Process } from "@yao/runtime";
export function Create(
ctx: agent.Context,
messages: agent.Message[]
): agent.Create {
const input = messages[messages.length - 1]?.content || "";
// 为 Claude Code 写入结构化上下文文件
ctx.workspace.WriteFile("context.md", `
# 任务
${input}
# 指令
- 将实现输出到 \`output/\`
- 将摘要写入 \`output/summary.md\`
- 不要修改 \`output/\` 之外的文件
`);
// 可选:存储时间戳供 Next Hook 使用
ctx.memory.context.Set("start_time", Date.now());
ctx.memory.context.Set("input", input);
return { messages };
}
```
### 注入动态数据
从数据库或其他来源加载数据,并写入上下文文件:
```typescript
export function Create(
ctx: agent.Context,
messages: agent.Message[]
): agent.Create {
const input = messages[messages.length - 1]?.content || "";
const projectId = ctx.route?.match(/[?&]project=([^&]+)/)?.[1];
let projectContext = "";
if (projectId) {
const project = Process("models.project.Find", Number(projectId), {});
if (project) {
projectContext = `
# 项目上下文
名称:${project.name}
技术栈:${project.stack}
描述:${project.description}
`;
}
}
ctx.workspace.WriteFile("context.md", `
# 任务
${input}
${projectContext}
# 指令
将实现输出到 \`output/\`。
`);
ctx.memory.context.Set("project_id", projectId || "");
return { messages };
}
```
### 执行准备命令
使用 `ctx.computer.Exec` 在 Claude 启动前准备环境:
```typescript
export function Create(
ctx: agent.Context,
messages: agent.Message[]
): agent.Create {
// 清理上一次的输出目录
const exists = ctx.workspace.Exists("output");
if (exists) {
ctx.workspace.RemoveAll("output");
}
ctx.workspace.MkdirAll("output");
// 获取当前 git 状态
const gitStatus = ctx.computer.Exec("git status --short");
ctx.workspace.WriteFile("context.md", `
# 任务
${messages[messages.length - 1]?.content || ""}
# 当前状态
\`\`\`
${gitStatus.stdout}
\`\`\`
`);
return { messages };
}
```
---
## Next Hook:收集产出
CLI Agent 中,`payload.completion.content` 包含 **Claude Code 产生的文本输出** —— 执行期间所有 text block 的拼接。
要收集结构化结果,读取 Claude Code 写入 workspace 的文件:
```typescript
import { agent, Process } from "@yao/runtime";
export function Next(
ctx: agent.Context,
payload: agent.Payload
): agent.Next | null {
if (payload.error) {
ctx.Send({ type: "error", props: { message: payload.error } });
return { data: { status: "error" } };
}
// 读取 Claude Code 生成的文件
if (!ctx.workspace.Exists("output/summary.md")) {
ctx.Send("Claude Code 未生成任何输出,请重试。");
return { data: { status: "no_output" } };
}
const summary = ctx.workspace.ReadFile("output/summary.md");
const files = ctx.workspace.ReadDir("output/");
// 列出生成的文件
const fileList = files
.filter((f) => !f.is_dir && f.name !== "summary.md")
.map((f) => `- ${f.name}(${f.size} 字节)`)
.join("\n");
const duration = Date.now() - (ctx.memory.context.Get("start_time") as number);
ctx.Send(`完成,用时 ${duration}ms\n\n**摘要:**\n${summary}\n\n**文件:**\n${fileList}`);
return { data: { status: "success" } };
}
```
### 读取结构化输出
若 Claude Code 写入 JSON:
```typescript
export function Next(
ctx: agent.Context,
payload: agent.Payload
): agent.Next | null {
if (payload.error) {
return { data: { status: "error", message: payload.error } };
}
let result: any;
try {
const raw = ctx.workspace.ReadFile("output/result.json");
result = JSON.parse(raw);
} catch {
ctx.Send("读取结果失败,请重试。");
return { data: { status: "parse_error" } };
}
// 保存到数据库
const id = Process("models.result.Save", result) as number;
// 跳转到结果页面
ctx.Send({
type: "action",
props: {
name: "navigate",
payload: { route: `/agents/my-agent/detail?id=${id}` },
},
});
return { data: { status: "success", id } };
}
```
---
## Payload 与 Yao Agent 的差异
| 字段 | Yao Agent | CLI Agent |
|------|-----------|-----------|
| `payload.completion.content` | LLM 的对话回复文本 | Claude Code 的文本输出(所有 text block 的拼接) |
| `payload.completion.tool_calls` | LLM 的工具调用 | 不使用(Claude Code 在内部处理工具调用) |
| `payload.tools` | Yao 的工具调用结果 | 不填充 |
| `payload.error` | LLM 或工具错误 | Runner 错误(容器崩溃、超时等) |
**关键转变:** 在 Yao Agent 中,`payload.completion.content` 就是答案。在 CLI Agent 中,答案通常在 **Claude Code 写入的文件里** —— `payload.completion.content` 是辅助信息。
## 下一步
你已了解 Hook 在 CLI Agent 中的用法。现在构建一个完整的端到端 Agent。
→ **[实战:Coding Agent](./build-coding-agent)**