rol-websocket-channel 1.4.2 → 1.4.8

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.
Files changed (43) hide show
  1. package/{MQTT-API /346/226/260/345/242/236/346/226/207/344/273/266/345/212/237/350/203/275.md" → MQTT-API 5-6.md } +89 -1
  2. package/dist/index.js +617 -617
  3. package/dist/message-handler.js +515 -503
  4. package/dist/src/admin/cli.js +43 -43
  5. package/dist/src/admin/jsonrpc.js +60 -60
  6. package/dist/src/admin/lib/fs.js +30 -30
  7. package/dist/src/admin/lib/paths.js +80 -80
  8. package/dist/src/admin/methods/admin.js +60 -60
  9. package/dist/src/admin/methods/agents-extended.js +251 -251
  10. package/dist/src/admin/methods/artifacts.js +736 -642
  11. package/dist/src/admin/methods/artifacts.test.js +210 -191
  12. package/dist/src/admin/methods/cron.js +250 -250
  13. package/dist/src/admin/methods/index.js +104 -102
  14. package/dist/src/admin/methods/mem9.js +309 -270
  15. package/dist/src/admin/methods/mem9.test.js +34 -0
  16. package/dist/src/admin/methods/memory.js +363 -363
  17. package/dist/src/admin/methods/models-extended.js +190 -190
  18. package/dist/src/admin/methods/models.js +195 -195
  19. package/dist/src/admin/methods/pairing.js +268 -268
  20. package/dist/src/admin/methods/sessions-extended.js +215 -215
  21. package/dist/src/admin/methods/sessions.js +75 -75
  22. package/dist/src/admin/methods/skills-extended.js +157 -157
  23. package/dist/src/admin/methods/skills-toggle.js +183 -183
  24. package/dist/src/admin/methods/skills.js +528 -528
  25. package/dist/src/admin/methods/system.js +271 -180
  26. package/dist/src/admin/methods/usage.js +1170 -1170
  27. package/dist/src/admin/types.js +1 -1
  28. package/dist/src/mqtt/connection-manager.js +209 -209
  29. package/dist/src/mqtt/index.js +5 -5
  30. package/dist/src/mqtt/mqtt-client.js +110 -110
  31. package/dist/src/mqtt/mqtt.test.js +418 -418
  32. package/dist/src/mqtt/types.js +2 -2
  33. package/dist/src/shared/context.js +24 -24
  34. package/dist/src/shared/wrapper.js +23 -23
  35. package/message-handler.ts +15 -1
  36. package/openclaw.plugin.json +73 -0
  37. package/package.json +1 -1
  38. package/src/admin/methods/artifacts.test.ts +35 -0
  39. package/src/admin/methods/artifacts.ts +140 -2
  40. package/src/admin/methods/index.ts +3 -1
  41. package/src/admin/methods/mem9.test.ts +39 -0
  42. package/src/admin/methods/mem9.ts +48 -1
  43. package/src/admin/methods/system.ts +129 -1
@@ -1,191 +1,210 @@
1
- import { afterEach, describe, test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import fs from 'node:fs/promises';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import { ensureArtifactUploaded, listArtifacts, markArtifactUploaded, refreshArtifacts } from './artifacts.js';
7
- const tempDirs = [];
8
- const originalFetch = globalThis.fetch;
9
- afterEach(async () => {
10
- globalThis.fetch = originalFetch;
11
- while (tempDirs.length > 0) {
12
- const dir = tempDirs.pop();
13
- if (dir) {
14
- await fs.rm(dir, { recursive: true, force: true });
15
- }
16
- }
17
- });
18
- describe('artifacts workspace scope', () => {
19
- test('only indexes allowed artifact types inside workspace', async () => {
20
- const context = await createMethodContext();
21
- const workspaceRoot = path.join(context.openclawRoot, 'workspace');
22
- await fs.writeFile(path.join(workspaceRoot, 'SOUL.md'), '# root noise\n', 'utf8');
23
- await fs.mkdir(path.join(workspaceRoot, 'exports'), { recursive: true });
24
- await fs.mkdir(path.join(workspaceRoot, 'logs'), { recursive: true });
25
- await fs.mkdir(path.join(workspaceRoot, 'nested', 'media'), { recursive: true });
26
- await fs.writeFile(path.join(workspaceRoot, 'exports', 'preview.png'), 'png-data', 'utf8');
27
- await fs.writeFile(path.join(workspaceRoot, 'exports', 'report.pdf'), 'pdf-data', 'utf8');
28
- await fs.writeFile(path.join(workspaceRoot, 'nested', 'media', 'archive.zip'), 'zip-data', 'utf8');
29
- await fs.writeFile(path.join(workspaceRoot, 'nested', 'media', 'video.mp4'), 'mp4-data', 'utf8');
30
- await fs.writeFile(path.join(workspaceRoot, 'exports', 'spec.docx'), 'docx-data', 'utf8');
31
- await fs.writeFile(path.join(workspaceRoot, 'exports', 'ignored.md'), '# markdown noise\n', 'utf8');
32
- await fs.writeFile(path.join(workspaceRoot, 'exports', 'ignored.json'), '{"noise":true}\n', 'utf8');
33
- await fs.writeFile(path.join(workspaceRoot, 'logs', 'ignored.pdf'), 'log pdf noise', 'utf8');
34
- await fs.writeFile(path.join(workspaceRoot, 'artifacts.json'), '[]', 'utf8');
35
- const result = await refreshArtifacts({}, context);
36
- assert.equal(result.scope, 'workspace');
37
- assert.equal(result.count, 5);
38
- assert.deepEqual(result.items.map((item) => item.relativePath), [
39
- 'nested/media/archive.zip',
40
- 'exports/preview.png',
41
- 'exports/report.pdf',
42
- 'exports/spec.docx',
43
- 'nested/media/video.mp4'
44
- ]);
45
- assert.equal(result.workspaceRoot, workspaceRoot);
46
- assert.equal(result.manifestPath, path.join(workspaceRoot, 'artifacts.json'));
47
- });
48
- test('list reads workspace manifest without taskId', async () => {
49
- const context = await createMethodContext();
50
- const workspaceRoot = path.join(context.openclawRoot, 'workspace');
51
- await fs.mkdir(path.join(workspaceRoot, 'exports'), { recursive: true });
52
- await fs.writeFile(path.join(workspaceRoot, 'exports', 'me.pdf'), 'pdf-data', 'utf8');
53
- const refreshed = await refreshArtifacts({}, context);
54
- assert.equal(refreshed.count, 1);
55
- const listed = await listArtifacts({ refresh: false }, context);
56
- assert.equal(listed.scope, 'workspace');
57
- assert.equal(listed.count, 1);
58
- assert.deepEqual(listed.items.map((item) => item.relativePath), ['exports/me.pdf']);
59
- assert.equal(listed.manifestPath, path.join(workspaceRoot, 'artifacts.json'));
60
- assert.equal(listed.workspaceRoot, workspaceRoot);
61
- });
62
- test('ensureUploaded uploads local artifact and updates manifest', async () => {
63
- const context = await createMethodContext();
64
- const workspaceRoot = path.join(context.openclawRoot, 'workspace');
65
- const artifactPath = path.join(workspaceRoot, 'exports', 'me.pdf');
66
- await writeApiCoreBotConfig(context.openclawRoot, 'https://api.example.com');
67
- await fs.mkdir(path.dirname(artifactPath), { recursive: true });
68
- await fs.writeFile(artifactPath, 'pdf-data', 'utf8');
69
- const refreshed = await refreshArtifacts({}, context);
70
- const artifactId = refreshed.items[0]?.id;
71
- assert.ok(artifactId);
72
- const calls = [];
73
- globalThis.fetch = (async (input, init) => {
74
- const url = typeof input === 'string'
75
- ? input
76
- : input instanceof URL
77
- ? input.toString()
78
- : input.url;
79
- const method = init?.method ?? (input instanceof Request ? input.method : 'GET');
80
- calls.push({ url, method });
81
- if (url === 'https://api.example.com/api-core-bot/front/s3/get-presigned-post') {
82
- assert.equal(init?.body !== undefined, true);
83
- const parsedBody = JSON.parse(String(init?.body));
84
- assert.equal(parsedBody.dir, 'artifacts/');
85
- assert.equal(parsedBody.filename, 'me.pdf');
86
- assert.equal(parsedBody.file_name, undefined);
87
- return new Response(JSON.stringify({
88
- code: 0,
89
- message: '',
90
- success: true,
91
- data: {
92
- url: 'https://upload.example.com',
93
- fields: {
94
- key: 'artifacts/me.pdf',
95
- policy: 'policy-token'
96
- },
97
- file_key: 'artifacts/me.pdf',
98
- file_url: 'https://cdn.example.com/artifacts/me.pdf'
99
- }
100
- }), {
101
- status: 200,
102
- headers: { 'Content-Type': 'application/json' }
103
- });
104
- }
105
- if (url === 'https://upload.example.com') {
106
- assert.ok(init?.body instanceof FormData);
107
- return new Response(null, { status: 204 });
108
- }
109
- throw new Error(`Unexpected fetch call: ${url}`);
110
- });
111
- const ensured = await ensureArtifactUploaded({
112
- artifactId,
113
- presignedPostBody: {
114
- dir: 'artifacts/'
115
- }
116
- }, context);
117
- assert.equal(ensured.ok, true);
118
- assert.equal(ensured.uploaded, true);
119
- assert.equal(ensured.downloadUrl, 'https://cdn.example.com/artifacts/me.pdf');
120
- assert.equal(ensured.objectKey, 'artifacts/me.pdf');
121
- assert.equal(ensured.item.storageStatus, 'uploaded');
122
- assert.equal(ensured.item.fileUrl, 'https://cdn.example.com/artifacts/me.pdf');
123
- assert.equal(ensured.item.objectKey, 'artifacts/me.pdf');
124
- assert.deepEqual(calls, [
125
- {
126
- url: 'https://api.example.com/api-core-bot/front/s3/get-presigned-post',
127
- method: 'POST'
128
- },
129
- {
130
- url: 'https://upload.example.com',
131
- method: 'POST'
132
- }
133
- ]);
134
- const listed = await listArtifacts({ refresh: false }, context);
135
- assert.equal(listed.items.length, 1);
136
- assert.equal(listed.items[0]?.relativePath, 'exports/me.pdf');
137
- assert.equal(listed.items[0]?.storageStatus, 'uploaded');
138
- assert.equal(listed.items[0]?.fileUrl, 'https://cdn.example.com/artifacts/me.pdf');
139
- assert.equal(listed.items[0]?.objectKey, 'artifacts/me.pdf');
140
- });
141
- test('ensureUploaded reuses existing uploaded artifact without fetching', async () => {
142
- const context = await createMethodContext();
143
- const workspaceRoot = path.join(context.openclawRoot, 'workspace');
144
- const artifactPath = path.join(workspaceRoot, 'exports', 'me.pdf');
145
- await fs.mkdir(path.dirname(artifactPath), { recursive: true });
146
- await fs.writeFile(artifactPath, 'pdf-data', 'utf8');
147
- const refreshed = await refreshArtifacts({}, context);
148
- const artifactId = refreshed.items[0]?.id;
149
- assert.ok(artifactId);
150
- await markArtifactUploaded({
151
- artifactId,
152
- objectKey: 'artifacts/me.pdf',
153
- fileUrl: 'https://cdn.example.com/artifacts/me.pdf'
154
- }, context);
155
- globalThis.fetch = (async () => {
156
- throw new Error('fetch should not be called for existing uploaded artifact');
157
- });
158
- const ensured = await ensureArtifactUploaded({ artifactId }, context);
159
- assert.equal(ensured.ok, true);
160
- assert.equal(ensured.uploaded, false);
161
- assert.equal(ensured.downloadUrl, 'https://cdn.example.com/artifacts/me.pdf');
162
- assert.equal(ensured.item.storageStatus, 'uploaded');
163
- assert.equal(ensured.item.fileUrl, 'https://cdn.example.com/artifacts/me.pdf');
164
- });
165
- });
166
- async function createMethodContext() {
167
- const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'artifacts-task-scope-'));
168
- tempDirs.push(rootDir);
169
- const openclawRoot = path.join(rootDir, '.openclaw');
170
- const workspaceRoot = path.join(openclawRoot, 'workspace');
171
- await fs.mkdir(workspaceRoot, { recursive: true });
172
- return {
173
- projectRoot: rootDir,
174
- openclawRoot
175
- };
176
- }
177
- async function writeApiCoreBotConfig(openclawRoot, baseUrl) {
178
- await fs.writeFile(path.join(openclawRoot, 'openclaw.json'), JSON.stringify({
179
- plugins: {
180
- entries: {
181
- 'rol-websocket-channel': {
182
- config: {
183
- apiCoreBot: {
184
- baseUrl
185
- }
186
- }
187
- }
188
- }
189
- }
190
- }, null, 2), 'utf8');
191
- }
1
+ import { afterEach, describe, test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { ensureArtifactUploaded, listArtifacts, markArtifactUploaded, refreshArtifacts } from './artifacts.js';
7
+ const tempDirs = [];
8
+ const originalFetch = globalThis.fetch;
9
+ afterEach(async () => {
10
+ globalThis.fetch = originalFetch;
11
+ while (tempDirs.length > 0) {
12
+ const dir = tempDirs.pop();
13
+ if (dir) {
14
+ await fs.rm(dir, { recursive: true, force: true });
15
+ }
16
+ }
17
+ });
18
+ describe('artifacts workspace scope', () => {
19
+ test('only indexes allowed artifact types inside workspace', async () => {
20
+ const context = await createMethodContext();
21
+ const workspaceRoot = path.join(context.openclawRoot, 'workspace');
22
+ await fs.writeFile(path.join(workspaceRoot, 'SOUL.md'), '# root noise\n', 'utf8');
23
+ await fs.mkdir(path.join(workspaceRoot, 'exports'), { recursive: true });
24
+ await fs.mkdir(path.join(workspaceRoot, 'logs'), { recursive: true });
25
+ await fs.mkdir(path.join(workspaceRoot, 'nested', 'media'), { recursive: true });
26
+ await fs.writeFile(path.join(workspaceRoot, 'exports', 'preview.png'), 'png-data', 'utf8');
27
+ await fs.writeFile(path.join(workspaceRoot, 'exports', 'report.pdf'), 'pdf-data', 'utf8');
28
+ await fs.writeFile(path.join(workspaceRoot, 'nested', 'media', 'archive.zip'), 'zip-data', 'utf8');
29
+ await fs.writeFile(path.join(workspaceRoot, 'nested', 'media', 'video.mp4'), 'mp4-data', 'utf8');
30
+ await fs.writeFile(path.join(workspaceRoot, 'exports', 'spec.docx'), 'docx-data', 'utf8');
31
+ await fs.writeFile(path.join(workspaceRoot, 'exports', 'ignored.md'), '# markdown noise\n', 'utf8');
32
+ await fs.writeFile(path.join(workspaceRoot, 'exports', 'ignored.json'), '{"noise":true}\n', 'utf8');
33
+ await fs.writeFile(path.join(workspaceRoot, 'logs', 'ignored.pdf'), 'log pdf noise', 'utf8');
34
+ await fs.writeFile(path.join(workspaceRoot, 'artifacts.json'), '[]', 'utf8');
35
+ const result = await refreshArtifacts({}, context);
36
+ assert.equal(result.scope, 'workspace');
37
+ assert.equal(result.count, 5);
38
+ assert.deepEqual(result.items.map((item) => item.relativePath), [
39
+ 'nested/media/archive.zip',
40
+ 'exports/preview.png',
41
+ 'exports/report.pdf',
42
+ 'exports/spec.docx',
43
+ 'nested/media/video.mp4'
44
+ ]);
45
+ assert.equal(result.workspaceRoot, workspaceRoot);
46
+ assert.equal(result.manifestPath, path.join(workspaceRoot, 'artifacts.json'));
47
+ });
48
+ test('indexes only markdown files added after the initial baseline', async () => {
49
+ const context = await createMethodContext();
50
+ const workspaceRoot = path.join(context.openclawRoot, 'workspace');
51
+ await fs.mkdir(path.join(workspaceRoot, 'docs'), { recursive: true });
52
+ await fs.writeFile(path.join(workspaceRoot, 'docs', 'existing.md'), '# existing\n', 'utf8');
53
+ await fs.writeFile(path.join(workspaceRoot, 'MEMORY.md'), '# memory\n', 'utf8');
54
+ await fs.writeFile(path.join(workspaceRoot, 'SoUL.md'), '# soul\n', 'utf8');
55
+ const baseline = await refreshArtifacts({}, context);
56
+ assert.equal(baseline.count, 0);
57
+ assert.deepEqual(baseline.items, []);
58
+ await fs.writeFile(path.join(workspaceRoot, 'docs', 'existing.md'), '# changed\n', 'utf8');
59
+ await fs.writeFile(path.join(workspaceRoot, 'MEMORY.md'), '# changed memory\n', 'utf8');
60
+ await fs.writeFile(path.join(workspaceRoot, 'docs', 'generated.md'), '# generated\n', 'utf8');
61
+ const refreshed = await refreshArtifacts({}, context);
62
+ assert.equal(refreshed.count, 1);
63
+ assert.deepEqual(refreshed.items.map((item) => item.relativePath), ['docs/generated.md']);
64
+ assert.equal(refreshed.items[0]?.category, 'document');
65
+ assert.equal(refreshed.items[0]?.mimeType, 'text/markdown');
66
+ });
67
+ test('list reads workspace manifest without taskId', async () => {
68
+ const context = await createMethodContext();
69
+ const workspaceRoot = path.join(context.openclawRoot, 'workspace');
70
+ await fs.mkdir(path.join(workspaceRoot, 'exports'), { recursive: true });
71
+ await fs.writeFile(path.join(workspaceRoot, 'exports', 'me.pdf'), 'pdf-data', 'utf8');
72
+ const refreshed = await refreshArtifacts({}, context);
73
+ assert.equal(refreshed.count, 1);
74
+ const listed = await listArtifacts({ refresh: false }, context);
75
+ assert.equal(listed.scope, 'workspace');
76
+ assert.equal(listed.count, 1);
77
+ assert.deepEqual(listed.items.map((item) => item.relativePath), ['exports/me.pdf']);
78
+ assert.equal(listed.manifestPath, path.join(workspaceRoot, 'artifacts.json'));
79
+ assert.equal(listed.workspaceRoot, workspaceRoot);
80
+ });
81
+ test('ensureUploaded uploads local artifact and updates manifest', async () => {
82
+ const context = await createMethodContext();
83
+ const workspaceRoot = path.join(context.openclawRoot, 'workspace');
84
+ const artifactPath = path.join(workspaceRoot, 'exports', 'me.pdf');
85
+ await writeApiCoreBotConfig(context.openclawRoot, 'https://api.example.com');
86
+ await fs.mkdir(path.dirname(artifactPath), { recursive: true });
87
+ await fs.writeFile(artifactPath, 'pdf-data', 'utf8');
88
+ const refreshed = await refreshArtifacts({}, context);
89
+ const artifactId = refreshed.items[0]?.id;
90
+ assert.ok(artifactId);
91
+ const calls = [];
92
+ globalThis.fetch = (async (input, init) => {
93
+ const url = typeof input === 'string'
94
+ ? input
95
+ : input instanceof URL
96
+ ? input.toString()
97
+ : input.url;
98
+ const method = init?.method ?? (input instanceof Request ? input.method : 'GET');
99
+ calls.push({ url, method });
100
+ if (url === 'https://api.example.com/api-core-bot/front/s3/get-presigned-post') {
101
+ assert.equal(init?.body !== undefined, true);
102
+ const parsedBody = JSON.parse(String(init?.body));
103
+ assert.equal(parsedBody.dir, 'artifacts/');
104
+ assert.equal(parsedBody.filename, 'me.pdf');
105
+ assert.equal(parsedBody.file_name, undefined);
106
+ return new Response(JSON.stringify({
107
+ code: 0,
108
+ message: '',
109
+ success: true,
110
+ data: {
111
+ url: 'https://upload.example.com',
112
+ fields: {
113
+ key: 'artifacts/me.pdf',
114
+ policy: 'policy-token'
115
+ },
116
+ file_key: 'artifacts/me.pdf',
117
+ file_url: 'https://cdn.example.com/artifacts/me.pdf'
118
+ }
119
+ }), {
120
+ status: 200,
121
+ headers: { 'Content-Type': 'application/json' }
122
+ });
123
+ }
124
+ if (url === 'https://upload.example.com') {
125
+ assert.ok(init?.body instanceof FormData);
126
+ return new Response(null, { status: 204 });
127
+ }
128
+ throw new Error(`Unexpected fetch call: ${url}`);
129
+ });
130
+ const ensured = await ensureArtifactUploaded({
131
+ artifactId,
132
+ presignedPostBody: {
133
+ dir: 'artifacts/'
134
+ }
135
+ }, context);
136
+ assert.equal(ensured.ok, true);
137
+ assert.equal(ensured.uploaded, true);
138
+ assert.equal(ensured.downloadUrl, 'https://cdn.example.com/artifacts/me.pdf');
139
+ assert.equal(ensured.objectKey, 'artifacts/me.pdf');
140
+ assert.equal(ensured.item.storageStatus, 'uploaded');
141
+ assert.equal(ensured.item.fileUrl, 'https://cdn.example.com/artifacts/me.pdf');
142
+ assert.equal(ensured.item.objectKey, 'artifacts/me.pdf');
143
+ assert.deepEqual(calls, [
144
+ {
145
+ url: 'https://api.example.com/api-core-bot/front/s3/get-presigned-post',
146
+ method: 'POST'
147
+ },
148
+ {
149
+ url: 'https://upload.example.com',
150
+ method: 'POST'
151
+ }
152
+ ]);
153
+ const listed = await listArtifacts({ refresh: false }, context);
154
+ assert.equal(listed.items.length, 1);
155
+ assert.equal(listed.items[0]?.relativePath, 'exports/me.pdf');
156
+ assert.equal(listed.items[0]?.storageStatus, 'uploaded');
157
+ assert.equal(listed.items[0]?.fileUrl, 'https://cdn.example.com/artifacts/me.pdf');
158
+ assert.equal(listed.items[0]?.objectKey, 'artifacts/me.pdf');
159
+ });
160
+ test('ensureUploaded reuses existing uploaded artifact without fetching', async () => {
161
+ const context = await createMethodContext();
162
+ const workspaceRoot = path.join(context.openclawRoot, 'workspace');
163
+ const artifactPath = path.join(workspaceRoot, 'exports', 'me.pdf');
164
+ await fs.mkdir(path.dirname(artifactPath), { recursive: true });
165
+ await fs.writeFile(artifactPath, 'pdf-data', 'utf8');
166
+ const refreshed = await refreshArtifacts({}, context);
167
+ const artifactId = refreshed.items[0]?.id;
168
+ assert.ok(artifactId);
169
+ await markArtifactUploaded({
170
+ artifactId,
171
+ objectKey: 'artifacts/me.pdf',
172
+ fileUrl: 'https://cdn.example.com/artifacts/me.pdf'
173
+ }, context);
174
+ globalThis.fetch = (async () => {
175
+ throw new Error('fetch should not be called for existing uploaded artifact');
176
+ });
177
+ const ensured = await ensureArtifactUploaded({ artifactId }, context);
178
+ assert.equal(ensured.ok, true);
179
+ assert.equal(ensured.uploaded, false);
180
+ assert.equal(ensured.downloadUrl, 'https://cdn.example.com/artifacts/me.pdf');
181
+ assert.equal(ensured.item.storageStatus, 'uploaded');
182
+ assert.equal(ensured.item.fileUrl, 'https://cdn.example.com/artifacts/me.pdf');
183
+ });
184
+ });
185
+ async function createMethodContext() {
186
+ const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'artifacts-task-scope-'));
187
+ tempDirs.push(rootDir);
188
+ const openclawRoot = path.join(rootDir, '.openclaw');
189
+ const workspaceRoot = path.join(openclawRoot, 'workspace');
190
+ await fs.mkdir(workspaceRoot, { recursive: true });
191
+ return {
192
+ projectRoot: rootDir,
193
+ openclawRoot
194
+ };
195
+ }
196
+ async function writeApiCoreBotConfig(openclawRoot, baseUrl) {
197
+ await fs.writeFile(path.join(openclawRoot, 'openclaw.json'), JSON.stringify({
198
+ plugins: {
199
+ entries: {
200
+ 'rol-websocket-channel': {
201
+ config: {
202
+ apiCoreBot: {
203
+ baseUrl
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }, null, 2), 'utf8');
210
+ }