Skill-B: pre_brief — 会前简报自动发送
定位
本技能是 cron 定时执行的会前简报流程总控。
职责分工:
-
OpenClaw(本次对话/cron 任务)
- 触发本技能 scan 脚本
- 读取 scan 返回的会议列表
- 对每场会议:调用 gitea-routine-report 拉取 Gitea 活动数据
- 作为 AI 生成 ai_overview / ai_suggestion / member_summaries / risk_notes
- 调用 gitea-routine-report 的 render_email.py 渲染 HTML
- 调用本技能 commit 脚本保存文件并更新状态
- 调用 imap-smtp-email 向全体参会人发送简报邮件
-
Skill-B(本技能)
- scan:遍历所有受管仓库,找出需要发简报的会议,计算时间窗口
- commit:将简报存入 Gitea 会议目录,更新 meta.yaml 状态,写日志,返回邮件参数
-
gitea-routine-report
- 只做数据拉取 + HTML 渲染
- 不直接触发其自带邮件发送逻辑(由 OpenClaw 决定发给谁)
-
imap-smtp-email
- 发送最终简报邮件给全体参会人
配置要求
配置文件:~/.config/skill-b-pre-brief/.env
首次使用前运行:
bash setup.sh
严格工作流(每次 cron 触发必须按顺序执行)
第一步:运行 scan,获取待发简报会议列表
node main.js scan
返回 JSON,meetings 字段包含所有需要处理的会议,每条有以下字段:
action:"generate_brief"= 正常发简报;"mark_only"= 改期会议只改状态不发邮件repo:仓库全名,如HKU-AIFusion/dexterous-handmeeting_dir:会议目录名,如2026-04-22-1500topic:会议主题scheduled_time:会议时间(ISO8601)organizer:组织者 Gitea 用户名attendees:参会人 Gitea 用户名列表attendee_emails:参会人邮箱列表since:Gitea 活动统计开始时间(ISO8601)until:Gitea 活动统计结束时间(ISO8601)report_params:传给 gitea-routine-report 的参数{repo, since, until}
只有会议时间落在“开始前 15 分钟到 6 小时”这个窗口内,才会被 scan 命中。
如果 meetings 为空,本次 cron 无需继续执行。
第二步:对 action == "mark_only" 的会议,直接更新状态
对每条 action == "mark_only" 的会议,运行:
node main.js commit \
--repo "HKU-AIFusion/dexterous-hand" \
--meeting-dir "2026-04-22-1500" \
--mark-only
这会直接将 meta.yaml status 改为 brief-sent,不生成简报、不发邮件。
第三步:对 action == "generate_brief" 的会议,逐条生成并发送简报
对每条 action == "generate_brief" 的会议,按以下子步骤处理:
3a. 调用 gitea-routine-report 拉取 Gitea 活动数据
使用 scan 返回的 report_params:
python <gitea-routine-report路径>/scripts/generate_report.py \
--repo "HKU-AIFusion/dexterous-hand" \
--since "2026-04-15" \
--until "2026-04-22"
输出是 JSON 数组,取 [0] 元素作为本次会议仓库的数据。
注意:
since/until日期格式为YYYY-MM-DD,从 scan 返回的report_params.since/report_params.until字段取值(已格式化为此格式)。
3b. OpenClaw 作为 AI 生成纯文字内容
严格按照 gitea-routine-report 的 SKILL.md 第三步格式输出,对上一步的数据生成:
{
"ai_overview": "...",
"ai_suggestion": "...",
"member_summaries": { "成员名": "一句话摘要" },
"risk_notes": "..."
}
规则同 gitea-routine-report:
- 只输出 JSON,不加任何 markdown 代码块标记
member_summaries键名必须与数据中成员用户名完全一致- 严禁编造数据中没有的内容
risk_notes无风险时填空字符串""
3c. 将 AI JSON 保存到临时文件
将上一步 JSON 内容存入 /tmp/ai_brief_{meeting_dir}.json(meeting_dir 中 / 替换为 _):
echo '{"ai_overview": "...", ...}' > /tmp/ai_brief_2026-04-22-1500.json
3d. 调用 gitea-routine-report 渲染 HTML
python -c "
import json, sys, os
sys.path.insert(0, '<gitea-routine-report路径>/scripts')
from render_email import render
data = json.loads(open('/tmp/report_data.json').read())
ai_content = json.loads(open('/tmp/ai_brief_MEETING_DIR.json').read())
repo_data = data[0]
html = render(repo_data, ai_content)
open('/tmp/brief_body_MEETING_DIR.html', 'w').write(html)
print('HTML 已生成,长度:', len(html))
"
注意:
generate_report.py的输出需要先保存到/tmp/report_data.json,再供 render 使用。MEETING_DIR替换为实际目录名(如2026-04-22-1500)。
3e. 调用本技能 commit,保存简报文件并更新状态
node main.js commit \
--repo "HKU-AIFusion/dexterous-hand" \
--meeting-dir "2026-04-22-1500" \
--topic "v2 设计评审" \
--html-file "/tmp/brief_body_2026-04-22-1500.html" \
--attendee-emails "email1@163.com,email2@163.com" \
--since "2026-04-15" \
--until "2026-04-22"
返回 JSON,关键字段:
success:是否成功brief_email.to:收件人邮箱列表brief_email.subject:邮件主题brief_email.html:邮件 HTML 正文
3f. 调用 imap-smtp-email 发送简报邮件
使用 commit 返回的 brief_email 字段,调用 imap-smtp-email 发送。
收件人:brief_email.to(全体参会人邮箱)
主题:brief_email.subject
正文:brief_email.html(HTML 格式)
错误处理规则
| 错误场景 | 处理方式 |
|---|---|
| scan 无结果 | 正常退出,本次 cron 结束 |
| generate_report.py 失败 | 记录错误,跳过本条会议,继续处理下一条 |
| AI 内容生成失败 | 记录错误,跳过本条会议 |
| render_email 失败 | 记录错误,跳过本条会议 |
| commit 写 Gitea 失败 | 记录错误,不发邮件,下次 cron 重试(status 未变,仍为 scheduled) |
| 邮件发送失败 | 记录错误,但 status 已更新为 brief-sent(日志标注 email_failed) |
幂等性说明
- scan 只返回
status == scheduled且不含rescheduled_from的会议 - commit 成功后 status →
brief-sent - 下次 cron 不会再次命中同一场会议
- commit 失败时 status 不变,下次 cron 自动重试