| name | cam-notify |
| description | Use when receiving a CAM system event (permission_request, waiting_for_input, agent_exited, error) to decide whether to notify user, auto-approve, or request human confirmation. Covers three-layer decision model (whitelist, blacklist, LLM judgment), risk levels, message formatting, and reply routing. |
CAM System Event 处理指南
当你收到来自 CAM 的 system event 时,使用此 skill 处理。
When to Use
- 收到 CAM 发送的 system event webhook
- 需要决定是否通知用户
- 需要自动审批 permission_request
- 需要格式化通知消息
- 需要路由用户回复到正确的 Agent
When NOT to Use
- 管理 Agent 生命周期(使用 cam skill)
- Team 编排和多 Agent 协作(使用 agent-teams skill)
System Event 数据结构
CAM 发送的 system event 格式:
{
"source": "cam",
"version": "1.0",
"agent_id": "cam-xxx",
"event_type": "permission_request",
"urgency": "HIGH",
"project_path": "/path/to/project",
"timestamp": "2026-02-18T10:00:00Z",
"event_data": {
"tool_name": "Bash",
"tool_input": {"command": "npm install express"},
"is_decision_required": false
},
"context": {
"terminal_snapshot": "...",
"extracted_message": "AI 提取的格式化消息",
"question_fingerprint": "npm-install-express-confirm",
"message_type": "confirmation",
"options": [],
"risk_level": "MEDIUM"
}
}
Context 字段说明
| 字段 | 类型 | 说明 |
|---|
terminal_snapshot | string? | 原始终端快照(最后 30-80 行) |
extracted_message | string? | AI 提取的格式化消息,包含完整问题和选项 |
question_fingerprint | string? | 语义指纹,用于去重(如 npm-install-express-confirm) |
message_type | string? | 问题类型:choice/confirmation/open_ended |
options | array? | 选项列表(仅 choice 类型) |
risk_level | string | 风险等级:LOW/MEDIUM/HIGH |
重要:优先使用 extracted_message,它包含 AI 提取的完整上下文。只有当 extracted_message 为空时才回退到 terminal_snapshot。
event_data 字段说明(按 event_type)
| event_type | 字段 | 类型 | 说明 |
|---|
waiting_for_input | pattern_type | string | 等待模式类型(Choice/Confirmation/OpenEnded) |
waiting_for_input | is_decision_required | bool | 是否需要关键决策(方案选择、架构设计等) |
permission_request | tool_name | string | 工具名称 |
permission_request | tool_input | object | 工具输入参数 |
Event Types
注意: Webhook payload 中 event_type 使用 snake_case(如下表所示)。代码内部枚举使用 PascalCase(如 WaitingForInput),序列化时自动转换。
| event_type | 描述 | 典型 urgency |
|---|
permission_request | 权限请求(工具执行确认) | HIGH |
waiting_for_input | 等待用户输入 | HIGH |
notification | 一般通知 | MEDIUM/LOW |
agent_exited | Agent 退出 | MEDIUM(正常)/ HIGH(异常) |
error | 错误发生 | HIGH |
session_start | 会话启动 | LOW |
session_end | 会话结束 | LOW |
Risk Levels
权威来源: 此处是风险等级的完整定义。agent-teams skill 中的风险等级引用此处。
| risk_level | 描述 | 示例 |
|---|
| LOW | 安全操作 | ls, cat, /tmp/ 路径 |
| MEDIUM | 需确认 | npm install, git push, 项目文件 |
| HIGH | 高风险 | rm -rf, sudo, 系统文件 |
三层决策模型
命令/确认请求
↓
┌─────────────────────────────────────┐
│ 第一层:白名单 → 直接批准 │
└─────────────────────────────────────┘
↓ 不在白名单
┌─────────────────────────────────────┐
│ 第二层:黑名单 → 必须人工确认 │
└─────────────────────────────────────┘
↓ 不在黑名单
┌─────────────────────────────────────┐
│ 第三层:LLM 判断 → 智能决策 │
└─────────────────────────────────────┘
第一层:白名单(直接批准)
# 只读命令
git status, git diff, git log
ls, pwd, which, cat, head, tail
# 测试命令
cargo test, cargo check, cargo clippy
npm test, npm run lint, npm run build
yarn test, pytest, go test, tsc --noEmit
⚠️ 参数安全检查:即使命令在白名单,如果参数包含以下敏感路径,仍需人工确认:
/etc/, ~/.ssh/, ~/.aws/, ~/.config/
.env, credentials, secret, token, password, id_rsa
示例:
cat README.md → ✅ 自动批准
cat /etc/passwd → ⚠️ 人工确认(敏感路径)
ls ~/.ssh/ → ⚠️ 人工确认(敏感路径)
第二层:黑名单(必须人工确认)
# 删除类命令
rm, rmdir, delete, drop, truncate
# 决策类提示
包含 "brainstorm", "选择方案", "which approach", "你想要" 的提示
# 生产/部署相关
deploy, push --force, production, release
# 命令链和重定向
包含 &&, ||, ;, |, >, >>, <, $(), `` 的命令
# 环境变量展开
包含 $VAR 形式的变量引用
第三层:LLM 判断
不在白名单也不在黑名单的命令,分析风险后决策:
- LOW: 只读操作、不影响系统状态、可逆操作、/tmp/ 路径 → 自动批准
- MEDIUM: 写入操作但影响范围有限、项目内文件 → 自动批准并通知
- HIGH: 删除、覆盖、不可逆、影响生产、敏感路径 → 人工确认
决策类事件(必须人工确认)
当 event_data.is_decision_required == true 时,表示 Agent 需要用户做出影响后续方向的重要决策。
决策类问题的特征:
- 技术方案选择("Which approach?", "哪个方案?")
- 架构设计决策("组件结构"、"数据流设计")
- 技术栈选择("React vs Vue"、"选择框架")
- 功能取舍("要不要加这个功能?")
- 实现策略("从头开始还是增强现有?"、"重构还是新写?")
处理规则:
is_decision_required == true → 必须转发给用户,不可自动处理
- 即使问题看起来简单,也不能代替用户做决策
- 使用
choice 或 open_ended 模板格式化后发送
禁止 决策类事件的合理化借口(全部禁止):
| 借口 | 为什么是错的 |
|---|
| "这个决策很简单,我可以代替用户选" | 决策的价值在于用户参与,不在于难度 |
| "用户之前选过类似的,我按惯例处理" | 每次决策的上下文不同,必须让用户确认 |
| "选默认选项就好" | 没有"默认"概念,所有选项平等,需要用户判断 |
| "这只是确认不是决策" | is_decision_required=true 时不区分,一律人工确认 |
自动批准后的通知规则
自动批准操作后,根据风险等级决定是否通知用户:
| 风险等级 | 通知行为 |
|---|
| LOW | 完全静默 - 不发送任何消息 |
| MEDIUM | 简短通知 - 发一条简短消息 |
| HIGH | 不会自动批准,必须人工确认 |
MEDIUM 风险自动批准通知格式:
✅ 已自动批准: {command} (MEDIUM)
[{agent_id}]
LOW 风险:直接执行 cam_agent_send(agent_id, "y"),不向用户发送任何消息。
非确认类 LOW 事件(如 session_start、session_end、notification 且 urgency=LOW):完全忽略,不执行任何操作,不产生任何输出。
🚫 "完全静默"的含义 — 不可绕过:
当规则要求静默时,意味着不产生任何面向用户的输出。包括但不限于:
- ❌ 不发送通知消息
- ❌ 不发送状态播报(如"agent 正在继续跑...")
- ❌ 不发送操作汇报(如"刚才批准了 xxx")
- ❌ 不发送进度更新
- ❌ 不在回复中附带提及这些事件
常见的合理化借口(全部禁止):
| 借口 | 为什么是错的 |
|---|
| "我不是在发通知,是在汇报状态" | 汇报状态 = 通知用户,静默意味着两者都不做 |
| "用户可能想知道 agent 在干什么" | 用户需要时会主动查看,不需要被动推送 |
| "我只是顺便提一下" | 顺便提 = 发送了消息,违反静默规则 |
| "批准了多个操作,应该汇总一下" | LOW 风险批量操作同样静默,不需要汇总 |
| "让用户知道我在监控" | 静默本身就是正确的监控行为 |
| "确认一下收到了事件" | 确认收到 = 发送消息,静默意味着不做任何回应 |
| "和其他通知一起顺带提一句" | 不得在合法的 MEDIUM/HIGH 通知中夹带 LOW 事件信息 |
| "用户之前说过想知道所有操作" | 用户偏好不覆盖静默规则,LOW 事件始终静默 |
正确行为:处理完 LOW 风险事件后,什么都不说,等待下一个需要用户操作的事件。
AgentExited 处理
区分正常退出和异常退出:
| 退出类型 | 判断条件 | Urgency | 行为 |
|---|
| 正常完成 | exit code 0 | LOW | 静默或简短通知 |
| 异常退出 | exit code != 0 | HIGH | 立即通知用户 |
| 超时退出 | 超过配置时间 | MEDIUM | 通知用户 |
通知聚合(Swarm 场景)
分层聚合,根据 urgency 级别使用不同窗口:
| Urgency | 聚合窗口 | 行为 |
|---|
| HIGH | 不聚合 | 立即发送 |
| MEDIUM | 30 秒 | 合并同类通知 |
| LOW | — | 完全静默 — 不聚合、不通知、不产生任何用户可见输出 |
聚合格式:
✅ [refactor-team] 已自动批准 5 个操作:
- git status (dev1, dev2, dev3)
- cargo check (dev1, dev2)
消息格式化规则
核心原则:用户不看终端也能理解问题并做出决策。
消息来源优先级
- 优先使用
extracted_message - AI 已提取完整上下文
- 回退到
terminal_snapshot - 当 AI 提取失败时
- 最后使用
event_data - 构造基本信息
根据 message_type 格式化
选择题 (choice)
当 context.message_type == "choice" 时:
💬 需要你选择
📋 问题:
{extracted_message 中的问题文本}
🔢 选项:
{遍历 context.options,保持原始编号}
A) 选项一描述
B) 选项二描述
C) 选项三描述
📝 回复字母选择 (A/B/C)
[{agent_id}]
确认题 (confirmation)
当 context.message_type == "confirmation" 或 event_type == "permission_request" 时:
⚠️ 请求确认
🔧 操作:
{tool_name}: {tool_input 的关键信息}
💡 上下文:
{extracted_message 中的背景说明,如果有}
⚡ 风险: {risk_level_emoji} {risk_level}
📝 回复 y 允许 / n 拒绝
[{agent_id}]
开放式问题 (open_ended)
当 context.message_type == "open_ended" 时:
💬 需要你输入
📋 问题:
{extracted_message 中的完整问题}
💡 背景:
{如果 extracted_message 包含背景信息}
📝 直接回复你的答案
[{agent_id}]
AI 提取失败时的处理
当 extracted_message 为空或 AI 提取失败时:
⚠️ 需要你的输入
📋 无法解析具体问题,请查看终端
🖥️ 终端快照:
{terminal_snapshot 最后 20 行,去除 UI 噪音}
📝 查看终端后回复
[{agent_id}]
处理步骤:
- 检查
context.extracted_message 是否存在且非空
- 如果为空,使用
terminal_snapshot 的最后 20 行
- 过滤掉明显的 UI 噪音(进度条、动画字符等)
- 明确告知用户需要查看终端
风险等级 Emoji
| risk_level | Emoji | 含义 |
|---|
| LOW | 🟢 | 安全操作,可放心执行 |
| MEDIUM | 🟡 | 需要确认,但风险可控 |
| HIGH | 🔴 | 高风险,请仔细检查 |
错误通知
❌ 遇到错误
{error_message}
回复查看详情或处理建议
[{agent_id}]
自动批准通知
✅ 已自动批准: {command}
[{agent_id}]
Common Mistakes
| 错误 | 正确做法 |
|---|
| LOW 风险事件时向用户发送状态更新 | LOW 事件完全静默,不产生任何用户可见输出 |
未检查 extracted_message 是否为 null 就使用 | 必须先检查,为 null 时回退到 terminal_snapshot |
| 在合法通知中夹带 LOW 事件信息 | 每条通知只包含触发它的那个事件 |
不区分 message_type 直接生成通知 | 必须按 choice/confirmation/open_ended 使用对应模板 |
| is_decision_required=true 时自动处理或代替用户做选择 | 决策类事件必须转发给用户,不可自动处理或代选 |
用户回复处理
当用户回复时,调用 CAM Plugin 工具:
| 用户回复 | 操作 |
|---|
| y / yes / 允许 | cam_agent_send(agent_id, "y") |
| n / no / 拒绝 | cam_agent_send(agent_id, "n") |
| 1 / 2 / 3 | cam_agent_send(agent_id, "1") 等 |
| 其他文本 | cam_agent_send(agent_id, 用户输入) |
回复工具
所有回复工具均可通过 CAM Plugin 调用(cam_ 前缀):
| 工具 | 说明 |
|---|
cam_agent_send(agent_id, message) | 向指定 agent 发送消息 |
cam_get_pending_confirmations() | 获取所有待处理的确认请求 |
cam_reply_pending(reply, target?) | 回复待处理确认(支持 y/n/1/2/3 快捷回复) |
cam_handle_user_reply(reply, context?) | 处理自然语言回复(自动解析意图) |
批量回复
支持批量操作:
| 命令 | 说明 |
|---|
cam reply y --all | 批准所有待处理请求 |
cam reply y --agent cam-* | 批准指定 agent 的请求 |
cam reply y --risk low | 批准所有 LOW 风险请求 |
Team 回复路由
如果 agent_id 包含 team 信息(如 team-xxx/member):
- 使用
cam_inbox_send(team, member, reply) 发送回复
重复确认机制
首次人工批准的命令,5 分钟内相同命令自动批准:
- 命令必须完全相等(包括所有参数)
- 检测到命令链符号时不自动批准
- 状态存储在 OpenClaw 会话中(会话结束清空)
完整示例场景
场景 1: 选择题 - 项目方向决策
收到的 Payload:
{
"agent_id": "cam-abc123",
"event_type": "waiting_for_input",
"urgency": "HIGH",
"context": {
"extracted_message": "你想要增强现有的 React Todo List 还是从头开始?\n\nA) 增强现有项目 - 在当前代码基础上添加新功能\nB) 从头开始 - 使用最新最佳实践创建全新项目",
"question_fingerprint": "react-todo-enhance-or-fresh",
"message_type": "choice",
"options": ["增强现有项目", "从头开始"],
"risk_level": "LOW"
}
}
发送给用户的消息:
💬 需要你选择
📋 问题:
你想要增强现有的 React Todo List 还是从头开始?
🔢 选项:
A) 增强现有项目 - 在当前代码基础上添加新功能
B) 从头开始 - 使用最新最佳实践创建全新项目
📝 回复字母选择 (A/B)
[cam-abc123]
用户回复: A
执行: cam_agent_send("cam-abc123", "A")
场景 2: 确认题 - 危险命令
收到的 Payload:
{
"agent_id": "cam-xyz789",
"event_type": "permission_request",
"urgency": "HIGH",
"event_data": {
"tool_name": "Bash",
"tool_input": {"command": "rm -rf ./dist && rm -rf ./node_modules"}
},
"context": {
"extracted_message": "清理构建产物和依赖目录,准备全新构建",
"message_type": "confirmation",
"risk_level": "HIGH"
}
}
发送给用户的消息:
⚠️ 请求确认
🔧 操作:
Bash: rm -rf ./dist && rm -rf ./node_modules
💡 上下文:
清理构建产物和依赖目录,准备全新构建
⚡ 风险: 🔴 HIGH
📝 回复 y 允许 / n 拒绝
[cam-xyz789]
用户回复: y
执行: cam_agent_send("cam-xyz789", "y")
场景 3: 开放式问题 - 需要用户输入
收到的 Payload:
{
"agent_id": "cam-def456",
"event_type": "waiting_for_input",
"urgency": "HIGH",
"context": {
"extracted_message": "请提供你的 GitHub Personal Access Token,用于创建 PR",
"message_type": "open_ended",
"risk_level": "MEDIUM"
}
}
发送给用户的消息:
💬 需要你输入
📋 问题:
请提供你的 GitHub Personal Access Token,用于创建 PR
📝 直接回复你的答案
[cam-def456]
用户回复: ghp_xxxxxxxxxxxx
执行: cam_agent_send("cam-def456", "ghp_xxxxxxxxxxxx")
场景 4: AI 提取失败 - Fallback 处理
收到的 Payload:
{
"agent_id": "cam-fail001",
"event_type": "waiting_for_input",
"urgency": "HIGH",
"context": {
"terminal_snapshot": "...\n⏺ 我分析了你的代码结构...\n\n你觉得这个方案怎么样?\n❯ ",
"extracted_message": null,
"risk_level": "MEDIUM"
}
}
发送给用户的消息:
⚠️ 需要你的输入
📋 无法解析具体问题,请查看终端
🖥️ 终端快照:
⏺ 我分析了你的代码结构...
你觉得这个方案怎么样?
❯
📝 查看终端后回复
[cam-fail001]
场景 5: 自动批准 — 不同风险等级对比
| LOW 风险 | MEDIUM 风险 |
|---|
| 示例命令 | cargo test | npm install express |
| 匹配规则 | 白名单命中 | LLM 判断 |
| 是否自动批准 | 是 | 是 |
| 通知用户 | 否(完全静默) | 是(简短通知) |
LOW 风险处理流程:
- 检查白名单 →
cargo test 在白名单中
- 检查参数安全 → 无敏感路径
- 自动批准,执行
cam_agent_send("cam-auto001", "y")
- 不通知用户(LOW 风险静默)
MEDIUM 风险处理流程:
- 检查白名单 →
npm install 不在白名单
- 检查黑名单 → 不在黑名单
- LLM 判断 → MEDIUM 风险,自动批准
- 执行
cam_agent_send("cam-med001", "y")
- 发送简短通知
MEDIUM 风险通知:
✅ 已自动批准: npm install express (MEDIUM)
[cam-med001]
场景 6: Agent 异常退出
收到的 Payload:
{
"agent_id": "cam-exit001",
"event_type": "agent_exited",
"urgency": "HIGH",
"event_data": {
"exit_code": 1,
"reason": "Process terminated unexpectedly"
},
"context": {
"terminal_snapshot": "...\nerror: could not compile `myapp`\n",
"risk_level": "HIGH"
}
}
发送给用户的消息:
❌ Agent 异常退出
退出码: 1
原因: Process terminated unexpectedly
最后输出:
error: could not compile `myapp`
回复 "resume" 恢复会话,或 "logs" 查看完整日志
[cam-exit001]