@yoooclaw/phone-notifications
v1.19.1
Published
后台同步手机通知数据到本地存储,供 Agent 查询消费
Readme
@yoooclaw/phone-notifications
OpenClaw / QClaw 手机通知同步插件:接收手机通知并写入本地 JSON 文件系统,供 Agent 查询和消费。
工作方式
插件支持两种接入模式:
- Gateway Native(默认)
- 手机端通过 gateway 已有连接调用
notifications.push - 插件通过
registerGatewayMethod接收params.items
- HTTP 备选模式
- 开启
httpEnabled=true后,注册POST /notifications - 通过
Authorization: Bearer <apiKey>鉴权
存储设计
通知按以下目录结构写入(JSON):
<workspace>/notifications/
├── 2026-03-02.json
├── 2026-03-03.json
└── 2026-03-04.json补充规则:
workspaceDir可用时,根目录是<workspace>/notificationsworkspaceDir不可用或不可写时,回退到<stateDir>/plugins/phone-notifications/notifications- 通知写入
YYYY-MM-DD.json的 JSON 数组(append-only) - 通知条目基础字段为
appName、title、content、timestamp(ISO 8601 含时区) - 飞书消息落库时会对齐微信语义:群聊写成
title=群名、content=发送人: 正文;私聊保持title=发送人 - 部分应用会补充结构化字段;例如飞书消息会额外写入
senderName、conversationType(group/private)、conversationName - 同一天内按
notification-id去重(使用内部索引文件) - 手机通知永久保存,不做过期清理
上下文来源清单(磁盘读取契约)
常驻应用(如 ClawPilot Desktop)需要对通知/录音/网页做增量索引。让它们周期性起
ntf 子进程既贵又脆(Node 运行时、构建产物、PATH 任一环出问题,本地已有数据就
索引不到),而这三类数据本来就已经原子落盘了。因此插件在三个 store 全部 init
成功之后、发布 storage ready 之前,原子写出一份版本化清单,把最终实际采用的
目录发布出去:
<stateDir>/plugins/phone-notifications/context-source.json{
"schemaVersion": 1,
"pluginVersion": "1.13.2",
"generatedAt": "2026-08-13T10:00:00.000Z",
"sources": {
"notifications": { "formatVersion": 1, "root": "/abs/path/notifications", "commit": "daily-json" },
"recordings": { "formatVersion": 1, "root": "/abs/path/recordings", "commit": "index.json" },
"webPages": { "formatVersion": 1, "root": "/abs/path/web-pages", "commit": "index.json" }
}
}约定:
schemaVersion管清单结构;每个来源的formatVersion独立管对应磁盘数据格式, 改一类数据的格式不牵连另外两类。消费方遇到不认识的高版本必须报「不兼容」, 不能按旧格式猜着解析。root一律是规范化后的绝对路径,且是插件运行时最终采用的那一个——某个 store 因为 stateDir 不可写回退到 workspace 时,写进清单的就是回退后的路径。 消费方只消费,不自行推导目录。commit是提交点语义:daily-json——通知以单日YYYY-MM-DD.json整文件为提交单元,新增通知时整份 原子重写,所以不能把新增等同于「追加的最后一行」。index.json——录音/网页先写产物(音频、转写、摘要、正文),再原子替换index.json。消费方只有读到新的index.json才算这组文件已提交;直接盯着 正文文件的 mtime 会读到写了一半的文档。
- 某个来源不可用时,清单里写
{ "unavailableReason": "..." },绝不写一个不存在 的路径冒充可用。 - 清单只含定位与版本信息,不含通知正文、转录或网页正文。
- 插件停止时不删清单:它描述的是仍然躺在磁盘上的数据,插件没在跑的时候消费方 照样要能据此完成索引。存储目录变化时覆盖清单。
- 清单写失败只记告警,不影响通知/录音/网页服务本身。
ntf CLI 继续保留,服务 Agent/Skill 的按需查询和用户手动排障——它和这条常驻索引
链路是两条独立的轨道。
图片同步
App 通过 images.sync(Gateway Native)或 POST /images(HTTP 备选)下发图片元数据,图片二进制走 OSS;插件在后台下载原图落盘。
接入要点:
- 请求体为
{ imageId, image };image至少包含oss_image_url和created_at,可附带mime_type / width / height / size_bytes / source_app / caption - 同步即时返回
status=syncing,下载在后台进行;同一imageId+ 同 URL 已synced时直接去重返回 - 单张下载大小上限默认 20MB,可通过配置
image.maxBytes调整 - 存储结构(与录音同级,优先
stateDir,回退workspace):
<storageDir>/images/
├── files/ # 下载的原图,<imageId>.<ext>(扩展名由 mime_type 推断)
└── index.json # 元数据索引({ images: [] })Agent 侧通过 CLI 查询消费:
ntf image list [--status <s>] [--app <app>]列出图片ntf image status <id>查看单张详情ntf image path <id>打印本地文件绝对路径(仅synced可用)ntf image storage-path打印图片存储目录
网页同步数据查询
浏览器扩展通过 webPages.save(Gateway Native)或 POST /web-pages
下发已经清洗为 Markdown 的网页正文;插件将正文、可选的 HTML 存档和索引保存到本地。
存储结构(与录音、图片同级,优先 stateDir,回退 workspace):
<storageDir>/web-pages/
├── files/
│ ├── <urlHash前8位>-<slug>.md # YAML frontmatter + 清洗后的正文
│ └── <urlHash前8位>-<slug>.html # 可选的原始 HTML 存档
└── index.json # 元数据索引({ pages: [] })Agent 侧通过 CLI 查询消费:
ntf synced-web-page list [--from <ISO_TIME>] [--to <ISO_TIME>]按收藏时间倒序列出本地已同步网页,可按capturedAt时间范围过滤ntf synced-web-page path <urlHash>打印 Markdown 文件绝对路径,支持至少 8 位的唯一哈希前缀ntf synced-web-page search <keyword> [--limit <n>]搜索本地已同步网页的标题、URL 和 Markdown 正文,不访问互联网ntf synced-web-page storage-path打印网页存储目录
list 的 --from 包含起点,--to 不包含终点;时间参数使用带明确时区的
ISO 8601 格式,过滤字段为 capturedAt。
统一上下文查询 Skill
Agent 侧所有只读查询统一由 skills/context-query 路由。主 SKILL.md
只负责识别数据源和查询意图,通知、录音、网页、图片的具体执行规则放在
references/ 中按需加载。新增上下文来源时增加 reference 和 CLI 适配器即可,
不再为每种来源增加独立 Query Skill。
录音深加工统一由 skills/recordings-process 路由。会议纪要、录音翻译、
思维导图、采访整理和实体提取共享录音定位流程,再按用户需要加载对应的
references/ 产物规则,不再各占一个 Skill。通知写入记忆等写入流程仍保留独立
Skill。
前置要求
- Node.js >= 22.12.0
- OpenClaw >= 2026.3.28,或兼容其插件 ABI 的 QClaw 宿主
可选:长录音音频缓存 / 解码工具
新链路中 ASR 和总结由 App 下发,插件不再推荐本地 Whisper 转写。若需要在本机播放、导出或处理硬件 OGG/Opus 音频,可按需安装:
# opus-tools(推荐):解码硬件录音的 OGG/Opus 格式
# 若已安装 ffmpeg 则可跳过
brew install opus-tools说明
- 硬件录音以 Opus 编码、OGG 容器格式传输。
opusdec(来自 opus-tools)或ffmpeg任意一个可用即可完成格式转换。asr.mode = "local"已停用,仅保留枚举兼容旧客户端。
宿主兼容性
- OpenClaw:可直接使用全局
openclawCLI 安装、更新、验证和执行插件命令。 - QClaw:插件 ABI 与 OpenClaw 兼容,但宿主目录、配置入口和 CLI 管理方式不同。QClaw 桌面应用通常不提供全局
openclaw命令,而是由应用内置 wrapper 调用 OpenClaw runtime。 - 一键安装脚本会自动探测宿主状态目录,优先读取
~/.qclaw/qclaw.json;如果你的变体产品使用~/.qclow/qclaw.json,脚本和插件也会兼容。 - QClaw 下不支持本脚本自动执行
gateway restart/ Tailscale 配置。安装完成后如插件未自动生效,请重启 QClaw 桌面应用。
安装
通过 OpenClaw CLI 安装(OpenClaw)
openclaw plugins install @yoooclaw/phone-notifications更新
openclaw plugins update @yoooclaw/phone-notifications指定安装版本时,--version <版本号> 优先于安装脚本内置的下载地址;需要自定义包地址时使用 --tgz-url(优先级最高)。同时指定版本和包地址时,包内版本必须匹配,否则在替换旧插件前终止。此规则适用于 Bash 与 PowerShell 安装器。
只检查插件更新(不下载、安装或重启网关):
openclaw ntf update --check返回 current(运行版本)、latest(候选版本)、hasUpdate 和 updated: false。macOS/Linux 按当前环境和账号查询灰度服务,未命中时 latest 为 null;Windows 沿用 OSS 渠道检查。--check 与 --force 同用也不会安装。不加 --check 则保持原有检查并安装行为。
插件内的 plugin.update / plugin-update 和 ntf update 复用仓库已有的
scripts/install-core.mjs,不再由内置 updater 直接替换目录:
- 标准 OpenClaw:探测
openclaw plugins install --help,通过plugins info --json校验当前安装归属,再调用原生安装器。默认归档使用 tgz;新版托管 npm 安装使用npm-pack:<绝对路径>,并跟随宿主返回的新版本目录,不再假设都在extensions下。 - 检测到 5.28 的旧扫描安装协议、旧 CLI 不支持覆盖安装、未找到 CLI,或使用其他宿主:沿用已有安装脚本,先准备生产依赖,再备份并替换插件。不添加危险代码放行参数;已废弃为 no-op 的旧扫描参数不触发旧版分流。但检测到新版 SQLite 状态/托管 npm 目录时不允许此回退,须先恢复宿主 CLI;原生安装下的链接或未明确归属的自定义来源也不自动迁移。
- 原生能力探测异常或原生安装失败:报告失败,不会绕过宿主策略再次尝试直接覆盖;Nix 模式拒绝自更新。
- 原生成功后不再自行写配置、manifest、依赖或执行
npm link;安装登记(含新版 SQLite)、$include及启用状态由宿主管理。不直接授予 hooks/工具权限。旧脚本回退路径仍沿用既有兼容配置。 - 新版更新同一已启用插件时,核对完整 manifest 契约、包入口及 bin 不变后,自动衔接既有能力授权;兼容旧包补声明已存在的
ntf/phone-notificationsCLI 入口。新增能力或无法核实则返回CAPABILITY_CONSENT_REQUIRED,用户审阅宿主报告后可明确执行ntf update --accept-capabilities。不绕过宿主安装策略。 - 走原生路径的旧宿主覆盖安装可能重新启用已禁用插件;此时在安装前停止,要求升级宿主或由用户明确启用后重试。
Gateway 内触发更新时,独立进程执行安装并保存结果,不阻塞 Relay 心跳。
安装日志位于 <stateDir>/extensions/.phone-notifications-update/<updateId>/install.log,
结果位于该根目录的 last-update.json,不会随 npm 版本目录切换而丢失;子目录的 install-result.json 保存实际安装目录和原生错误。原生安装使用的 tgz 保存在
<stateDir>/plugins/phone-notifications/install-artifacts/,避免宿主登记的来源路径随临时文件清理而失效。
Windows 沿用手动停止/启动模式:先停止 Gateway,等待 last-update.json 中
status=succeeded,再启动,避免安装尚未完成就加载插件。
升级完成后会校验 systemd gateway 是否真正激活并展示实时连接状态(1.16.2 起),及早发现「升级已完成但服务未启动」。每次自动 / 手动更新的开始、成功、失败及运行中版本心跳会持久化上报到服务端(1.17.0 起),网络异常时本地排队并自动重试。
注意:此策略只在运行包含本改动的版本后生效,旧版 updater 不会被远程自动改写。
首次部署应使用带 --update 的新版安装脚本或宿主原生安装命令;不要把旧版“已解包”当成新版完整安装流程已经执行。
隔离联调脚本:node scripts/installer/smoke-native-update.mjs /绝对路径/openclaw。它创建无网络行为的测试插件和独立 state/config,只验证安装链路,不启动 Gateway,也不代表真实业务运行验收。新版覆盖归档、npm 版本目录、授权拒绝/接受、禁用状态和 $include;旧版覆盖原生归档更新。
通过一键脚本安装(推荐给 QClaw,备选给 OpenClaw)
适用于无法使用 npm 源的环境,或宿主不是标准 OpenClaw CLI 的场景。脚本从 CDN 获取插件包并自动注册。
curl -fsSL https://artifact.yoooclaw.com/plugin/install.sh | bash& ([scriptblock]::Create((irm https://artifact.yoooclaw.com/plugin/install.ps1)))PowerShell 入口用于 OpenClaw 与 EClaw;ArkClaw / JvsClaw 只提供 macOS / Linux shell 入口。
EClaw Windows 入口:
& ([scriptblock]::Create((irm https://artifact.yoooclaw.com/plugin/install-eclaw.ps1)))脚本会自动探测宿主:
- OpenClaw:默认使用
~/.openclaw - ArkClaw:暂与 OpenClaw 使用相同安装路径和重启策略,入口会写入
PHONE_NOTIFICATIONS_CLAW_PROFILE=arkclaw - QClaw:优先读取
~/.qclaw/qclaw.json - JvsClaw:使用专用入口,默认写入可写目录
/home/admin/.openclaw - EClaw(Ecloud AI Assist):使用专用入口,默认写入
~/.eclaw - 兼容变体:如存在
~/.qclow/qclaw.json也会识别
显式渠道入口:
curl -fsSL https://artifact.yoooclaw.com/plugin/install-openclaw.sh | bash
curl -fsSL https://artifact.yoooclaw.com/plugin/install-arkclaw.sh | bash
curl -fsSL https://artifact.yoooclaw.com/plugin/install-jvsclaw.sh | bash
curl -fsSL https://artifact.yoooclaw.com/plugin/install-eclaw.sh | bashJvsClaw 环境下不要写只读的 ~/.openclaw 基线目录,使用:
curl -fsSL https://artifact.yoooclaw.com/plugin/install-jvsclaw.sh | bashJvsClaw 宿主可能把 JVSCLAW_STATE_DIR 指向只读目录(例如 /opt/jvs-claw)。
插件状态默认统一写入 /home/admin/.openclaw;如需覆盖可写目录,使用
PHONE_NOTIFICATIONS_STATE_DIR;如需单独覆盖配置文件,使用
PHONE_NOTIFICATIONS_CONFIG_PATH。不要改写宿主路径变量:
curl -fsSL https://artifact.yoooclaw.com/plugin/install-jvsclaw.sh |
PHONE_NOTIFICATIONS_STATE_DIR=/custom/writable/openclaw bash指定版本安装
curl -fsSL https://artifact.yoooclaw.com/plugin/install.sh | bash -s -- --version 1.2.3& ([scriptblock]::Create((irm https://artifact.yoooclaw.com/plugin/install.ps1))) --version 1.2.3带 API Key 安装
安装时同时写入灯控鉴权 API Key(写入 <stateDir>/credentials.json):
curl -fsSL https://artifact.yoooclaw.com/plugin/install.sh | bash -s -- --api-key ock_xxx& ([scriptblock]::Create((irm https://artifact.yoooclaw.com/plugin/install.ps1))) --api-key ock_xxx使用本地包安装
bash install.sh --tgz-url ./yoooclaw-phone-notifications-1.2.3.tgz全部选项
| 选项 | 说明 | 默认值 |
|---|---|---|
| --version <ver> | 指定版本号 | 自动获取最新版本 |
| --tgz-url <url\|path> | 直接指定插件包地址(跳过版本检测) | - |
| --target-dir <path> | 安装目录 | <stateDir>/extensions/phone-notifications |
| --state-dir <path> | 宿主状态目录 | 自动探测 |
| --config-path <path> | 宿主配置文件路径 | <stateDir>/openclaw.json |
| --channel <openclaw\|arkclaw\|jvsclaw\|eclaw> | 写入运行渠道 | 渠道脚本自动传入;直接运行核心时不写入,默认 openclaw |
| --host-cli <cmd> | 宿主 CLI 命令名 | openclaw |
| --api-key <apiKey> | 灯控鉴权 API Key | - |
| --token <token> | 兼容旧参数,等价于 --api-key | - |
| --lang <zh-CN\|zh-TW\|en-US> | 安装提示与插件运行时输出(CLI 输出、错误信息、skill 回复)使用的语言;等价环境变量 PHONE_NOTIFICATIONS_LANG | zh-CN |
脚本做了什么
- 自动探测宿主状态目录和配置文件路径,并提前检查安装目录、配置文件、凭据文件的写入权限
- 从 CDN 下载最新版本的插件 tgz 包(或使用你指定的版本/地址)
- 解压并验证包完整性(检查
package.json、入口文件可加载) - 备份旧版本(如有),原子替换到
<stateDir>/extensions/phone-notifications,并通过 npm 安装package.json中声明的生产依赖(继承当前终端的 npm/代理配置) - 写入
<configPath>配置:plugins.entries— 启用插件、合并用户配置。根据实际宿主的配置 schema 处理hooks.allowConversationAccess:支持时为提示词 Hook 补充授权(保留显式false),不支持时清理遗留字段;无法确认时保留现有值、不新增该字段。App 会话管理使用独立的 Gateway RPC 权限。tools.allow/tools.alsoAllow与agents.main.tools.*— 根据插件contracts.tools自动放行工具- 若宿主已启用
plugins.allow白名单,会自动补入phone-notifications,确保插件能被宿主加载 - 清理该插件旧的
plugins.load.paths/ 旧的plugins.installs安装记录(该字段已被宿主废弃,插件不再读写),避免继续命中之前通过openclaw plugins install或--link保留的副本
- 如传入
--api-key/--token,写入<stateDir>/credentials.json - OpenClaw 下尝试执行
gateway restart,并验证ntf/phone-notifications命令是否可用;JvsClaw 下触发gateway restart后跳过即时 CLI 自检,避免平台 RPC 恢复期间误报;QClaw 下不会主动重启宿主进程 - 安装失败时自动回滚到旧版本
如果你的宿主显式配置了 plugins.deny,且其中包含 phone-notifications,安装脚本会中止并提示先移除 denylist 项,不会写入一个无法加载的安装结果。
验证安装
OpenClaw
以下命令适用于带全局 openclaw CLI 的标准 OpenClaw 宿主:
ls ~/.openclaw/extensions/phone-notifications
~/.openclaw/plugins主要是运行时数据目录或旧插件目录;这里没有phone-notifications不代表安装缺失。安装脚本的目标目录是~/.openclaw/extensions/phone-notifications。
openclaw ntf --helpopenclaw ntf --version如果环境里已有其他插件占用了 ntf,可改用不冲突的别名:
openclaw phone-notifications --helpopenclaw phone-notifications -vQClaw
- QClaw 通常没有全局
openclaw命令。 - 插件安装完成后,如未自动生效,请先重启 QClaw 桌面应用。
- 如需执行 OpenClaw CLI 子命令,请使用 QClaw 自带的 wrapper;命令语义与本文档中的
openclaw ...子命令保持一致,但入口由 QClaw 提供。
配置
以下命令以 OpenClaw 为例。QClaw 下请通过其自带 wrapper 执行同样的 OpenClaw 子命令。
安装脚本会自动写入必要的插件注册配置。如需手动调整:
# 可选:忽略特定 app
openclaw config set plugins.entries.phone-notifications.config.ignoredApps '["com.android.systemui"]'配置项说明:
ignoredApps?: string[]relay.heartbeatSec?: number:Relay 心跳发送间隔,默认10秒relay.heartbeatTimeoutSec?: number:多久没有收到 Relay 入站数据后判定断线,默认90秒relay.reconnectBackoffMs?: number:断线后的基础重连退避,默认2000毫秒
录音结果同步
当前版本由 App / 云端完成 ASR 和总结,再通过 recordings.result.write 把结果与音频 OSS 地址写入插件。旧的 recordings.sync、recordings.asr.init、recordings.retranscribe 和插件侧 ASR 已移除。
Gateway WebSocket:recordings.result.write
WebSocket 方法会同步写入请求中提供的转写和总结文件,然后立即响应;OSS 音频在后台下载,进度和最终结果通过 recording.status 事件推送。
可选参数 ossUrl 是最终音频的 OSS 地址。传入时会覆盖同一录音先前的音频来源;不传时只写入转写/总结。
{
"method": "recordings.result.write",
"params": {
"recordingId": "ios-rec-20260609-203000",
"ossUrl": "https://bucket.oss.example.com/recordings/audio/final.mp3",
"transcript": {
"generatedAt": "2026-06-09T20:25:00+08:00",
"source": {
"provider": "model-proxy",
"taskId": "asr-task-xxx",
"requestId": "req-xxx",
"status": "SUCCEEDED"
},
"title": "产品方案讨论",
"category": "meeting",
"brief": "讨论 App 写入转录和总结的新链路。",
"text": "完整转写正文...",
"segments": [
{
"text": "这一段是转写内容。",
"startMs": 0,
"endMs": 4200,
"speakerId": 1
}
]
},
"summary": {
"format": "markdown",
"markdown": "# 总结\n\n- ASR 和总结由 App 通过 result.write 写入。\n"
}
}
}私有上传任务字段:ossTaskId
recordings.result.write 在 params 顶层接受可选字符串 ossTaskId,值为上传服务返回的 taskId。新私有上传链路应传入;旧请求可省略。传入时必须为非空字符串(最多 256 字符),并同时提供音频 URL;不使用 ASR taskId 或 OSS Multipart uploadId。链接刷新和下述可选回显已实现。
{
"recordingId": "ios-rec-20260609-203000",
"ossTaskId": "<上传服务返回的 taskId>",
"ossUrl": "https://bucket.oss.example.com/recordings/audio/final.mp3",
"transcript": {
"source": { "provider": "model-proxy", "taskId": "asr-task-xxx" },
"text": "完整转写正文..."
}
}ossTaskId是上传业务任务 ID;transcript.source.taskId仍是 ASR 任务 ID,二者不可混用。ossTaskId用于刷新下载链接,并作为现有同步结果的附加字段;不参与去重或音频替换。- 保留原有复用规则:同一
recordingId存在非空本地音频时直接复用,URL或任务 ID变化也不重下;本地音频缺失时才下载。 - 现有响应、
recording.status事件和recordings.status/list查询附带可选ossTaskId,无值时省略。App 仍通过recordingId关联录音,旧 App 可忽略字段;不新增独立回执接口。回显不代表下载成功,也不证明复用的本地音频来自该上传任务。 - 下载返回 403 时,携带插件 API Key 请求
/api/device/file/plugin/download-url,请求体为{ "taskId": "<ossTaskId>" },最多刷新一次并重试。404 不刷新,刷新失败或再次 403 时结束。不向 OSS 发送插件凭证。 - 当前 URL与配套
ossTaskId保存到索引,重启恢复下载时仍可刷新;旧请求未传任务 ID时不自动刷新。保留本次调用内 Range 续传,不增加跨进程断点。 - 自动刷新已实现,但真实 App
RECORDING任务的服务端访问尚待联调验证。当前已验证测试环境插件接口鉴权,以及模拟请求的链接刷新和断点续传。 ossUrl使用纯 URL 字符串。现有transcript/summary校验与受理、音频落盘状态的区别不变。
HTTP:POST /recordings(已停用)
该路由保留并返回 410 Gone,不写入结果、不下载音频。包括 Tailscale 在内的直连客户端统一使用 Gateway WebSocket 方法 recordings.result.write。
写入响应表示已受理;客户端通过 recording.status 事件或 recordings.status 查询确认 audio_status=downloaded 且 audioFile 非空后,再标记同步完成。部分宿主会过滤直连事件,因此必须保留状态查询兜底。
历史接口(当前版本已移除)
下面的内容只用于理解旧客户端协议;当前版本不再注册这些 Gateway 方法。
录音转写不再依赖插件侧 openclaw.json 持久配置,而是由客户端在请求里显式传入 asr 参数,插件保持无状态执行。
| asr.mode | 说明 | 额外依赖 |
|---|---|---|
| "api" | 云端 model-proxy 长录音转写 | 请求级 asr.api.apiKey,未传则回退插件本地 API Key |
| "local" | 已停用,保留枚举兼容旧客户端 | — |
| 未传 asr | 关闭转写,仅下载保存音频 | — |
初始化接口
客户端在正式转写前可先调用 recordings.asr.init:
mode = "api":校验请求级或插件本地 API Key 是否可用,并返回当前环境的长录音 submit-task 端点mode = "local":已停用,调用会返回错误
示例:
{
"method": "recordings.asr.init",
"params": {
"asr": {
"mode": "api",
"api": {
"language": "zh",
"enableNormalization": true
}
}
}
}在 recordings.sync 中指定转写方式
仅同步录音,不触发插件侧转写:
{
"method": "recordings.sync",
"params": {
"recordingId": "2026-03-23_14-32",
"recording": {
"name": "会议录音",
"duration_sec": 120,
"duration_display": "2m 0s",
"file_size_display": "1 MB",
"created_at": "2026-03-23T14:32:00+08:00",
"oss_audio_url": "https://bucket.oss.example.com/recordings/audio/2026-03-23_14-32.mp3",
"markers": [],
"transfer_status": "syncing_openclaw"
}
}
}云端 API(model-proxy 长录音):
{
"method": "recordings.sync",
"params": {
"recordingId": "2026-03-23_14-32",
"recording": {
"name": "会议录音",
"duration_sec": 120,
"duration_display": "2m 0s",
"file_size_display": "1 MB",
"created_at": "2026-03-23T14:32:00+08:00",
"oss_audio_url": "https://bucket.oss.example.com/recordings/audio/2026-03-23_14-32.mp3",
"markers": [],
"transfer_status": "syncing_openclaw"
},
"asr": {
"mode": "api",
"api": {
"language": "zh",
"enableNormalization": true
}
}
}
}手动重新触发转写
当录音状态为 synced、transcribe_failed 或 transcribed 时,客户端可通过 Gateway Native 主动调用 recordings.retranscribe。
- 当前接口必须再次传入
asr - HTTP
POST /recordings已停用,返回 410 - 接口会立即返回,实际转写进度应结合
recording.status广播或recordings.status查询确认
{
"method": "recordings.retranscribe",
"params": {
"recordingId": "2026-03-23_14-32",
"asr": {
"mode": "api",
"api": {
"language": "zh"
}
}
}
}转写结果存储
<stateDir>/plugins/phone-notifications/recordings/
├── audio/
│ └── 2026-03-31_14-32.ogg # 原始录音(OGG/Opus)
├── transcript-data/
│ └── 2026-03-31_14-32.json # 结构化转写 JSON
├── transcripts/
│ └── 2026-03-31_14-32_与王总沟通产品方向.md # 转写文本 + 关键点
├── summaries/
│ └── 2026033114_与王总沟通产品方向_2026-03-31_14-32.md # 总结 Markdown
│ # 名字是「本地时间 YYYYMMDDHH + 标题 + 录音 ID」,
│ # 按文件名排序即按时间排序;权威路径始终看
│ # index.json 的 summaryFile,不要自行拼接
└── index.json # 录音状态索引数据协议
Gateway Native:notifications.push
{
"type": "req",
"id": "n_abc",
"method": "notifications.push",
"params": {
"items": [
{
"id": "n_001",
"app": "WeChat",
"title": "张三",
"body": "在吗?",
"timestamp": "2026-03-02T08:30:00+08:00",
"category": "message",
"metadata": { "chatId": "c1" }
}
]
}
}说明:
id用于同一天内去重(写入.ids索引),不会作为通知正文字段落盘。app是手机端上报字段;插件落盘时会标准化为appName。
Gateway Native:通知总结进度与结果
用户在 App 里通过普通对话说“总结未读通知”时,Agent 会调用插件工具 notifications_summary。插件在工具执行过程中会主动广播 notification.summary.status,完成后广播 notification.summary.result,App 不需要把入口改成直接 Gateway RPC 也能展示进度与最终总结。
notification.summary.status 示例:
{
"jobId": "sum_20260614031000_ab12cd34",
"phase": "summarizing",
"status": "running",
"message": "正在整理通知摘要:2/5 个分片",
"doneChunks": 2,
"totalChunks": 5,
"percent": 40
}notification.summary.result 示例:
{
"jobId": "sum_20260614031000_ab12cd34",
"status": "complete",
"ok": true,
"message": "通知总结已完成",
"title": "未读通知总结",
"markdown": "# 未读通知总结\n\n共整理 700 条通知..."
}如果 App 需要绕过 Agent、直接发起通知总结并展示进度,也可以调用:
{
"type": "req",
"id": "sum_abc",
"method": "notifications.summary.start",
"params": {
"limit": 1000,
"chunkSize": 150,
"from": "2026-06-13T09:00:00+08:00"
}
}接口会立即返回:
{
"ok": true,
"jobId": "sum_20260614031000_ab12cd34",
"status": "queued",
"event": "notification.summary.status",
"resultEvent": "notification.summary.result"
}随后插件通过 notification.summary.status 广播阶段事件,phase 可能为 queued、scanning、snapshot_created、summarizing、merging、complete、failed。App 可用 message 直接展示进度,也可用 doneChunks/totalChunks/percent 做进度条。总结完成后,插件会通过 notification.summary.result 广播可直接展示的 markdown。若错过事件,可用 notifications.summary.status 或 notifications.summary.result 传入 jobId 查询当前状态和结果。
HTTP 备选:POST /notifications
POST /notifications
Authorization: Bearer ntf-xxxxxxxxxxxx
Content-Type: application/json{
"notifications": [
{
"id": "n_001",
"app": "SMS",
"title": "京东快递",
"body": "包裹已发出",
"timestamp": "2026-03-02T08:30:00+08:00"
}
]
}响应示例:
{ "ok": true, "ingested": 1 }CLI 命令
以下示例均以 OpenClaw 全局 CLI 为例。QClaw 下请将 openclaw 替换为 QClaw 提供的 OpenClaw wrapper。
插件注册了 openclaw ntf 命令组,并提供 openclaw phone-notifications 作为无冲突别名,供 Agent 直接消费。所有命令输出 JSON。
认证管理
# 设置用户 API Key(灯效下发等接口需要)
openclaw ntf auth set-api-key "ock_xxx"
# 查看当前认证状态
openclaw ntf auth show
# 清除认证信息
openclaw ntf auth clear兼容旧命令:openclaw ntf auth set-token <token>,效果等同于 set-api-key。
通知查询
# 查询最近通知(默认按时间倒序)
openclaw ntf search
# 查询指定应用的通知
openclaw ntf search --app wechat --from 2026-03-01T00:00:00+08:00 --to 2026-03-09T23:59:59+08:00
# 按关键词搜索
openclaw ntf search --keyword "开会" --limit 20
# 按发送人过滤
openclaw ntf search --sender "张三" --from 2026-03-01T00:00:00+08:00
# 仅看飞书群聊消息
openclaw ntf search --app Feishu --conversation-type group
# 生成轻量通知摘要输入,适合 Agent 总结大量通知
openclaw ntf summary --limit 700
# 创建并运行分片总结任务,适合 1000+ 条通知
openclaw ntf summary-job create --limit 3000 --chunk-size 150 --max-content 120
openclaw ntf summary-job run <jobId> --max-chunks 30 --include-result
openclaw ntf summary-job status <jobId>
openclaw ntf summary-job result <jobId>通知统计
# 全维度统计
openclaw ntf stats
# 按应用维度统计
openclaw ntf stats --dim app --from 2026-03-01
# 按时段维度统计
openclaw ntf stats --dim hour --from 2026-03-01 --to 2026-03-09通知同步(记忆系统)
# 扫描当天未同步通知(默认只扫描本地当天)
openclaw ntf sync scan
# 扫描指定日期或 checkpoint 之后的所有未同步通知
openclaw ntf sync scan --date 2026-03-09
openclaw ntf sync scan --from-date 2026-03-02 --to-date 2026-03-09
openclaw ntf sync scan --all
# 获取指定日期未处理通知(单次最多 300 条)
openclaw ntf sync fetch --date 2026-03-09
# 标记当前批次同步完成;若 fetch 返回 hasMore=true,继续重复 fetch/commit
openclaw ntf sync commit --date 2026-03-09
# 迭代器写法:自动定位下一个未处理的日期/批次,直接返回可执行的 commitCommand
openclaw ntf sync next
openclaw ntf sync next --all
openclaw ntf sync next --from-date 2026-03-02 --to-date 2026-03-09通知监控任务
# 创建监控任务
openclaw ntf monitor create boss-alert \
--description "监控老板微信消息" \
--match-rules '{"appName":"wechat","senderKeywords":["张总"]}' \
--schedule "*/30 * * * *"
# 列出监控任务
openclaw ntf monitor list
# 查看监控任务详情
openclaw ntf monitor show boss-alert
# 启用/暂停监控任务
openclaw ntf monitor enable boss-alert
openclaw ntf monitor disable boss-alert
# 删除监控任务
openclaw ntf monitor delete boss-alert --yes灯效规则
灯效规则工具调用独立 Notification Intelligence Agent 的插件侧 API:
/api/plugin/notification-intelligence/light-rules。鉴权只使用
X-Api-Key-Id,不调用 App 端 JWT 接口,也不调用
preview / evaluate / import。
# 启用一次性灯效及云端灯效规则工具
openclaw ntf light setup
# 调试命令:直接发送灯效指令到硬件设备
openclaw ntf light send \
--segments '[{"mode":"wave","duration_s":4,"brightness":192,"color":{"r":255,"g":0,"b":0}}]'
# 呼吸灯效
openclaw ntf light send \
--segments '[{"mode":"breath","duration_s":6,"brightness":128,"color":{"r":0,"g":255,"b":0}}]'
# 只在带屏幕的二代硬件上显示文字(不传 --segments)
openclaw ntf light send --title "会议提醒" --reason "15:00 产品评审"只传 --title / --reason 时按纯文字屏幕展示处理:下发一段 8 秒显式关灯段、不循环(可能关掉当前灯效)。段内省略的 brightness 默认 128、color 默认白色,显式写出的非法值仍按严格校验处理。
数据迁移(1.16.0 本地包 / 1.18.0 云端中转)
使用 openclaw-data-transfer Skill 或 ntf transfer,在两个环境间搬运通知、录音转写、摘要和收藏网页。源数据保留,目标冲突不覆盖;音频默认不带,记忆、配置和凭据不迁移。
# 目标插件运行后,可先导出能力文件带回源端预检
ntf transfer capabilities --out ./target-capabilities.json
# 源端预览并创建明文本地包;输出路径必须不存在
ntf transfer export --dry-run
ntf transfer export --out ./transfer-package.tar.gz --target-capabilities ./target-capabilities.json
# 用户自行携带这一个文件到目标
ntf transfer import --file ./transfer-package.tar.gz --dry-run
# 使用预览返回的两个 ID
ntf transfer import --local <localTransferId> --plan <planId>
# 失败或部分完成后,本地继续
ntf transfer import --local <localTransferId> --plan <planId> --resume--out 以 .tar.gz/.tgz 结尾时生成单个 gzip 压缩包(1.18.0 起),其他路径仍生成包目录;import --file 两种都接受。目标端低于 1.18.0 时只能导入包目录,带 --target-capabilities 导出 tar.gz 会提前报 ARCHIVE_UNSUPPORTED_BY_TARGET。压缩包只是打包,内容仍是明文。与 yoooclaw CLI 的 yoooclaw transfer 包格式互通。
--with-audio、--with-images、--with-html 选择附件;--include 选择数据类型,--from/--to 使用含时区时间戳(左闭右开)。
1.18.0 起支持 OSS 云端中转(迁移 Skill 默认使用),使用插件 channel API Key 和 /file/plugin 接口:
# 导出并上传 tar.gz,返回 taskId 和可复制到目标 Agent 的导入话术
ntf transfer export --via oss
# 接收端使用同账号凭证,自动获取地址、下载并导入
ntf transfer import --task <taskId>
# 只预览,不导入
ntf transfer import --task <taskId> --dry-run云端迁移包由服务端统一维护,在上传完成 24 小时后自动删除,与导入结果无关。插件不调用云端删除接口,也不提供删除命令。导入成功仍会清理本地 staging 副本;部分成功或失败保留本地副本和报告供重试。迁移回复会醒目提醒:⚠️ 迁移数据包含大量隐私信息,请妥善保管任务 ID 和数据包,避免向他人分享或泄露。
上传复用阿里云 SDK 分片续传,下载复用插件 Range 下载器;仅支持本次命令内恢复,不支持重启后续传。客户端限制单包 5 GiB,默认不保留临时压缩包(用 --out package.tar.gz 可保留)。创建任务每天最多 3 次,不自动重试创建;STS 过期时停止,不自动重建任务。上传确认失败可用错误中的 taskId/objectKey 执行 ntf transfer complete --task <taskId> --object-key <objectKey>。导出只交付任务 ID 和导入话术,下载地址在命令内部使用,不进入用户消息或日志;默认云端文件保留 24 小时。
导入经本机鉴权的插件进程执行。目标插件必须运行;不直接修改在线存储。历史通知默认跳过自动记忆消费,本地待消费通知不受影响;所有使用该存储的 ntf sync 消费者需升级到 1.16.0。暂存包与报告位于目标状态目录的 plugins/phone-notifications/transfers/,部分结果可从本地重试,处理完成后用户可删除对应任务目录。该目录含明文数据,勿当作可公开分享的日志。
AI TODO 待办管理
提供 ai_todo_list、ai_todo_get、ai_todo_create、ai_todo_update、ai_todo_delete 五个原生 Agent 工具,复用 App 待办服务和插件 API Key。完成和恢复通过 ai_todo_update 的 isDone 字段操作。
同时提供 ntf todo list/get/create/update/delete 命令,与原生工具共用同一个 Client。完成和恢复使用 ntf todo update <id> --is-done true|false。
接口、CLI 用法、时间语义、组合更新的部分成功处理和待联调前提见 AI TODO Agent 接入说明。
媒体生成与结果交付(1.18.0)
media-generate Skill 支持文生图、图生图 / 图片编辑、文生视频、参考图生视频、首尾帧视频,以及积分估算和视频任务查询。参考素材、尺寸与时长校验与 CLI 的 yoooclaw-media-generate 对齐:每次新请求都先确认规格、估价并取得本次授权才生成;视频为 2–30 秒的整数时长,任务自动轮询最长 20 分钟,沿用原进程等待结果;生成结果按批次直接交付并显示标题编号,回复中的媒体链接会按工具真实输出校正。
图片、视频与迁移话术统一使用原生工具 result_deliver 交付(替代 media_deliver)。网页会话中,ntf transfer export --via oss --delivery-reference 保存真实任务 ID,仅返回交付编号;工具校验会话并在本轮结束后发送隐私提醒与可复制话术。导入仍使用原命令。未新增迁移原生工具;旧工具白名单需将 media_deliver 更新为 result_deliver。
迁移交付编号从 [[transfer:1]] 开始,在当前插件状态目录内持续递增;重启不重置,导出失败可能跳号。编号仅用于本机 result_deliver,不能替代发给目标 Agent 的真实任务 ID。
开发
pnpm install
pnpm run build # tsc 编译
pnpm test # vitest 测试目录结构
src/
├── types.ts # 类型定义
├── index.ts # 插件入口:service + gateway method + http route + tool + cli
├── logger.ts # 日志模块
├── recording/
│ ├── handler.ts # recordings.sync 核心处理流程
│ ├── result-writer.ts # recordings.result.write 写入转录/总结
│ ├── asr.ts # Legacy ASR 调度器(api;local 已停用)
│ ├── whisper-local.ts# Legacy 本地 Whisper 兼容模块
│ ├── downloader.ts # 从 OSS 下载音频文件
│ ├── storage.ts # 录音本地存储管理(index.json)
│ └── state-machine.ts# 录音状态机
├── notification/
│ ├── storage.ts # 通知文件系统存储引擎
│ └── ...
├── light/ # 灯效控制
├── light-protocol.ts # 灯效 ANCS 协议编码
└── cli/
├── index.ts # CLI 注册汇总入口
├── helpers.ts # 共享工具(路径解析、日期处理、JSON 输出)
├── auth.ts # auth 子命令
├── ntf-search.ts # search 子命令
├── ntf-stats.ts # stats 子命令
├── ntf-sync.ts # sync 子命令
├── ntf-monitor.ts # monitor 子命令
└── light-send.ts # light send 子命令发布与运维(CI/CD)
发布采用 release branch flow:master 只保存已发布的稳定基线,开发/测试/beta/正式发布都在 release/** 分支完成。
版本发布
在 release/** 分支上执行版本命令(要求工作区干净、本地与 origin/<当前 release 分支> 完全一致):
bun run release:patch # 预发布转正 或 稳定版 patch+1
bun run release:minor
bun run release:beta # 递增 beta.N命令会提交 package 版本号、打附注 tag 并推送,触发 release.yml。
发布流水线(Artifact Promotion 制品晋级)
按「一次构建、同字节晋级」分三个 job 串行执行(当前无人工审批关卡,全自动):
- build:校验 tag 在
release/**上、tag 与 package.json 版本一致,构建一次并bun pm pack,算出SHA256SUMS,通过actions/upload-artifact传给后续 job。此 job 不挂 environment。 - deploy-test(environment
test):不重新构建,校验 sha256 后把同一份 tgz 上传到测试 OSS 稳定渠道,重新生成测试安装脚本,发飞书「RC 已上测试 OSS」。 - deploy-prod(environment
production):deploy-test 成功后自动继续,下载同一份产物、再次 sha256 校验,原样上传线上 OSS(含latest/beta渠道标记),重新生成线上安装脚本,npm publish现成 tarball(仅稳定版,不重新构建),创建 GitHub Release(直接挂这份 tgz),发飞书「已上线」。
environment: test / production 仅用于部署轨迹追踪与未来按需加保护(当前两者 protection_rules 均为空,不形成关卡)。beta 走同一条流水线、跳过 npm publish。若日后想恢复上线审批,在 production environment 开启 Required reviewers 即可,无需改 workflow。
安装脚本(
install*.sh/install.ps1/install-core.mjs)是环境相关的薄胶水层,不参与晋级,每个环境各自重新生成;只有 tgz 走同字节晋级。部署时安装脚本会同时归档到v{版本}/installer/,作为回滚恢复源。
回滚
回滚是把渠道指针(latest/beta)重指到已存在、不可变的 v{版本}/ 目录,不重新构建。GitHub → Actions → Rollback OSS Channel → Run workflow,填入 version(含 - 自动识别 beta 渠道)与 environment(test/production):
- 脚本先 HEAD 校验目标 tgz 存在,再重指渠道标记,并从
v{版本}/installer/恢复安装脚本。归档缺失(早于本机制上线的历史版本)时只回退 tgz 指针并显式告警,不静默跳过。 - 当前
test/production回滚均免审批(环境无保护规则)。environment声明保留,便于日后需要时单独给生产回滚加 Required reviewers。
不自动回滚的部分(手动 SOP):
- npm:无法安全 unpublish,对问题版本
npm deprecate '@yoooclaw/phone-notifications@<bad>' '请安装上一版'。OSS 是主要分发路径。 - GitHub Release:不自动删除,手动编辑标注「已回滚」,保留追溯。
CI/CD 配置(GitHub 仓库设置)
- Secrets 单一来源(build-once 前提):构建期烤入
dist的 secrets(LIGHT_APP_KEY/LIGHT_TEMPLATE_ID/APP_NAME_MAP_URL/OPENCLAW_HOST_DEVELOPMENT/OPENCLAW_HOST_TEST/OPENCLAW_HOST_PRODUCTION)配置为 repo-level secrets(已就绪:当前全部 secret 均在 repo 级,test/productionenvironment 无任何 environment 级覆盖)。不要在 environment 级重复配置这些键,否则 build-once 会静默失效(构建字节取决于 build job 挂的 environment)。 - 无审批关卡:
test/productionenvironment 当前protection_rules为空,不形成审批。如需恢复上线审批,给productionenvironment 加 Required reviewers 即可,无需改 workflow。 - OSS / npm 凭据(
OSS_*/NPM_TOKEN)走 repo 级 secrets;测试 OSS 共享OSS_ACCESS_KEY_ID/OSS_ACCESS_KEY_SECRET,测试 bucket / region / 域名在 workflow 内以明文常量内置。 - 备注:
APP_NAME_MAP_URL目前未配置为 secret,构建时解析为空字符串(与改造前一致);如需启用应用名映射,补上该 repo 级 secret。
License
MIT
