rol-websocket-channel 1.5.2 → 1.5.5
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/{MQTT-API 5-6.md → MQTT-API System.md } +112 -1
- package/SDK-CHANNEL-TODO.md +6 -0
- package/dist/index.js +69 -15
- package/dist/src/admin/methods/mem9.js +10 -0
- package/dist/src/admin/methods/pairing.js +11 -2
- package/dist/src/admin/methods/system.js +2 -2
- package/index.ts +79 -15
- package/openclaw.plugin.json +7 -7
- package/package.json +1 -1
- package/src/admin/methods/mem9.ts +10 -0
- package/src/admin/methods/pairing.ts +14 -4
- package/src/admin/methods/system.ts +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# OpenClaw MQTT API 文档
|
|
1
|
+
# OpenClaw MQTT API 文档 (System 更新接口)
|
|
2
2
|
|
|
3
3
|
## 消息格式
|
|
4
4
|
|
|
@@ -1797,3 +1797,114 @@ MQTT 请求示例:
|
|
|
1797
1797
|
- `category === "document"` 时,可以作为文档展示或下载。
|
|
1798
1798
|
- `storageStatus === "local_only"` 时,先调 `artifactsEnsureUploaded` 获取可访问的 `downloadUrl`。
|
|
1799
1799
|
- `storageStatus === "uploaded"` 且 `fileUrl` 存在时,可以直接使用 `fileUrl` 展示或下载。
|
|
1800
|
+
|
|
1801
|
+
---
|
|
1802
|
+
|
|
1803
|
+
## System 更新接口
|
|
1804
|
+
|
|
1805
|
+
这两个接口用于 App 按钮触发本机 OpenClaw 或当前插件更新。请求通过 MQTT 发送到当前已配对设备,插件会在本机执行对应的 `openclaw` 命令。
|
|
1806
|
+
|
|
1807
|
+
### openclawUpdate
|
|
1808
|
+
|
|
1809
|
+
作用:升级本机 OpenClaw。
|
|
1810
|
+
|
|
1811
|
+
实际执行命令:
|
|
1812
|
+
|
|
1813
|
+
```bash
|
|
1814
|
+
openclaw update
|
|
1815
|
+
```
|
|
1816
|
+
|
|
1817
|
+
MQTT 请求示例:
|
|
1818
|
+
|
|
1819
|
+
```json
|
|
1820
|
+
{
|
|
1821
|
+
"type": "openclawUpdate",
|
|
1822
|
+
"trace_id": "openclaw-update-001",
|
|
1823
|
+
"data": {}
|
|
1824
|
+
}
|
|
1825
|
+
```
|
|
1826
|
+
|
|
1827
|
+
成功返回示例:
|
|
1828
|
+
|
|
1829
|
+
```json
|
|
1830
|
+
{
|
|
1831
|
+
"type": "receiver",
|
|
1832
|
+
"trace_id": "openclaw-update-001",
|
|
1833
|
+
"source": "system",
|
|
1834
|
+
"success": true,
|
|
1835
|
+
"data": {
|
|
1836
|
+
"ok": true,
|
|
1837
|
+
"action": "openclawUpdate",
|
|
1838
|
+
"restartRecommended": true,
|
|
1839
|
+
"command": "openclaw",
|
|
1840
|
+
"args": ["update"],
|
|
1841
|
+
"cwd": "/home/woowonjae/.openclaw",
|
|
1842
|
+
"stdout": "...",
|
|
1843
|
+
"stderr": ""
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
```
|
|
1847
|
+
|
|
1848
|
+
### pluginSelfUpdate
|
|
1849
|
+
|
|
1850
|
+
作用:升级当前 `rol-websocket-channel` 插件。
|
|
1851
|
+
|
|
1852
|
+
实际执行命令:
|
|
1853
|
+
|
|
1854
|
+
```bash
|
|
1855
|
+
openclaw plugins update rol-websocket-channel
|
|
1856
|
+
```
|
|
1857
|
+
|
|
1858
|
+
MQTT 请求示例:
|
|
1859
|
+
|
|
1860
|
+
```json
|
|
1861
|
+
{
|
|
1862
|
+
"type": "pluginSelfUpdate",
|
|
1863
|
+
"trace_id": "plugin-self-update-001",
|
|
1864
|
+
"data": {}
|
|
1865
|
+
}
|
|
1866
|
+
```
|
|
1867
|
+
|
|
1868
|
+
成功返回示例:
|
|
1869
|
+
|
|
1870
|
+
```json
|
|
1871
|
+
{
|
|
1872
|
+
"type": "receiver",
|
|
1873
|
+
"trace_id": "plugin-self-update-001",
|
|
1874
|
+
"source": "system",
|
|
1875
|
+
"success": true,
|
|
1876
|
+
"data": {
|
|
1877
|
+
"ok": true,
|
|
1878
|
+
"action": "pluginSelfUpdate",
|
|
1879
|
+
"plugin": "rol-websocket-channel",
|
|
1880
|
+
"restartRecommended": true,
|
|
1881
|
+
"command": "openclaw",
|
|
1882
|
+
"args": ["plugins", "update", "rol-websocket-channel"],
|
|
1883
|
+
"cwd": "/home/woowonjae/.openclaw",
|
|
1884
|
+
"stdout": "...",
|
|
1885
|
+
"stderr": ""
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
```
|
|
1889
|
+
|
|
1890
|
+
### 失败返回
|
|
1891
|
+
|
|
1892
|
+
如果本机命令执行失败,`success` 会是 `false`,错误信息里会带上实际执行的命令、参数、工作目录、`stdout` 和 `stderr`,前端可以直接展示 `error.message` 或 `error.stderr`。
|
|
1893
|
+
|
|
1894
|
+
```json
|
|
1895
|
+
{
|
|
1896
|
+
"type": "receiver",
|
|
1897
|
+
"trace_id": "plugin-self-update-001",
|
|
1898
|
+
"source": "system",
|
|
1899
|
+
"success": false,
|
|
1900
|
+
"error": {
|
|
1901
|
+
"message": "OpenClaw system command failed: Command failed: openclaw plugins update rol-websocket-channel",
|
|
1902
|
+
"action": "pluginSelfUpdate",
|
|
1903
|
+
"command": "openclaw",
|
|
1904
|
+
"args": ["plugins", "update", "rol-websocket-channel"],
|
|
1905
|
+
"cwd": "/home/woowonjae/.openclaw",
|
|
1906
|
+
"stdout": "...",
|
|
1907
|
+
"stderr": "..."
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# SDK Channel TODO
|
|
2
|
+
|
|
3
|
+
1. Migrate the plugin entry to the current SDK style with `defineChannelPluginEntry({ channel, cli, hooks, manifest })`.
|
|
4
|
+
2. Move `sender` message normalization into a dedicated inbound normalizer while keeping admin MQTT command types out of the chat inbound path.
|
|
5
|
+
3. Align CLI metadata with the SDK entry format while preserving `admin-bridge` and `rol-websocket-channel` command compatibility.
|
|
6
|
+
4. Keep `package.json`, `openclaw.plugin.json`, and ClawHub release versions in sync for update/install flows.
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,26 @@ import * as ConnectionManager from "./src/mqtt/connection-manager.js";
|
|
|
9
9
|
// ============================================
|
|
10
10
|
import { getContext, initializeContext } from "./src/shared/context.js";
|
|
11
11
|
let pluginRuntime = null;
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
14
|
+
}
|
|
15
|
+
function pickNonEmptyString(...values) {
|
|
16
|
+
for (const value of values) {
|
|
17
|
+
if (typeof value !== "string")
|
|
18
|
+
continue;
|
|
19
|
+
const trimmed = value.trim();
|
|
20
|
+
if (trimmed)
|
|
21
|
+
return trimmed;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
function getChannelConfig(cfg) {
|
|
26
|
+
const entry = cfg.channels?.["rol-websocket-channel"];
|
|
27
|
+
if (!isRecord(entry))
|
|
28
|
+
return null;
|
|
29
|
+
const nested = isRecord(entry.config) ? entry.config : {};
|
|
30
|
+
return { entry, nested };
|
|
31
|
+
}
|
|
12
32
|
export function getPluginRuntime() {
|
|
13
33
|
return pluginRuntime;
|
|
14
34
|
}
|
|
@@ -99,7 +119,7 @@ const WebSocketChannel = {
|
|
|
99
119
|
enum: ["pairing", "allowlist", "open", "disabled"],
|
|
100
120
|
},
|
|
101
121
|
},
|
|
102
|
-
required: ["mqttUrl"],
|
|
122
|
+
required: ["mqttUrl", "mqttTopic"],
|
|
103
123
|
},
|
|
104
124
|
},
|
|
105
125
|
},
|
|
@@ -139,12 +159,12 @@ const WebSocketChannel = {
|
|
|
139
159
|
},
|
|
140
160
|
"config.mqttUrl": {
|
|
141
161
|
label: "MQTT Broker URL",
|
|
142
|
-
placeholder: "ws://
|
|
162
|
+
placeholder: "ws://mqtt.example.com:8083/mqtt",
|
|
143
163
|
help: "MQTT broker WebSocket URL",
|
|
144
164
|
},
|
|
145
165
|
"config.mqttTopic": {
|
|
146
166
|
label: "MQTT Topic",
|
|
147
|
-
placeholder: "announcement/
|
|
167
|
+
placeholder: "announcement/{userId}/{agentId}/#",
|
|
148
168
|
help: "MQTT topic to subscribe/publish",
|
|
149
169
|
},
|
|
150
170
|
"config.groupPolicy": {
|
|
@@ -155,26 +175,37 @@ const WebSocketChannel = {
|
|
|
155
175
|
},
|
|
156
176
|
config: {
|
|
157
177
|
listAccountIds: (cfg) => {
|
|
158
|
-
const channelCfg = cfg
|
|
159
|
-
|
|
178
|
+
const channelCfg = getChannelConfig(cfg);
|
|
179
|
+
const mqttUrl = channelCfg
|
|
180
|
+
? pickNonEmptyString(channelCfg.nested.mqttUrl, channelCfg.entry.mqttUrl)
|
|
181
|
+
: undefined;
|
|
182
|
+
if (!mqttUrl) {
|
|
160
183
|
return [];
|
|
161
184
|
}
|
|
162
185
|
return ["default"];
|
|
163
186
|
},
|
|
164
187
|
resolveAccount: (cfg, accountId) => {
|
|
165
|
-
const channelCfg = cfg
|
|
166
|
-
if (!channelCfg
|
|
188
|
+
const channelCfg = getChannelConfig(cfg);
|
|
189
|
+
if (!channelCfg) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
const config = channelCfg.nested;
|
|
193
|
+
const mqttUrl = pickNonEmptyString(config.mqttUrl, channelCfg.entry.mqttUrl);
|
|
194
|
+
if (!mqttUrl) {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
const mqttTopic = pickNonEmptyString(config.mqttTopic, channelCfg.entry.mqttTopic);
|
|
198
|
+
if (!mqttTopic) {
|
|
167
199
|
return undefined;
|
|
168
200
|
}
|
|
169
|
-
const config = channelCfg.config;
|
|
170
201
|
return {
|
|
171
202
|
accountId: "default",
|
|
172
|
-
mqttUrl
|
|
173
|
-
mqttTopic
|
|
203
|
+
mqttUrl,
|
|
204
|
+
mqttTopic,
|
|
174
205
|
enabled: config.enabled !== false,
|
|
175
|
-
dmPolicy: channelCfg.dmPolicy || config.groupPolicy || "open",
|
|
176
|
-
allowFrom: Array.isArray(channelCfg.allowFrom)
|
|
177
|
-
? channelCfg.allowFrom
|
|
206
|
+
dmPolicy: channelCfg.entry.dmPolicy || config.groupPolicy || "open",
|
|
207
|
+
allowFrom: Array.isArray(channelCfg.entry.allowFrom)
|
|
208
|
+
? channelCfg.entry.allowFrom
|
|
178
209
|
: [],
|
|
179
210
|
groupPolicy: config.groupPolicy || "open",
|
|
180
211
|
};
|
|
@@ -262,8 +293,31 @@ const WebSocketChannel = {
|
|
|
262
293
|
console.error(`[MQTT] startAccount(${account.accountId}): Runtime API not available`);
|
|
263
294
|
throw new Error("Runtime API not available");
|
|
264
295
|
}
|
|
265
|
-
const mqttUrl = account.mqttUrl
|
|
266
|
-
|
|
296
|
+
const mqttUrl = pickNonEmptyString(account.mqttUrl);
|
|
297
|
+
if (!mqttUrl) {
|
|
298
|
+
const message = "MQTT broker URL is not configured";
|
|
299
|
+
console.error(`[MQTT] startAccount(${account.accountId}): ${message}`);
|
|
300
|
+
ctx.setStatus({
|
|
301
|
+
accountId: account.accountId,
|
|
302
|
+
running: false,
|
|
303
|
+
connected: false,
|
|
304
|
+
lastError: message,
|
|
305
|
+
});
|
|
306
|
+
throw new Error(message);
|
|
307
|
+
}
|
|
308
|
+
const mqttTopic = pickNonEmptyString(account.mqttTopic);
|
|
309
|
+
if (!mqttTopic) {
|
|
310
|
+
const message = "MQTT topic is not configured";
|
|
311
|
+
console.error(`[MQTT] startAccount(${account.accountId}): ${message}`);
|
|
312
|
+
ctx.setStatus({
|
|
313
|
+
accountId: account.accountId,
|
|
314
|
+
mqttUrl,
|
|
315
|
+
running: false,
|
|
316
|
+
connected: false,
|
|
317
|
+
lastError: message,
|
|
318
|
+
});
|
|
319
|
+
throw new Error(message);
|
|
320
|
+
}
|
|
267
321
|
console.log(`[MQTT] startAccount(${account.accountId}): url=${mqttUrl}, topic=${mqttTopic}`);
|
|
268
322
|
ctx.setStatus({
|
|
269
323
|
accountId: account.accountId,
|
|
@@ -211,6 +211,10 @@ async function writeMem9Config(openclawRoot, apiKey) {
|
|
|
211
211
|
config.plugins.entries[MEM9_PLUGIN_ID] = {
|
|
212
212
|
...existingEntry,
|
|
213
213
|
enabled: true,
|
|
214
|
+
hooks: {
|
|
215
|
+
...(isRecord(existingEntry.hooks) ? existingEntry.hooks : {}),
|
|
216
|
+
allowConversationAccess: true
|
|
217
|
+
},
|
|
214
218
|
config: {
|
|
215
219
|
...existingPluginConfig,
|
|
216
220
|
apiUrl: MEM9_API_URL,
|
|
@@ -221,6 +225,7 @@ async function writeMem9Config(openclawRoot, apiKey) {
|
|
|
221
225
|
await writeJsonFile(configPath, config);
|
|
222
226
|
return [
|
|
223
227
|
hadExistingKey ? 'plugins.entries.mem9.config.apiKey (replaced)' : 'plugins.entries.mem9',
|
|
228
|
+
'plugins.entries.mem9.hooks.allowConversationAccess',
|
|
224
229
|
'plugins.slots.memory'
|
|
225
230
|
];
|
|
226
231
|
}
|
|
@@ -240,6 +245,10 @@ async function ensureMem9SlotConfig(openclawRoot, apiKey) {
|
|
|
240
245
|
config.plugins.entries[MEM9_PLUGIN_ID] = {
|
|
241
246
|
...existingEntry,
|
|
242
247
|
enabled: true,
|
|
248
|
+
hooks: {
|
|
249
|
+
...(isRecord(existingEntry.hooks) ? existingEntry.hooks : {}),
|
|
250
|
+
allowConversationAccess: true
|
|
251
|
+
},
|
|
243
252
|
config: {
|
|
244
253
|
...existingPluginConfig,
|
|
245
254
|
apiUrl: MEM9_API_URL,
|
|
@@ -250,6 +259,7 @@ async function ensureMem9SlotConfig(openclawRoot, apiKey) {
|
|
|
250
259
|
await writeJsonFile(configPath, config);
|
|
251
260
|
return [
|
|
252
261
|
'plugins.entries.mem9',
|
|
262
|
+
'plugins.entries.mem9.hooks.allowConversationAccess',
|
|
253
263
|
'plugins.slots.memory'
|
|
254
264
|
];
|
|
255
265
|
}
|
|
@@ -105,7 +105,16 @@ function normalizePairingPayload(raw, endpoint, existingMqttUrl) {
|
|
|
105
105
|
?? pickString(root.mqttTopic)
|
|
106
106
|
?? pickString(root.mqtt_topic)
|
|
107
107
|
?? rawValue
|
|
108
|
-
??
|
|
108
|
+
?? null;
|
|
109
|
+
if (!mqttTopic) {
|
|
110
|
+
throwPairingError('PAIR_CHANNEL_CONFIG_INVALID', 'mqttTopic is missing from pairing payload', {
|
|
111
|
+
endpoint,
|
|
112
|
+
rootKeys: Object.keys(root),
|
|
113
|
+
channelKeys: Object.keys(channelValue),
|
|
114
|
+
channelConfigKeys: Object.keys(channelConfigValue),
|
|
115
|
+
hasRawValue: Boolean(rawValue)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
109
118
|
const groupPolicy = normalizeGroupPolicy(pickString(channelValue.groupPolicy)
|
|
110
119
|
?? pickString(channelValue.group_policy)
|
|
111
120
|
?? pickString(channelValue.dmPolicy)
|
|
@@ -181,7 +190,7 @@ function applyPairingConfig(config, key, payload) {
|
|
|
181
190
|
...existingChannelConfig,
|
|
182
191
|
enabled: true,
|
|
183
192
|
mqttUrl: payload.channel.mqttUrl,
|
|
184
|
-
mqttTopic: payload.channel.mqttTopic
|
|
193
|
+
mqttTopic: payload.channel.mqttTopic,
|
|
185
194
|
groupPolicy: payload.channel.groupPolicy
|
|
186
195
|
}
|
|
187
196
|
};
|
|
@@ -81,11 +81,11 @@ export const openclawUpdate = async (_params, context) => {
|
|
|
81
81
|
};
|
|
82
82
|
};
|
|
83
83
|
export const pluginSelfUpdate = async (_params, context) => {
|
|
84
|
-
const result = await runOpenClawCommand(['plugins', '
|
|
84
|
+
const result = await runOpenClawCommand(['plugins', 'update', 'rol-websocket-channel'], context, 'pluginSelfUpdate');
|
|
85
85
|
return {
|
|
86
86
|
ok: true,
|
|
87
87
|
action: 'pluginSelfUpdate',
|
|
88
|
-
plugin: '
|
|
88
|
+
plugin: 'rol-websocket-channel',
|
|
89
89
|
restartRecommended: true,
|
|
90
90
|
...result
|
|
91
91
|
};
|
package/index.ts
CHANGED
|
@@ -34,6 +34,29 @@ import { getContext, initializeContext } from "./src/shared/context.js";
|
|
|
34
34
|
|
|
35
35
|
let pluginRuntime: any = null;
|
|
36
36
|
|
|
37
|
+
function isRecord(value: unknown): value is Record<string, any> {
|
|
38
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function pickNonEmptyString(...values: unknown[]): string | undefined {
|
|
42
|
+
for (const value of values) {
|
|
43
|
+
if (typeof value !== "string") continue;
|
|
44
|
+
const trimmed = value.trim();
|
|
45
|
+
if (trimmed) return trimmed;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getChannelConfig(cfg: OpenClawConfig): {
|
|
51
|
+
entry: Record<string, any>;
|
|
52
|
+
nested: Record<string, any>;
|
|
53
|
+
} | null {
|
|
54
|
+
const entry = cfg.channels?.["rol-websocket-channel"];
|
|
55
|
+
if (!isRecord(entry)) return null;
|
|
56
|
+
const nested = isRecord(entry.config) ? entry.config : {};
|
|
57
|
+
return { entry, nested };
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
export function getPluginRuntime(): any {
|
|
38
61
|
return pluginRuntime;
|
|
39
62
|
}
|
|
@@ -135,7 +158,7 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
|
|
|
135
158
|
enum: ["pairing", "allowlist", "open", "disabled"],
|
|
136
159
|
},
|
|
137
160
|
},
|
|
138
|
-
required: ["mqttUrl"],
|
|
161
|
+
required: ["mqttUrl", "mqttTopic"],
|
|
139
162
|
},
|
|
140
163
|
},
|
|
141
164
|
},
|
|
@@ -175,12 +198,12 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
|
|
|
175
198
|
},
|
|
176
199
|
"config.mqttUrl": {
|
|
177
200
|
label: "MQTT Broker URL",
|
|
178
|
-
placeholder: "ws://
|
|
201
|
+
placeholder: "ws://mqtt.example.com:8083/mqtt",
|
|
179
202
|
help: "MQTT broker WebSocket URL",
|
|
180
203
|
},
|
|
181
204
|
"config.mqttTopic": {
|
|
182
205
|
label: "MQTT Topic",
|
|
183
|
-
placeholder: "announcement/
|
|
206
|
+
placeholder: "announcement/{userId}/{agentId}/#",
|
|
184
207
|
help: "MQTT topic to subscribe/publish",
|
|
185
208
|
},
|
|
186
209
|
"config.groupPolicy": {
|
|
@@ -192,29 +215,43 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
|
|
|
192
215
|
|
|
193
216
|
config: {
|
|
194
217
|
listAccountIds: (cfg: OpenClawConfig) => {
|
|
195
|
-
const channelCfg = cfg
|
|
196
|
-
|
|
218
|
+
const channelCfg = getChannelConfig(cfg);
|
|
219
|
+
const mqttUrl = channelCfg
|
|
220
|
+
? pickNonEmptyString(channelCfg.nested.mqttUrl, channelCfg.entry.mqttUrl)
|
|
221
|
+
: undefined;
|
|
222
|
+
if (!mqttUrl) {
|
|
197
223
|
return [];
|
|
198
224
|
}
|
|
199
225
|
return ["default"];
|
|
200
226
|
},
|
|
201
227
|
|
|
202
228
|
resolveAccount: (cfg: OpenClawConfig, accountId: string) => {
|
|
203
|
-
const channelCfg = cfg
|
|
204
|
-
if (!channelCfg
|
|
229
|
+
const channelCfg = getChannelConfig(cfg);
|
|
230
|
+
if (!channelCfg) {
|
|
205
231
|
return undefined;
|
|
206
232
|
}
|
|
207
233
|
|
|
208
|
-
const config = channelCfg.
|
|
234
|
+
const config = channelCfg.nested;
|
|
235
|
+
const mqttUrl = pickNonEmptyString(config.mqttUrl, channelCfg.entry.mqttUrl);
|
|
236
|
+
if (!mqttUrl) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
const mqttTopic = pickNonEmptyString(
|
|
240
|
+
config.mqttTopic,
|
|
241
|
+
channelCfg.entry.mqttTopic,
|
|
242
|
+
);
|
|
243
|
+
if (!mqttTopic) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
209
246
|
|
|
210
247
|
return {
|
|
211
248
|
accountId: "default",
|
|
212
|
-
mqttUrl
|
|
213
|
-
mqttTopic
|
|
249
|
+
mqttUrl,
|
|
250
|
+
mqttTopic,
|
|
214
251
|
enabled: config.enabled !== false,
|
|
215
|
-
dmPolicy: channelCfg.dmPolicy || config.groupPolicy || "open",
|
|
216
|
-
allowFrom: Array.isArray(channelCfg.allowFrom)
|
|
217
|
-
? channelCfg.allowFrom
|
|
252
|
+
dmPolicy: channelCfg.entry.dmPolicy || config.groupPolicy || "open",
|
|
253
|
+
allowFrom: Array.isArray(channelCfg.entry.allowFrom)
|
|
254
|
+
? channelCfg.entry.allowFrom
|
|
218
255
|
: [],
|
|
219
256
|
groupPolicy: config.groupPolicy || "open",
|
|
220
257
|
};
|
|
@@ -322,8 +359,35 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
|
|
|
322
359
|
throw new Error("Runtime API not available");
|
|
323
360
|
}
|
|
324
361
|
|
|
325
|
-
const mqttUrl = account.mqttUrl
|
|
326
|
-
|
|
362
|
+
const mqttUrl = pickNonEmptyString(account.mqttUrl);
|
|
363
|
+
if (!mqttUrl) {
|
|
364
|
+
const message = "MQTT broker URL is not configured";
|
|
365
|
+
console.error(
|
|
366
|
+
`[MQTT] startAccount(${account.accountId}): ${message}`,
|
|
367
|
+
);
|
|
368
|
+
ctx.setStatus({
|
|
369
|
+
accountId: account.accountId,
|
|
370
|
+
running: false,
|
|
371
|
+
connected: false,
|
|
372
|
+
lastError: message,
|
|
373
|
+
});
|
|
374
|
+
throw new Error(message);
|
|
375
|
+
}
|
|
376
|
+
const mqttTopic = pickNonEmptyString(account.mqttTopic);
|
|
377
|
+
if (!mqttTopic) {
|
|
378
|
+
const message = "MQTT topic is not configured";
|
|
379
|
+
console.error(
|
|
380
|
+
`[MQTT] startAccount(${account.accountId}): ${message}`,
|
|
381
|
+
);
|
|
382
|
+
ctx.setStatus({
|
|
383
|
+
accountId: account.accountId,
|
|
384
|
+
mqttUrl,
|
|
385
|
+
running: false,
|
|
386
|
+
connected: false,
|
|
387
|
+
lastError: message,
|
|
388
|
+
});
|
|
389
|
+
throw new Error(message);
|
|
390
|
+
}
|
|
327
391
|
console.log(
|
|
328
392
|
`[MQTT] startAccount(${account.accountId}): url=${mqttUrl}, topic=${mqttTopic}`,
|
|
329
393
|
);
|
package/openclaw.plugin.json
CHANGED
|
@@ -69,12 +69,12 @@
|
|
|
69
69
|
},
|
|
70
70
|
"mqttUrl": {
|
|
71
71
|
"label": "MQTT Broker URL",
|
|
72
|
-
"placeholder": "ws://
|
|
73
|
-
"help": "MQTT broker WebSocket URL
|
|
72
|
+
"placeholder": "ws://mqtt.example.com:8083/mqtt",
|
|
73
|
+
"help": "MQTT broker WebSocket URL"
|
|
74
74
|
},
|
|
75
75
|
"mqttTopic": {
|
|
76
76
|
"label": "MQTT Topic",
|
|
77
|
-
"placeholder": "announcement/
|
|
77
|
+
"placeholder": "announcement/{userId}/{agentId}/#",
|
|
78
78
|
"help": "MQTT topic to subscribe/publish"
|
|
79
79
|
},
|
|
80
80
|
"groupPolicy": {
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
"enum": ["pairing", "allowlist", "open", "disabled"]
|
|
217
217
|
}
|
|
218
218
|
},
|
|
219
|
-
"required": ["mqttUrl"]
|
|
219
|
+
"required": ["mqttUrl", "mqttTopic"]
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
}
|
|
@@ -294,12 +294,12 @@
|
|
|
294
294
|
},
|
|
295
295
|
"channels.rol-websocket-channel.config.mqttUrl": {
|
|
296
296
|
"label": "MQTT Broker URL",
|
|
297
|
-
"placeholder": "ws://
|
|
298
|
-
"help": "MQTT broker WebSocket URL
|
|
297
|
+
"placeholder": "ws://mqtt.example.com:8083/mqtt",
|
|
298
|
+
"help": "MQTT broker WebSocket URL"
|
|
299
299
|
},
|
|
300
300
|
"channels.rol-websocket-channel.config.mqttTopic": {
|
|
301
301
|
"label": "MQTT Topic",
|
|
302
|
-
"placeholder": "announcement/
|
|
302
|
+
"placeholder": "announcement/{userId}/{agentId}/#",
|
|
303
303
|
"help": "MQTT topic to subscribe/publish"
|
|
304
304
|
},
|
|
305
305
|
"channels.rol-websocket-channel.config.groupPolicy": {
|
package/package.json
CHANGED
|
@@ -270,6 +270,10 @@ async function writeMem9Config(openclawRoot: string, apiKey: string): Promise<st
|
|
|
270
270
|
config.plugins.entries[MEM9_PLUGIN_ID] = {
|
|
271
271
|
...existingEntry,
|
|
272
272
|
enabled: true,
|
|
273
|
+
hooks: {
|
|
274
|
+
...(isRecord(existingEntry.hooks) ? existingEntry.hooks : {}),
|
|
275
|
+
allowConversationAccess: true
|
|
276
|
+
},
|
|
273
277
|
config: {
|
|
274
278
|
...existingPluginConfig,
|
|
275
279
|
apiUrl: MEM9_API_URL,
|
|
@@ -282,6 +286,7 @@ async function writeMem9Config(openclawRoot: string, apiKey: string): Promise<st
|
|
|
282
286
|
|
|
283
287
|
return [
|
|
284
288
|
hadExistingKey ? 'plugins.entries.mem9.config.apiKey (replaced)' : 'plugins.entries.mem9',
|
|
289
|
+
'plugins.entries.mem9.hooks.allowConversationAccess',
|
|
285
290
|
'plugins.slots.memory'
|
|
286
291
|
];
|
|
287
292
|
}
|
|
@@ -304,6 +309,10 @@ async function ensureMem9SlotConfig(openclawRoot: string, apiKey: string): Promi
|
|
|
304
309
|
config.plugins.entries[MEM9_PLUGIN_ID] = {
|
|
305
310
|
...existingEntry,
|
|
306
311
|
enabled: true,
|
|
312
|
+
hooks: {
|
|
313
|
+
...(isRecord(existingEntry.hooks) ? existingEntry.hooks : {}),
|
|
314
|
+
allowConversationAccess: true
|
|
315
|
+
},
|
|
307
316
|
config: {
|
|
308
317
|
...existingPluginConfig,
|
|
309
318
|
apiUrl: MEM9_API_URL,
|
|
@@ -316,6 +325,7 @@ async function ensureMem9SlotConfig(openclawRoot: string, apiKey: string): Promi
|
|
|
316
325
|
|
|
317
326
|
return [
|
|
318
327
|
'plugins.entries.mem9',
|
|
328
|
+
'plugins.entries.mem9.hooks.allowConversationAccess',
|
|
319
329
|
'plugins.slots.memory'
|
|
320
330
|
];
|
|
321
331
|
}
|
|
@@ -41,7 +41,7 @@ interface PairingPayload {
|
|
|
41
41
|
};
|
|
42
42
|
channel: {
|
|
43
43
|
mqttUrl: string;
|
|
44
|
-
mqttTopic
|
|
44
|
+
mqttTopic: string;
|
|
45
45
|
groupPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';
|
|
46
46
|
};
|
|
47
47
|
}
|
|
@@ -51,7 +51,8 @@ interface PairingPayloadDebug {
|
|
|
51
51
|
rootKeys: string[];
|
|
52
52
|
channelKeys: string[];
|
|
53
53
|
channelConfigKeys: string[];
|
|
54
|
-
hasExistingMqttUrl
|
|
54
|
+
hasExistingMqttUrl?: boolean;
|
|
55
|
+
hasRawValue?: boolean;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export async function pairWithKey(
|
|
@@ -181,7 +182,16 @@ function normalizePairingPayload(
|
|
|
181
182
|
?? pickString(root.mqttTopic)
|
|
182
183
|
?? pickString(root.mqtt_topic)
|
|
183
184
|
?? rawValue
|
|
184
|
-
??
|
|
185
|
+
?? null;
|
|
186
|
+
if (!mqttTopic) {
|
|
187
|
+
throwPairingError('PAIR_CHANNEL_CONFIG_INVALID', 'mqttTopic is missing from pairing payload', {
|
|
188
|
+
endpoint,
|
|
189
|
+
rootKeys: Object.keys(root),
|
|
190
|
+
channelKeys: Object.keys(channelValue),
|
|
191
|
+
channelConfigKeys: Object.keys(channelConfigValue),
|
|
192
|
+
hasRawValue: Boolean(rawValue)
|
|
193
|
+
});
|
|
194
|
+
}
|
|
185
195
|
|
|
186
196
|
const groupPolicy = normalizeGroupPolicy(
|
|
187
197
|
pickString(channelValue.groupPolicy)
|
|
@@ -266,7 +276,7 @@ function applyPairingConfig(config: OpenClawConfig, key: string, payload: Pairin
|
|
|
266
276
|
...existingChannelConfig,
|
|
267
277
|
enabled: true,
|
|
268
278
|
mqttUrl: payload.channel.mqttUrl,
|
|
269
|
-
mqttTopic: payload.channel.mqttTopic
|
|
279
|
+
mqttTopic: payload.channel.mqttTopic,
|
|
270
280
|
groupPolicy: payload.channel.groupPolicy
|
|
271
281
|
}
|
|
272
282
|
};
|
|
@@ -91,7 +91,7 @@ export const openclawUpdate: MethodHandler = async (_params, context: MethodCont
|
|
|
91
91
|
|
|
92
92
|
export const pluginSelfUpdate: MethodHandler = async (_params, context: MethodContext): Promise<JsonValue> => {
|
|
93
93
|
const result = await runOpenClawCommand(
|
|
94
|
-
['plugins', '
|
|
94
|
+
['plugins', 'update', 'rol-websocket-channel'],
|
|
95
95
|
context,
|
|
96
96
|
'pluginSelfUpdate'
|
|
97
97
|
);
|
|
@@ -99,7 +99,7 @@ export const pluginSelfUpdate: MethodHandler = async (_params, context: MethodCo
|
|
|
99
99
|
return {
|
|
100
100
|
ok: true,
|
|
101
101
|
action: 'pluginSelfUpdate',
|
|
102
|
-
plugin: '
|
|
102
|
+
plugin: 'rol-websocket-channel',
|
|
103
103
|
restartRecommended: true,
|
|
104
104
|
...result
|
|
105
105
|
};
|