| name | miloco-notify |
| description | 需要主动触达家庭成员时使用——感知告警、危险预警、任务到期、定时播报、设备反馈、关怀提醒等一切「主动告知 / 通知 / 触达」的场景。负责决策:通知给谁 → 走哪个渠道(音箱 TTS / IM / 米家推送)→ 写什么文案 → 交付执行。只要不是被动回答用户当前的提问、而是系统或 agent 要主动找人,就加载本 skill。用户想配置 / 绑定通知接收渠道时也走本 skill(见 references/channel-config.md)。 |
| metadata | {"author":"miloco","version":"3.1","date":"2026-06-16","openclaw":{"requires":{"bins":["miloco-cli"]}}} |
miloco-notify
凡是 agent 主动找人(而非回答当前对话)的场景,都经由本 skill 决策「给谁 → 走哪 → 说什么」,再交对应渠道执行。
何时激活
典型触发源:感知引擎 suggestion、任务 fire、cron 定时、巡检关怀、含「异常 / 危险 / 提醒 / 通知」语义的系统事件。
重要原则
- 渠道优先级:TTS 现场 > IM 备选 > 米家推送兜底。 TTS 是当面说话,最自然、最及时;IM 是 TTS 够不着时的备选;米家推送适用于系统通知+危险警报。
- TTS 的前置条件:目标房间有在线音箱,且有人能听到(在场或在邻近房间)。 没音箱、或确认全家无人,对着空房间播报没意义 → 降级 IM / 推送。但在场无法确认、或需引人前往处置时,宁可播报也别漏(见下「特例」与「异常处理」)。
- 一次只用一个音箱。 多个音箱在线时只挑目标房间那一个播报;目标房间无在线音箱时,按「工作流」和「异常处理」的级别规则决定是退选其他音箱还是放弃 TTS;多台齐鸣会此起彼伏,制造混乱。
工作流
解析 → 分级 → 选人 → 选渠道 → 写文案 → 交付执行
1. 解析
从触发源提取:通知内容、接收人线索、事发房间、紧迫度、是否危险。
2. 分级
| 级别 | 含义 | 例子 |
|---|
| L1 危险 | 已发生的人身 / 财产险情 | 老人跌倒、明火、燃气泄漏、破门闯入 |
| L2 预警 | 危险前兆 / 监护对象异常(还来得及干预) | 儿童靠近危险物、烟雾初判、水浸初判 |
| L3 日常 | 提醒 / 督促 / 信息播报 | 吃药、久坐、天气、任务完成、陌生人逗留 |
L1 / L2 = 危险类 → 走「危险通知」;L3 = 普通类 → 走「普通通知」。
陌生人 ≠ 入侵:门口 / 院内出现陌生人,默认按 L3 平和告知,不要替家人脑补成入侵。只有伴随明确威胁行为(破门、翻墙、持械、暴力撬锁、翻箱倒柜等)才升 L1。
3. 选人
触发源已指定接收人 → 直接用。未指定 → miloco-cli person list 取成员,按下表判定:
| 情况 | 接收人 |
|---|
| L1 危险 | 全家 |
| L2 / L3 当事人是能自理的成人 | 当事人本人 |
| L2 / L3 当事人是儿童 / 老人 | 当事人 + 成人监护人 |
| L2 / L3 涉及家庭公共事务 | 全家 |
监护人:儿童 → role 含「爸爸 / 妈妈 / 父亲 / 母亲」的成员;老人 → role 含「儿子 / 女儿」的成员。
身份不确定时:
- 感知给了 person_id 且匹配 person list → 用真名称呼;
- 标注「陌生人」/ 未识别 / 无 person_id → 文案只说「有人」,绝不猜身份。
4. 选渠道
选择通知渠道前需要确认 2 件事:
- 谁在家、在哪个房间 —— 来自触发源的感知上下文(拿不到时见「异常处理」)。
- 那个房间有没有在线音箱 —— 交 miloco-devices skill 查
category=speaker 的在线设备,本 skill 不直接拼设备命令。
危险通知(L1 / L2)—— 目标:让能处理的人立刻注意到
家里有人:
- 事发房间有在线音箱 → 事发房间 TTS(现场播报,引起家人注意)+ IM + 米家推送(三渠道全覆盖,确保不漏)
- 事发房间没在线音箱 → 改用其他成人 / 监护人所在房间的音箱 TTS + IM + 米家推送
全家无人:
- 没人能听见,TTS 无意义 → IM + 米家推送
普通通知(L3)—— 目标:把信息平静地递到对的人
- 接收人所在房间有在线音箱 → 该房间 TTS
- 接收人不在家,或所在房间没在线音箱(TTS 不可达)→ IM
特例:需要引导人前往某房间处理的事(厨房干烧 / 溢锅但厨房没人)——即使该房间当前无人,也优先用该房间音箱 TTS,目的是让家里听得到的人注意去处理。
5. 写文案 —— 你是家人,不是监控系统
三渠道共享一套核心表述,只按长度 / 形式微调,不要刻意改写成三套话。统一语气:
- 温暖自然,像家人之间的关心
- 客观陈述事实,不催促、不指挥、不反复追问——用户看到事实自会判断(L1 紧急除外,可用「马上来」这类引导)
- 不用「检测到 / 系统发现 / 感知引擎 / 根据数据」这类机器话,用户不关心底层
- 已识别成员用称呼(爸爸、奶奶、豆豆);未识别只说「有人 / 来了个人」,不猜身份
- 对儿童温和引导,别命令、别吓到孩子
| 渠道 | 长度 | 形式 |
|---|
| TTS | 一句话、尽量短 | 口语、现场播报——长了打断不了、听着烦 |
| IM | 1~2 句 | 客观陈述,可补一点细节 |
| 米家推送 | 精炼一句 | 系统通知口吻 |
示例(同一事件、三渠道):
| 场景 | TTS | IM | 推送 |
|---|
| 老人跌倒 L1 | 奶奶摔倒了,家里人马上来 | 奶奶在客厅摔倒了,15 秒没起来 | 奶奶在客厅摔倒,15 秒未起身 |
| 儿童危险 L2 | 豆豆,那个不能玩,先放回去 | 豆豆在厨房拿了把菜刀,旁边没大人 | 豆豆在厨房拿菜刀,无成人在场 |
| 陌生人逗留 L3 | 门口来了个人,不认识 | 门口来了个陌生人,待了好几分钟了 | 门口有陌生人逗留 |
| 吃药提醒 L3 | 该吃药了 | 该吃降压药了 | 该吃降压药了 |
6. 交付执行
| 渠道 | 怎么发 |
|---|
| TTS | 交给 miloco-devices skill:在目标房间定位 category=speaker 的在线设备并播报。本 skill 不直接拼设备命令。 |
| IM | 调 agent 工具 miloco_im_push(message="<文案>")——这是 agent 内置工具(function call),直接调用;不是 miloco-cli 命令,别用 shell / miloco-cli 去执行。返回 needsBind=true → 见 references/channel-config.md,立即带 bindHint 重发(这是继续操作的信号,不是回复给用户的内容)。 |
| 米家推送 | 命令行 miloco-cli notify push --text "<文案>"(这条才是 CLI 命令,与上面的 agent 工具区分)。 |
异常处理
| 异常 | 处理 |
|---|
| 接收人识别不出 | 按「家庭公共」处理,发全家 |
| 事发 / 目标房间识别不出 | 跳过 TTS,走 IM |
| 目标房间无在线音箱 / TTS 失败 | L1/L2:改用其他有人房间的音箱(全家无在线音箱才放弃 TTS),IM + 米家推送照常必发;L3:降级 IM |
IM 返回 needsBind=true | 见 references/channel-config.md,带 bindHint 重发 |
IM 返回 ok=false(非 needsBind) | 降级米家推送 |
| 通知工具报错 / 调用失败 | 绝不对同一条通知反复重推(会造成死循环、疯狂骚扰用户)。规则:单次发送 + 至多一次降级(TTS→IM→米家推送);needsBind=true 补 bindHint 重发是唯一允许的重试(只一次)。其余失败按级别降级一次;若已无可降级渠道、或降级仍失败 → 立即结束本轮通知、不再重试,交由下个触发周期重新决策。 |
| 在家 / 在场、人在哪个房间无法确认 | 宁可播报别漏。TTS 选哪台:优先事发房间,无在线音箱 → 任选一台在线音箱,全家都无在线音箱才放弃 TTS。危险类(L1/L2)无论 TTS 是否发出,IM + 米家推送照常必发;普通类(L3)放弃 TTS 后才降级 IM |
注意事项
- 主动触达统一入口——凡 agent 主动找人都走本 skill;多条通知逐条独立决策,不合并。
- 一次只用一个音箱、且是目标房间那一个——不多台齐鸣;TTS 要短,长了打断不了惹人烦。
- 渠道分层:危险类(L1/L2)三渠道全覆盖;普通类(L3)按可达性单选 TTS 或 IM;米家推送只用于系统通知(兼作危险类和 IM 通知失败兜底)。
- 系统触发的通知直接执行,不主动追问。
- TTS 执行交 miloco-devices;渠道配置见 references/channel-config.md——本 skill 只管决策,不重复造轮子。
- 绝不循环推送 / 发完即止。 一条通知本轮只走一遍决策链、只发一次;发出(或已尽力降级)后立即结束,不要回头重查 profile / 重读 skill / 反复自证「是不是只发了一次」——那会绕回来再次触发推送,形成死循环。工具调用失败也不自主重试(
needsBind 补 bindHint 重发除外)。