第8课:一键操作 —— Actions 系统详解
本课目标
搞懂 Actions 系统是怎么工作的——从 AI 生成按钮、到用户点击、到后端执行。
Action 是什么?
Action = 一个可以在聊天界面里点击执行的按钮。
关键特点: - 不是通用的——每个 Action 都是针对特定场景的(如"归档 Acme Corp 的旧邮件",不是"归档邮件") - AI 动态生成——根据对话上下文,AI 判断需要什么 Action - 用户确认——按钮出现后,用户点了才执行,不会自动执行 - 有完整上下文——执行时能访问邮箱API、AI、数据库、通知系统
Action 的生命周期
graph TD
A["1. 用户在聊天里说: 帮我把过期订阅邮件清理了"]
A --> B["2. AI 分析上下文,找到合适的 Action 模板<br>(archive-old-newsletters)"]
B --> C["3. AI 在回复中生成 ActionInstance<br>templateId: archive-old-newsletters<br>label: 归档 47 封过期订阅邮件"]
C --> D["4. 前端渲染为可点击的按钮"]
D -->|"用户点击"| E["5. 后端加载 action 模板的 handler 函数"]
E --> F["6. handler 执行(搜索邮件 -> 批量归档)"]
F --> G["7. 执行结果返回前端 + 记录到日志"]
Action 模板长什么样?
位置:agent/custom_scripts/actions/
每个 Action 模板包含两部分:config(配置)和 handler(执行逻辑)。
示例:归档过期订阅邮件
// agent/custom_scripts/actions/archive-old-newsletters.ts
// 第一部分:配置(告诉 AI 这个 Action 是干什么的)
export const config = {
templateId: "archive-old-newsletters",
name: "归档过期订阅邮件",
description: "归档指定天数前的订阅类邮件",
parameters: {
type: "object",
properties: {
olderThanDays: {
type: "number",
description: "归档多少天前的邮件",
default: 30
}
}
}
};
// 第二部分:执行逻辑(用户点击按钮后运行)
export async function handler(params, context) {
const { olderThanDays } = params;
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
// 1. 搜索过期订阅邮件
const emails = await context.emailApi.search({
query: `category:promotions before:${formatDate(cutoffDate)}`
});
// 2. 批量归档
let archived = 0;
for (const email of emails) {
await context.emailApi.archive(email.id);
archived++;
}
// 3. 通知用户
context.notify(`已归档 ${archived} 封过期订阅邮件`);
// 4. 记录日志
context.log({ action: "archive-newsletters", count: archived });
return { success: true, count: archived };
}
示例:转发 Bug 报告
// agent/custom_scripts/actions/forward-bugs-to-engineering.ts
export const config = {
templateId: "forward-bugs-to-engineering",
name: "转发Bug报告给工程团队",
description: "分析邮件中的Bug信息,附上AI分析摘要后转发",
parameters: {
type: "object",
properties: {
emailId: { type: "string", description: "要转发的邮件ID" },
engineeringEmail: { type: "string", description: "工程团队邮箱" }
}
}
};
export async function handler(params, context) {
const { emailId, engineeringEmail } = params;
// 1. 读取原始邮件
const email = await context.emailApi.read(emailId);
// 2. 调用 AI 分析 Bug 内容(递归 AI!)
const analysis = await context.callAgent({
prompt: `分析这封 Bug 报告邮件,提取关键信息:
主题: ${email.subject}
内容: ${email.body_text}`,
schema: {
type: "object",
properties: {
severity: { type: "string", enum: ["critical", "high", "medium", "low"] },
summary: { type: "string" },
steps_to_reproduce: { type: "string" },
affected_component: { type: "string" }
}
}
});
// 3. 构造转发内容
const forwardBody = `
=== AI 分析摘要 ===
严重程度: ${analysis.severity}
摘要: ${analysis.summary}
影响组件: ${analysis.affected_component}
复现步骤: ${analysis.steps_to_reproduce}
=== 原始邮件 ===
${email.body_text}
`;
// 4. 发送转发
await context.emailApi.send({
to: engineeringEmail,
subject: `[Bug-${analysis.severity}] ${email.subject}`,
body: forwardBody
});
context.notify(`Bug 报告已转发给 ${engineeringEmail}`);
return { success: true };
}
看到了吗?handler 里调用了 context.callAgent() ——让 AI 帮忙分析 Bug 内容,然后代码根据 AI 的结果构造转发邮件。这就是代码+AI 配合的典范。
ActionContext 里有什么?
handler 函数的第二个参数 context 是一个"百宝箱":
| 属性/方法 | 功能 |
|---|---|
context.emailApi.search() |
搜索邮件 |
context.emailApi.read() |
读取邮件 |
context.emailApi.archive() |
归档邮件 |
context.emailApi.star() |
标星邮件 |
context.emailApi.label() |
贴标签 |
context.emailApi.send() |
发送邮件 |
context.callAgent() |
调用 AI 做判断 |
context.uiState.get() |
读取看板数据 |
context.uiState.set() |
更新看板数据 |
context.notify() |
给用户发通知 |
context.addMessage() |
在聊天里追加消息 |
context.log() |
记录日志 |
日志系统
所有 Action 的执行都会被记录到 .logs/actions/ 目录下:
.logs/actions/2026-02-28.jsonl
每行一个 JSON 对象:
{"timestamp":"2026-02-28T14:30:22Z","action":"archive-old-newsletters","params":{"olderThanDays":30},"result":{"success":true,"count":47},"duration_ms":3200}
方便你事后审查"AI 代你做了什么"。
本课小结
- Action = 聊天里的可点击按钮,AI 动态生成,用户确认执行
- 每个 Action 模板有 config(配置)+ handler(执行逻辑)
- handler 能访问完整的 ActionContext:邮箱API、AI、看板、通知
- callAgent() 让 Action 在执行过程中调用 AI 做分析
- 所有执行都有 JSONL 审计日志