@partme.ai/openclaw-gotify
v2026.5.25-2
Published
OpenClaw Gotify channel plugin — REST delivery, WebSocket stream inbound, bootstrap helpers, and config validation
Maintainers
Readme
OpenClaw Gotify
OpenClaw 插件:Gotify 渠道桥接 — REST API 推送 + WebSocket 流接收 + 多账号会话隔离
@partme.ai/openclaw-gotify 是为 OpenClaw 开发的 Gotify 渠道插件:通过 REST API 发送消息,通过 WebSocket /stream 实时接收消息,并完整支持 Application 和 Client 的 生命周期管理。
📖 简介
OpenClaw Gotify(@partme.ai/openclaw-gotify)基于 Gotify 官方 REST API 和 WebSocket Stream API,将自托管的 Gotify 推送服务器桥接到 OpenClaw Agent 系统。插件按照官方文档使用 defineChannelPluginEntry + ChannelPlugin 实现。
🎯 核心能力
- REST 消息发送 — 通过 Gotify Message API (
POST /message) 发送 Agent 回复 - WebSocket 流接收 — 通过 Gotify Stream API (
GET /stream) 实时接收入站消息 - Application 管理 — 完整的 CRUD:创建、更新、删除、上传图标
- Client 管理 — 完整的 CRUD:创建、更新、删除
- 消息管理 — 获取消息列表(游标分页)、按 ID 删除、批量删除
- 多账号多智能体 —
accounts映射支持多个 Gotify 实例,按dmScope粒度隔离会话 - 会话隔离 — 完全遵循 OpenClaw 全局
session.dmScope配置(main/per-peer/per-channel-peer/per-account-channel-peer) - 幂等去重 — 60 秒窗口内相同账号+消息 ID 不会重复派发
生命周期
- WebSocket 监听器在 Gateway 对 Gotify 渠道执行
startAccount时启动 - 账号级并发锁确保同一 Gotify 实例的请求串行化,防止触发限流
- HTTP
GET /gotify/status、/gotify/health、/gotify/doctor在入口的registerFull中注册 - 会话键粒度遵循 OpenClaw 全局
session.dmScope配置 package.json→openclaw.setupEntry指向dist/setup-entry.js,通过defineSetupPluginEntry导出轻量入口
前置要求
- 已安装 OpenClaw(
>=2026.4.0,见package.json中peerDependencies与openclaw.compat/openclaw.build) - Node.js 22+(与官方 Building plugins 前置要求一致)
- 一个运行中的 Gotify 服务器(v2.x+)
安装
1. 使用 OpenClaw CLI(推荐)
openclaw plugins install @partme.ai/openclaw-gotify最低依赖:@partme.ai/openclaw-message-sdk >= 2026.5.22。
然后在 channels.gotify 中填写配置(见下文)。
2. 使用 npm(手动 / 高级)
npm install @partme.ai/openclaw-gotify再按你所用版本的规则,通过 openclaw.plugin.json、plugins.entries 等将包接入 OpenClaw。
配置
最小配置
{
"channels": {
"gotify": {
"defaultAccount": "default",
"accounts": {
"default": {
"name": "default",
"enabled": true,
"serverUrl": "https://gotify.example.com",
"appToken": "Axxxxxxxxxxxxx",
"clientToken": "Cxxxxxxxxxxxxx",
"defaultPriority": 5,
"dmPolicy": "open",
"allowFrom": ["*"],
"inbound": {
"enabled": true,
"allowedAppId": 1
}
}
}
}
}
}多账号多智能体
{
"channels": {
"gotify": {
"defaultAccount": "default",
"accounts": {
"default": {
"name": "default",
"enabled": true,
"serverUrl": "http://localhost:8080",
"appToken": "ACYOShvtHHH2U69",
"clientToken": "C7ErQjzzeoAXCKg",
"defaultPriority": 5,
"dmPolicy": "open",
"allowFrom": ["*"],
"inbound": {
"enabled": true
}
},
"e2e": {
"name": "e2e",
"enabled": true,
"serverUrl": "http://127.0.0.1:18080",
"appToken": "Aiq5hUNRZLE9ucx",
"clientToken": "CS8dXyptveo_dkm",
"defaultPriority": 5,
"dmPolicy": "open",
"allowFrom": ["*"],
"inbound": {
"enabled": true,
"allowedAppId": 2,
"deleteAfterConsume": false
}
}
}
}
}
}这个写法更明确:
accounts.default表示正常运行账号accounts.e2e表示本地联调用测试账号appToken始终表示 openclaw-gotify 自己用于出站发送 的 Application TokenclientToken表示插件用于 WebSocket/stream入站监听 与管理/查询 API 的 Client Tokeninbound.allowedAppId表示 一个账号只接收一个指定 Application ID 的入站消息
停机期间消息补偿(Backlog Replay)
当 inbound.enabled=true 时,openclaw-gotify 现在要求必须配置 inbound.allowedAppId,并据此在启动阶段执行历史消息补偿:
- 先建立
/stream连接,并把刚到达的实时消息暂存在内存缓冲区 - 调用
GET /application/{allowedAppId}/message?since=<lastSeenMessageId>拉取停机期间的历史消息 - 按消息 ID 升序 一条一条 回放
- 每成功处理一条消息,就持久化推进
lastSeenMessageId - 历史回放结束后,再按顺序处理缓冲区中的实时消息
- 最后切换到正常的
/stream实时处理
这样不会把一批历史消息一次性塞给智能体,而是保持逐条入站、逐条完成的处理方式。
如果本地测试需要再用一个独立的 Gotify Application 来模拟“外部用户发消息”,建议通过测试脚本环境变量(例如 GOTIFY_SENDER_APP_TOKEN)传入。那个 sender token 属于测试编排层,不属于插件运行配置本体。
配置说明
顶级配置(单账号兼容模式)
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| enabled | boolean | true | 是否启用渠道 |
| name | string | — | 账号显示名称 |
| serverUrl | string | — | Gotify 服务器地址(如 https://gotify.example.com) |
| appToken | string | — | 应用 Token,用于发送消息(前缀 A) |
| clientToken | string | — | 客户端 Token,用于接收消息和管理 API(前缀 C) |
| defaultPriority | number | 5 | 默认消息优先级(0–10) |
| defaultAccount | string | — | 多账号模式下的默认账号 ID |
| accounts | object | — | 多账号配置映射 |
inbound — WebSocket 流配置
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| enabled | boolean | false(有 clientToken 时为 true) | 是否启用 WebSocket 流监听 |
| reconnectDelayMs | number | 2000 | 初始重连延迟(毫秒) |
| maxReconnectDelayMs | number | 30000 | 最大重连延迟(指数退避上限) |
| maxReconnectAttempts | number | 10 | 最大重连尝试次数 |
| deleteAfterConsume | boolean | true | 消费即删:入站派发成功后删除原消息,Agent 回复发送成功后删除回复消息 |
消费即删策略
默认 deleteAfterConsume: true(严格策略):只要消息被消费,就从 Gotify 服务端删除。
| 方向 | 触发时机 |
|------|----------|
| 入站 | Agent 整轮回复投递完成后,DELETE 用户发来的原消息(避免先删后答) |
| 出站 | POST /message 成功后立即 DELETE 该回复(手机端先收到推送再清理) |
关闭方式:配置 channels.gotify.inbound.deleteAfterConsume: false。OPENCLAW_TEST_VISIBLE=1 不会跳过插件侧删除,仅影响标准测试 runner 的额外 cleanup 行为。
环境变量声明
以下环境变量在 openclaw.plugin.json 的 channelEnvVars 中声明,供 OpenClaw 的 setup 发现机制在插件加载前通告给用户。插件代码不直接读取 process.env — 所有配置均从 channels.gotify 配置节解析(参见上面的配置章节)。
| 变量 | 用途 |
|------|------|
| GOTIFY_SERVER_URL | Gotify 服务器地址 — 等同于配置 channels.gotify.serverUrl |
| GOTIFY_APP_TOKEN | 应用 Token — 等同于配置 channels.gotify.appToken |
| GOTIFY_CLIENT_TOKEN | 客户端 Token — 等同于配置 channels.gotify.clientToken |
🏗️ 消息处理流程
┌──────────────────────────────────────────────────────────┐
│ Gotify Server │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ Message API │ │ Stream API │ │ App/Client API │ │
│ │ POST /msg │ │ WS /stream │ │ CRUD │ │
│ └──────┬──────┘ └──────┬───────┘ └───────┬────────┘ │
└─────────┼────────────────┼──────────────────┼───────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────┐
│ openclaw-gotify Plugin │
│ │
│ Outbound: mapOutbound → sendGotifyMessage → Message API │
│ Inbound: WebSocket → dedup → dmScope session → Agent │
│ Admin: list/create/update/delete Apps & Clients │
│ Health: GET /health → latency check │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ OpenClaw Gateway │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ Agent │ │ Session │ │ Channel Reply │ │
│ │ Routing │ │ Store │ │ Pipeline │ │
│ └──────────┘ └──────────┘ └──────────────────────┘ │
└──────────────────────────────────────────────────────────┘- Gotify 应用或外部系统发送消息到 Gotify 服务器
- 插件通过 WebSocket
/stream实时接收消息 - 幂等去重(60 秒窗口,SDK
createIdempotencyCache,键${accountId}:${messageId}) - 解析对端标识(
extras.openclaw.peerId→appid→title) - 按
session.dmScope构造会话键 - 路由到对应 Agent 处理
- Agent 回复通过
POST /message(App Token)发送回 Gotify
💬 一来一回对话(Gotify + Control UI)
| 层面 | 行为 |
|------|------|
| Gotify App | 推送通知渠道;默认消费即删(入站用户消息在 Agent 回复发送成功后删除,出站回复在 POST 成功后删除) |
| OpenClaw Control UI | 完整对话历史 保存在 Session Store;多轮共用同一 sessionKey(同一 peerId / appid) |
| 幂等 | SDK @partme.ai/openclaw-message-sdk → createIdempotencyCache;仅按 messageId 去重,同一对端连续多条新消息不会互相屏蔽 |
| 出站 | sendGotifyMessageWithDeliveryRetry:失败时自动再试 1 次 |
在 Control UI 查看测试 / 真实对话:
- 打开 Gateway(如
http://127.0.0.1:18789)→ Sessions - 不要选默认
agent:main:main;选择gotify: e2e-user或 sessionKey:agent:main:gotify:default:direct:<peerId>(dmScope=per-account-channel-peer时,见下表) - Gotify 手机上可能看不到历史(已删),但 UI 里可顺畅多轮一来一回
手动连发 3 条验证:用 e2e-user App Token 连发 3 次 POST /message,应在同一 session 看到 3 轮 user/agent 记录。
🧭 dmScope 会话隔离
插件完全遵循 OpenClaw 全局 session.dmScope 配置,无需额外自定义隔离配置。
| dmScope | 会话键格式 | 隔离粒度 |
|---------|-----------|----------|
| main | agent:<agentId>:main | 所有消息共享同一会话 |
| per-peer | agent:<agentId>:direct:<peerId> | 按对端隔离 |
| per-channel-peer | agent:<agentId>:gotify:direct:<peerId> | 按渠道+对端隔离 |
| per-account-channel-peer | agent:<agentId>:gotify:<accountId>:direct:<peerId> | 按账号+渠道+对端隔离(推荐多账号) |
对端标识解析优先级:extras.openclaw.peerId → appid → title → "gotify"
🧪 测试
诚实评估:单元测试 ≠ Control UI 成功
| 层级 | 命令 | 证明什么 | 不能证明什么 |
|------|------|----------|------------------|
| L0 单元 | pnpm test(vitest,~91 条) | 配置解析、mock 派发、去重逻辑 | Gateway 运行、WS 入站、Control UI 有消息 |
| L1 标准 | pnpm test:standard | Gotify 往返 +(默认)chat.history 抽检 | 用户肉眼在 UI 点对了会话 |
| UI 验收门禁 | pnpm test:ui-gate | chat.history 含 user 消息 = UI 同源 transcript | Agent LLM 一定成功(user 消息必须先出现) |
发布 / 验收必须 pnpm test:ui-gate 通过。 仅 vitest 全绿不算成功。
# 1. 构建并重启 Gateway(加载最新插件)
pnpm build && openclaw gateway restart
# 2. UI 验收门禁(必过)
GOTIFY_APP_TOKEN=AK-MvdcbyFOfBmQ GOTIFY_CLIENT_TOKEN=C7ErQjzzeoAXCKg pnpm test:ui-gate
# 3. 单元测试(CI,mock)
pnpm test
# 4. 标准 + Agent 往返(可选,含 chat.history 尾检)
GOTIFY_APP_TOKEN=... GOTIFY_CLIENT_TOKEN=... pnpm test:standardControl UI 里查看测试 / E2E 对话
测试消息经 Gotify REST 入站后,会话键由 session.dmScope 决定(本仓库常见为 per-account-channel-peer → agent:main:gotify:default:direct:4),不会出现在默认 agent:main:main。
- 打开
http://127.0.0.1:18789→ Sessions → 选gotify: e2e-user或agent:main:gotify:default:direct:4 - 勿用
channels.gotify.appToken做入站测试(与出站同 appid 会被 echo 过滤);用 e2e-user App Token(appid=4) - 勿选已废弃的
agent:main:gotify:direct:4(旧 dmScope 残留、transcript 文件缺失时 UI 会显示 0 条消息) - 默认消费即删(入站 + 出站回复),Gotify App 消息列表通常为空;需保留消息时设
channels.gotify.inbound.deleteAfterConsume: false OPENCLAW_TEST_VISIBLE=1 pnpm test:standard:仅跳过 runner cleanup,不关闭插件删除
# 单元测试
npm test
# 类型检查
npm run typecheck
# 构建
npm run build🤖 GitHub Actions
| 工作流 | 触发方式 | 作用 |
|--------|----------|------|
| ci.yml | push / PR 到 main | 安装、类型检查、构建、测试 |
| release.yml | v* 标签 | 构建、测试并发布 npm 包 |
📦 发版
npm version patch
git push origin main --follow-tags📁 项目结构
openclaw-gotify/
├── src/
│ ├── index.ts # defineChannelPluginEntry + registerFull(HTTP 路由)
│ ├── setup-entry.ts # defineSetupPluginEntry 轻量入口
│ ├── channel.ts # ChannelPlugin 定义 + dispatchInboundMessage
│ ├── gotify-api.ts # Gotify REST API 全量封装(Message/Application/Client/Health)
│ ├── config.ts # 配置解析(多账号合并、默认值补齐)
│ ├── channel-config.ts # ChannelConfigSchema(Zod + JSON Schema)
│ ├── peer-resolver.ts # Gotify peerId 解析(供 resolveAgentRoute)
│ ├── inbound-access.ts # 入站 DM 策略(SDK channel-ingress-runtime)
│ ├── message-mapper.ts # 入站/出站消息映射
│ ├── outbound.ts # ChannelOutboundAdapter
│ ├── ws-listener.ts # WebSocket 流监听器(指数退避重连)
│ ├── setup.ts # Bootstrap + Doctor
│ ├── runtime.ts # 运行时状态管理
│ ├── config-wizard.ts # 配置向导
│ ├── gotify-client.ts # GotifyClient 类封装
│ └── types.ts # 类型定义
├── scripts/
│ ├── test-client.ts # 手动 doctor/bootstrap 测试客户端
│ ├── functional-test.ts # 完整 API 功能测试(需真实 Gotify 服务器)
│ ├── e2e-agent-test.ts # 端到端 Agent 通信 + chat.history 验收
│ └── ui-transcript-gate-test.ts # Control UI 验收门禁(发布必过)
├── openclaw.plugin.json # 插件清单
├── package.json
└── README.md / README.en.md📚 OpenClaw 官方文档
📚 Gotify 参考
企业级可靠性
| 项 | 行为 |
|----|------|
| 分级 | 协议限制需文档约束 |
| 入站 | WebSocket 流;无 broker ACK |
| 恢复 | backlog cursor + 重连 replay |
| 幂等 | 60s 内存 dedup(多实例需业务幂等) |
| 自消费 | extras.openclaw.outbound 过滤 |
❓ 常见问题
必须同时配置 appToken 和 clientToken 吗?
如果只需要发送消息(出站),仅配置 appToken 即可。如果需要实时接收消息(入站 WebSocket 流),则需要 clientToken。对于完整的双向通信,两者都需要。
WebSocket 连接断开后会自动重连吗?
是的。插件使用指数退避重连策略:初始延迟 reconnectDelayMs(默认 2000ms),每次失败翻倍,上限 maxReconnectDelayMs(默认 30000ms),最多重试 maxReconnectAttempts(默认 10)次。
如何实现多智能体隔离?
通过 session.dmScope 配置会话隔离粒度。推荐多账号场景使用 "per-account-channel-peer",这样不同 Gotify 应用的消息分配到独立的会话。
Gotify 消息如何路由到不同的 Agent?
入站消息通过 extras.openclaw.peerId 字段指定对端标识,或根据发送方 Application ID 自动识别。然后通过 OpenClaw 的 Agent 路由配置确定目标 Agent。
📄 开源协议
本项目采用 MIT License 协议。
🙏 致谢
如果这个项目对你有帮助,请给我们一个 ⭐️
Made with ❤️ by PartMe
消息格式指南
Gotify 使用原生 Message API 与 WebSocket Stream JSON,不直接消费通用 MQ wire envelope。Gotify 如何映射到 UnifiedMessage,以及其它队列插件的标准消息契约,见 OpenClaw 队列消息格式指南。
