@yoooclaw/phone-notifications
v1.12.7
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去重(使用内部索引文件) - 手机通知永久保存,不做过期清理
图片同步
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打印图片存储目录
前置要求
- 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通过一键脚本安装(推荐给 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> | 写入运行渠道 | 渠道脚本自动传入;直接运行核心时不写入,默认 openclaw |
| --host-cli <cmd> | 宿主 CLI 命令名 | openclaw |
| --api-key <apiKey> | 灯控鉴权 API Key | - |
| --token <token> | 兼容旧参数,等价于 --api-key | - |
脚本做了什么
- 自动探测宿主状态目录和配置文件路径,并提前检查安装目录、配置文件、凭据文件的写入权限
- 从 CDN 下载最新版本的插件 tgz 包(或使用你指定的版本/地址)
- 解压并验证包完整性(检查
package.json、入口文件可加载) - 备份旧版本(如有),原子替换到
<stateDir>/extensions/phone-notifications - 写入
<configPath>配置:plugins.entries— 启用插件,合并用户配置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毫秒
长录音 & ASR 配置
插件保持原有录音同步入口不变:App 仍通过 recordings.sync 下发录音元数据和 oss_audio_url,音频二进制仍走 OSS。新版 App 如果已经在 App / 云端完成 ASR 和总结,可额外调用 recordings.result.write 写入转录文本和总结;旧版插件没有这个方法时,recordings.sync 仍可正常工作。
写入转录文本和总结(可选)
新版客户端推荐调用顺序:
- 录音完成后上传音频到 OSS。
- 调用原有
recordings.sync,只同步recordingId、录音元数据和oss_audio_url;为了兼容旧插件,这个 WS 方法保持不变。 - App 调云端转写 / 总结接口,拿到 ASR 结构化结果和总结 Markdown。
- 如果插件支持
recordings.result.write,调用该方法写入转录文本和总结。 - 如果旧插件返回 method-not-found 或连接层不支持该方法,App 保留本地转录/总结即可;音频同步不受影响。
recordings.result.write 主要写文本结果,不新增 HTTP route。它写入的 transcript-data/、transcripts/、summaries/ 会覆盖旧 HTTP / 插件 ASR 链路生成的文件;反过来,后续 HTTP / 插件 ASR 重新生成结果时,也可以覆盖它写入的文件。
可选参数 ossUrl:App 侧如果产出了最终音频(如剪辑、降噪后的版本),可在 params.ossUrl 传入音频 OSS 地址。插件会按 recordings.sync 相同的下载逻辑在后台拉取,并覆盖之前 recordings.sync 下载的本地音频文件;下载完成或失败都会推送 recording.status 事件。该字段不影响文本结果写入,响应不等待下载完成。
{
"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"
}
}
}Legacy:插件侧 ASR
旧客户端仍可使用 recordings.sync 同步 OSS 音频,并通过请求级 asr 触发插件侧转写。该链路仅用于兼容,不推荐新客户端继续接入。
录音转写不再依赖插件侧 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,
"file_size_bytes": 1024000,
"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,
"file_size_bytes": 1024000,
"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仅支持同步录音,不支持重新转写 - 接口会立即返回,实际转写进度应结合
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/
│ └── 2026-03-31_14-32.md # App 下发或 ASR 结果中的总结 Markdown
└── 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通知监控任务
# 创建监控任务
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}}]'开发
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
