@2en/clawly-plugins 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/channel.ts +8 -46
- package/cron-hook.ts +3 -9
- package/gateway/plugin-version-management.md +190 -0
- package/package.json +1 -1
package/channel.ts
CHANGED
|
@@ -1,42 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Clawly cron delivery channel —
|
|
3
|
-
* cron jobs using `delivery: { channel: "clawly-cron" }` have a valid target.
|
|
2
|
+
* Clawly cron delivery channel — minimal no-op channel registration.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* With delivery.mode = "none", OpenClaw does not call sendText. This
|
|
5
|
+
* registration exists so that any existing cron jobs referencing
|
|
6
|
+
* channel "clawly-cron" still have a valid target and don't error out.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
|
-
import {$} from 'zx'
|
|
11
9
|
import type {PluginApi} from './index'
|
|
12
|
-
import {sendPushNotification} from './gateway/notification'
|
|
13
|
-
import {isClientOnline} from './gateway/presence'
|
|
14
|
-
|
|
15
|
-
$.verbose = false
|
|
16
|
-
|
|
17
|
-
async function injectAndNotify(text: string, api: PluginApi): Promise<void> {
|
|
18
|
-
try {
|
|
19
|
-
// Resolve main session key from config
|
|
20
|
-
const config = api.pluginConfig as Record<string, unknown> | undefined
|
|
21
|
-
const agentId = typeof config?.agentId === 'string' ? config.agentId : 'clawly'
|
|
22
|
-
const mainKey = typeof config?.mainKey === 'string' ? config.mainKey : 'main'
|
|
23
|
-
const sessionKey = `agent:${agentId}:${mainKey}`
|
|
24
|
-
|
|
25
|
-
const params = JSON.stringify({sessionKey, message: text})
|
|
26
|
-
await $`openclaw gateway call chat.inject --json --params ${params}`
|
|
27
|
-
api.logger.info(`clawly-cron: injected message (${text.length} chars) into ${sessionKey}`)
|
|
28
|
-
|
|
29
|
-
// Push notification if client is offline
|
|
30
|
-
const online = await isClientOnline()
|
|
31
|
-
if (!online) {
|
|
32
|
-
const pushSent = await sendPushNotification({body: text}, api)
|
|
33
|
-
api.logger.info(`clawly-cron: push notification sent=${pushSent}`)
|
|
34
|
-
}
|
|
35
|
-
} catch (err) {
|
|
36
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
37
|
-
api.logger.error(`clawly-cron: failed to inject message — ${msg}`)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
10
|
|
|
41
11
|
export function registerClawlyCronChannel(api: PluginApi) {
|
|
42
12
|
const channelRegistration = {
|
|
@@ -47,7 +17,7 @@ export function registerClawlyCronChannel(api: PluginApi) {
|
|
|
47
17
|
label: 'Clawly Cron',
|
|
48
18
|
selectionLabel: 'Clawly Cron (webchat)',
|
|
49
19
|
docsPath: '',
|
|
50
|
-
blurb: '
|
|
20
|
+
blurb: 'No-op cron delivery channel (delivery.mode = none)',
|
|
51
21
|
},
|
|
52
22
|
capabilities: {chatTypes: ['dm'] as const},
|
|
53
23
|
config: {
|
|
@@ -71,18 +41,10 @@ export function registerClawlyCronChannel(api: PluginApi) {
|
|
|
71
41
|
},
|
|
72
42
|
outbound: {
|
|
73
43
|
deliveryMode: 'direct' as const,
|
|
74
|
-
sendText: async (
|
|
75
|
-
const firstArg = args[0] as Record<string, unknown> | undefined
|
|
76
|
-
const text = typeof firstArg?.text === 'string' ? firstArg.text : ''
|
|
77
|
-
api.logger.info(`clawly-cron sendText: text=${text.length} chars`)
|
|
78
|
-
if (text) {
|
|
79
|
-
// Fire-and-forget — delivery system may not await sendText
|
|
80
|
-
injectAndNotify(text, api).catch(() => {})
|
|
81
|
-
}
|
|
44
|
+
sendText: async () => {
|
|
82
45
|
return {channel: 'clawly-cron', messageId: crypto.randomUUID()}
|
|
83
46
|
},
|
|
84
|
-
sendMedia: async (
|
|
85
|
-
api.logger.info(`clawly-cron sendMedia: ${JSON.stringify(args)}`)
|
|
47
|
+
sendMedia: async () => {
|
|
86
48
|
return {channel: 'clawly-cron', messageId: crypto.randomUUID()}
|
|
87
49
|
},
|
|
88
50
|
},
|
|
@@ -90,5 +52,5 @@ export function registerClawlyCronChannel(api: PluginApi) {
|
|
|
90
52
|
}
|
|
91
53
|
|
|
92
54
|
api.registerChannel(channelRegistration)
|
|
93
|
-
api.logger.info('channel: registered clawly-cron delivery channel')
|
|
55
|
+
api.logger.info('channel: registered clawly-cron delivery channel (no-op)')
|
|
94
56
|
}
|
package/cron-hook.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* before_tool_call hook for cron (action=add) — ensures delivery fields are
|
|
3
3
|
* always set correctly, even when the LLM omits them.
|
|
4
4
|
*
|
|
5
|
-
* Forces: delivery.
|
|
5
|
+
* Forces: delivery.mode = "none" (agent uses message/push tools explicitly)
|
|
6
6
|
* Patches: payload.kind "systemEvent" → "agentTurn"
|
|
7
7
|
*
|
|
8
8
|
* The cron tool name is "cron" (not "cron.create"). The LLM passes
|
|
@@ -21,14 +21,8 @@ function isRecord(v: unknown): v is UnknownRecord {
|
|
|
21
21
|
function patchJob(job: UnknownRecord): UnknownRecord {
|
|
22
22
|
const patched: UnknownRecord = {...job}
|
|
23
23
|
|
|
24
|
-
// Force delivery
|
|
25
|
-
|
|
26
|
-
patched.delivery = {
|
|
27
|
-
...delivery,
|
|
28
|
-
mode: 'announce',
|
|
29
|
-
channel: 'clawly-cron',
|
|
30
|
-
to: 'self',
|
|
31
|
-
}
|
|
24
|
+
// Force delivery.mode = "none" — agent reports via message/push tools explicitly
|
|
25
|
+
patched.delivery = {mode: 'none'}
|
|
32
26
|
|
|
33
27
|
// Patch payload.kind: systemEvent → agentTurn
|
|
34
28
|
if (isRecord(job.payload) && job.payload.kind === 'systemEvent') {
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# 插件版本管理
|
|
2
|
+
|
|
3
|
+
## 产品需求
|
|
4
|
+
|
|
5
|
+
### 背景
|
|
6
|
+
|
|
7
|
+
OpenClaw 插件以 npm 包形式安装在 gateway 的 `extensions/` 目录下。用户需要一种方式来查询已安装插件的版本、检测是否有更新、并执行更新操作——无需手动 SSH 到服务器执行 CLI 命令。
|
|
8
|
+
|
|
9
|
+
### 目标
|
|
10
|
+
|
|
11
|
+
- 通过 Gateway RPC 远程查询任意插件的已安装版本和 npm 最新版本
|
|
12
|
+
- 支持多种更新策略(常规更新、全新安装、强制重装)
|
|
13
|
+
- 更新后可选自动重启 gateway,使新版本立即生效
|
|
14
|
+
|
|
15
|
+
### 功能需求
|
|
16
|
+
|
|
17
|
+
1. **版本查询** — 给定 `pluginId` 和 `npmPkgName`,返回已安装版本、npm 最新版本、所有可用版本列表,以及是否有更新。
|
|
18
|
+
2. **插件更新** — 给定 `pluginId`、`npmPkgName` 和更新策略,执行安装或更新操作。支持指定目标版本和更新后自动重启。
|
|
19
|
+
3. **强制重装** — `force` 策略会备份插件配置、删除扩展目录、全新安装、再恢复配置,解决损坏安装或缓存问题。
|
|
20
|
+
|
|
21
|
+
### 约束
|
|
22
|
+
|
|
23
|
+
- 仅通过 Gateway WebSocket RPC 调用,不暴露 HTTP 端点。
|
|
24
|
+
- 版本比较仅支持标准 semver,预发布版本(如 `1.0.0-beta.1`)不会被标记为可用更新。
|
|
25
|
+
- npm registry 查询结果缓存 5 分钟(LRU,最多 5 个包),避免频繁请求。
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 技术设计
|
|
30
|
+
|
|
31
|
+
### 概览
|
|
32
|
+
|
|
33
|
+
插件版本管理通过 `gateway/plugins.ts` 注册两个 Gateway RPC 方法,分别负责版本查询和更新执行。
|
|
34
|
+
|
|
35
|
+
### 数据流
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Mobile / Agent
|
|
39
|
+
│
|
|
40
|
+
├─ clawly.plugins.version({ pluginId, npmPkgName })
|
|
41
|
+
│ ├─ 读取 extensions/<pluginId>/package.json → 已安装版本
|
|
42
|
+
│ ├─ npm view <npmPkgName> --json → 最新版本 + 所有版本
|
|
43
|
+
│ └─ isUpdateAvailable(current, latest) → 是否有更新
|
|
44
|
+
│
|
|
45
|
+
└─ clawly.plugins.update({ pluginId, npmPkgName, strategy, targetVersion?, restart? })
|
|
46
|
+
├─ strategy=install → openclaw plugins install <pkg>
|
|
47
|
+
├─ strategy=update → openclaw plugins update <pluginId>
|
|
48
|
+
└─ strategy=force → 备份配置 → 删除目录 → 安装 → 恢复配置
|
|
49
|
+
└─ restart=true → openclaw gateway restart
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Gateway RPC 方法
|
|
53
|
+
|
|
54
|
+
| 方法 | 参数 | 返回 |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `clawly.plugins.version` | `{ pluginId, npmPkgName }` | `VersionResult` |
|
|
57
|
+
| `clawly.plugins.update` | `{ pluginId, npmPkgName, strategy, targetVersion?, restart? }` | `UpdateResult` |
|
|
58
|
+
|
|
59
|
+
### 关键类型
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// clawly.plugins.version 返回
|
|
63
|
+
interface VersionResult {
|
|
64
|
+
pluginVersion: string | null // 已安装版本(来自 package.json)
|
|
65
|
+
npmPackageVersion: string | null // 同上(兼容字段)
|
|
66
|
+
latestNpmVersion: string | null // npm registry 最新稳定版
|
|
67
|
+
allNpmVersions: string[] // npm registry 所有已发布版本
|
|
68
|
+
updateAvailable: boolean // 是否有可用更新
|
|
69
|
+
error?: string // 查询错误(如 npm 不可达)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// clawly.plugins.update 返回
|
|
73
|
+
interface UpdateResult {
|
|
74
|
+
ok: boolean // 操作是否成功
|
|
75
|
+
strategy: string // 实际使用的策略
|
|
76
|
+
output?: string // CLI 输出
|
|
77
|
+
restarted?: boolean // 是否已重启 gateway
|
|
78
|
+
error?: string // 失败时的错误信息
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 更新策略
|
|
83
|
+
|
|
84
|
+
| 策略 | 行为 | 适用场景 |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `install` | `openclaw plugins install <pkg>[@version]` | 首次安装或指定版本安装 |
|
|
87
|
+
| `update` | `openclaw plugins update <pluginId>` | 常规更新到最新版 |
|
|
88
|
+
| `force` | 备份配置 → 删除扩展目录 → 全新安装 → 恢复配置 | 安装损坏、缓存问题、降级 |
|
|
89
|
+
|
|
90
|
+
### `force` 策略详细流程
|
|
91
|
+
|
|
92
|
+
1. 读取 `openclaw.json` 中 `plugins.entries.<pluginId>` 的用户配置并保存
|
|
93
|
+
2. 从 `openclaw.json` 删除该插件条目(确保干净安装)
|
|
94
|
+
3. 删除 `extensions/<pluginId>/` 目录
|
|
95
|
+
4. 执行 `openclaw plugins install <pkg>[@version]`
|
|
96
|
+
5. 将保存的用户配置合并回 `openclaw.json`
|
|
97
|
+
|
|
98
|
+
### 版本检测逻辑
|
|
99
|
+
|
|
100
|
+
已安装版本从 `<stateDir>/extensions/<pluginId>/package.json` 读取。npm 最新版本通过 `npm view <pkg> --json` 获取。版本比较使用内置 `isUpdateAvailable(current, latest)`:
|
|
101
|
+
|
|
102
|
+
- 仅比较标准 semver(`major.minor.patch`)
|
|
103
|
+
- `latest` 为预发布版本时返回 `false`(不推荐更新到预发布版)
|
|
104
|
+
- `latest > current` 时返回 `true`
|
|
105
|
+
|
|
106
|
+
### 缓存
|
|
107
|
+
|
|
108
|
+
npm registry 查询结果通过 `LruCache` 缓存:
|
|
109
|
+
|
|
110
|
+
- **容量**: 5 个包
|
|
111
|
+
- **TTL**: 5 分钟
|
|
112
|
+
- 更新操作成功后自动失效对应包的缓存
|
|
113
|
+
|
|
114
|
+
### 状态目录解析
|
|
115
|
+
|
|
116
|
+
`stateDir` 按以下优先级解析:
|
|
117
|
+
|
|
118
|
+
1. `api.runtime.state.resolveStateDir(process.env)`(插件 API 提供)
|
|
119
|
+
2. `OPENCLAW_STATE_DIR` 环境变量
|
|
120
|
+
3. 空字符串(回退,版本查询将返回 `null`)
|
|
121
|
+
|
|
122
|
+
### 调用示例
|
|
123
|
+
|
|
124
|
+
查询版本:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"method": "clawly.plugins.version",
|
|
129
|
+
"params": {
|
|
130
|
+
"pluginId": "clawly-plugins",
|
|
131
|
+
"npmPkgName": "@AISomething/clawly-plugins"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
常规更新:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"method": "clawly.plugins.update",
|
|
141
|
+
"params": {
|
|
142
|
+
"pluginId": "clawly-plugins",
|
|
143
|
+
"npmPkgName": "@AISomething/clawly-plugins",
|
|
144
|
+
"strategy": "update"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
强制重装并重启 gateway:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"method": "clawly.plugins.update",
|
|
154
|
+
"params": {
|
|
155
|
+
"pluginId": "clawly-plugins",
|
|
156
|
+
"npmPkgName": "@AISomething/clawly-plugins",
|
|
157
|
+
"strategy": "force",
|
|
158
|
+
"restart": true
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
安装指定版本:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"method": "clawly.plugins.update",
|
|
168
|
+
"params": {
|
|
169
|
+
"pluginId": "clawly-plugins",
|
|
170
|
+
"npmPkgName": "@AISomething/clawly-plugins",
|
|
171
|
+
"strategy": "install",
|
|
172
|
+
"targetVersion": "1.2.3"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 错误处理
|
|
178
|
+
|
|
179
|
+
- 参数缺失返回 `{ code: "invalid_params" }` 错误。
|
|
180
|
+
- 无效 strategy 返回 `{ code: "invalid_params" }` 错误。
|
|
181
|
+
- npm 查询失败时,版本查询仍返回成功,`error` 字段包含错误信息。
|
|
182
|
+
- 更新操作失败时返回 `{ ok: false, error: "..." }`。
|
|
183
|
+
- `force` 策略在恢复配置失败时仅打印警告日志,不影响整体操作结果。
|
|
184
|
+
|
|
185
|
+
### 不在范围内
|
|
186
|
+
|
|
187
|
+
- 批量更新多个插件。
|
|
188
|
+
- 插件安装/卸载(使用 `clawhub2gateway` 或 CLI)。
|
|
189
|
+
- 自动定时检查更新。
|
|
190
|
+
- 版本回滚历史记录。
|