rol-websocket-channel 1.1.1 → 1.1.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.
|
@@ -1655,4 +1655,80 @@ openclaw admin-bridge pair <key> --endpoint https://api.deotaland.ai
|
|
|
1655
1655
|
|
|
1656
1656
|
---
|
|
1657
1657
|
|
|
1658
|
-
```
|
|
1658
|
+
```
|
|
1659
|
+
```
|
|
1660
|
+
|
|
1661
|
+
## Artifacts 文件下载(按需上传对象存储)
|
|
1662
|
+
|
|
1663
|
+
如果文件之前已经上传过,则不会重复上传,而是直接复用已有 `fileUrl`。
|
|
1664
|
+
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
|
|
1668
|
+
### 下载前先获取列表
|
|
1669
|
+
|
|
1670
|
+
建议前端不要直接拿路径去调下载,而是先调用一次 `artifactsList`,确认当前可下载文件的 `artifactId`、`relativePath`、`storageStatus`。
|
|
1671
|
+
|
|
1672
|
+
MQTT 请求示例:
|
|
1673
|
+
|
|
1674
|
+
```json
|
|
1675
|
+
{
|
|
1676
|
+
"type": "artifactsList",
|
|
1677
|
+
"trace_id": "artifact-list-before-download-001",
|
|
1678
|
+
"data": {}
|
|
1679
|
+
}
|
|
1680
|
+
```
|
|
1681
|
+
|
|
1682
|
+
典型顺序:
|
|
1683
|
+
|
|
1684
|
+
1. `artifactsList`
|
|
1685
|
+
2. 用户选择某个文件
|
|
1686
|
+
3. `artifactsEnsureUploaded`
|
|
1687
|
+
4. 使用返回的 `downloadUrl`
|
|
1688
|
+
|
|
1689
|
+
|
|
1690
|
+
|
|
1691
|
+
### MQTT 请求示例
|
|
1692
|
+
|
|
1693
|
+
```json
|
|
1694
|
+
{
|
|
1695
|
+
"type": "artifactsEnsureUploaded",
|
|
1696
|
+
"trace_id": "artifact-download-006",
|
|
1697
|
+
"data": {
|
|
1698
|
+
"baseUrl": "http://api.deotaland.local",
|
|
1699
|
+
"authToken": "123",
|
|
1700
|
+
"relativePath": "me.pdf",
|
|
1701
|
+
"presignedPostBody": {
|
|
1702
|
+
"dir": "artifacts/"
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
```
|
|
1707
|
+
|
|
1708
|
+
### 成功返回示例
|
|
1709
|
+
|
|
1710
|
+
```json
|
|
1711
|
+
{
|
|
1712
|
+
"type": "receiver",
|
|
1713
|
+
"trace_id": "artifact-download-006",
|
|
1714
|
+
"source": "system",
|
|
1715
|
+
"timestamp": 1777364603366,
|
|
1716
|
+
"success": true,
|
|
1717
|
+
"data": {
|
|
1718
|
+
"ok": true,
|
|
1719
|
+
"scope": "workspace",
|
|
1720
|
+
"uploaded": true,
|
|
1721
|
+
"artifactId": "art_fe4672f1badd",
|
|
1722
|
+
"objectKey": "uploads/666ff786-9d01-4159-b4ae-338f385d6ae2.pdf",
|
|
1723
|
+
"downloadUrl": "https://draft-user.s3.us-east-2.amazonaws.com/uploads/666ff786-9d01-4159-b4ae-338f385d6ae2.pdf"
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
```
|
|
1727
|
+
|
|
1728
|
+
### 前端使用方式
|
|
1729
|
+
|
|
1730
|
+
1. 调 `artifactsList` 获取文件列表
|
|
1731
|
+
2. 用户点击下载时,调 `artifactsEnsureUploaded`
|
|
1732
|
+
3. 直接使用返回的 `data.downloadUrl`
|
|
1733
|
+
4. 如果返回 `uploaded: false`,表示这次没有重新上传,而是复用了历史上传结果
|
|
1734
|
+
|
package/message-handler.ts
CHANGED
|
@@ -7,6 +7,15 @@ import { getContext } from './src/shared/context.js';
|
|
|
7
7
|
import { wrapAdminCall } from './src/shared/wrapper.js';
|
|
8
8
|
import { getAgents, getConfig } from './src/admin/methods/admin.js';
|
|
9
9
|
import { createAgent, deleteAgent, listAgents, updateAgent } from './src/admin/methods/agents-extended.js';
|
|
10
|
+
import {
|
|
11
|
+
createArtifactRecord,
|
|
12
|
+
ensureArtifactUploaded,
|
|
13
|
+
getArtifactContent,
|
|
14
|
+
getArtifactPresignedPost,
|
|
15
|
+
listArtifacts,
|
|
16
|
+
markArtifactUploaded,
|
|
17
|
+
refreshArtifacts
|
|
18
|
+
} from './src/admin/methods/artifacts.js';
|
|
10
19
|
import {
|
|
11
20
|
addCron,
|
|
12
21
|
disableCron,
|
|
@@ -463,6 +472,55 @@ export class MessageHandler {
|
|
|
463
472
|
});
|
|
464
473
|
}
|
|
465
474
|
|
|
475
|
+
async artifactsList(data: any): Promise<any> {
|
|
476
|
+
return wrapAdminCall(async () => {
|
|
477
|
+
const context = getContext();
|
|
478
|
+
return await listArtifacts(data, context);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async artifactsRefresh(data: any): Promise<any> {
|
|
483
|
+
return wrapAdminCall(async () => {
|
|
484
|
+
const context = getContext();
|
|
485
|
+
return await refreshArtifacts(data, context);
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async artifactsGetContent(data: any): Promise<any> {
|
|
490
|
+
return wrapAdminCall(async () => {
|
|
491
|
+
const context = getContext();
|
|
492
|
+
return await getArtifactContent(data, context);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async artifactsEnsureUploaded(data: any): Promise<any> {
|
|
497
|
+
return wrapAdminCall(async () => {
|
|
498
|
+
const context = getContext();
|
|
499
|
+
return await ensureArtifactUploaded(data, context);
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async artifactsGetPresignedPost(data: any): Promise<any> {
|
|
504
|
+
return wrapAdminCall(async () => {
|
|
505
|
+
const context = getContext();
|
|
506
|
+
return await getArtifactPresignedPost(data, context);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async artifactsCreateRecord(data: any): Promise<any> {
|
|
511
|
+
return wrapAdminCall(async () => {
|
|
512
|
+
const context = getContext();
|
|
513
|
+
return await createArtifactRecord(data, context);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async artifactsMarkUploaded(data: any): Promise<any> {
|
|
518
|
+
return wrapAdminCall(async () => {
|
|
519
|
+
const context = getContext();
|
|
520
|
+
return await markArtifactUploaded(data, context);
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
466
524
|
async mem9Install(_data: any): Promise<any> {
|
|
467
525
|
return wrapAdminCall(async () => {
|
|
468
526
|
const context = getContext();
|
package/package.json
CHANGED
|
@@ -0,0 +1,320 @@
|
|
|
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
|
+
|
|
7
|
+
import {
|
|
8
|
+
ensureArtifactUploaded,
|
|
9
|
+
listArtifacts,
|
|
10
|
+
markArtifactUploaded,
|
|
11
|
+
refreshArtifacts
|
|
12
|
+
} from './artifacts.js';
|
|
13
|
+
|
|
14
|
+
const tempDirs: string[] = [];
|
|
15
|
+
const originalFetch = globalThis.fetch;
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
globalThis.fetch = originalFetch;
|
|
19
|
+
while (tempDirs.length > 0) {
|
|
20
|
+
const dir = tempDirs.pop();
|
|
21
|
+
if (dir) {
|
|
22
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('artifacts workspace scope', () => {
|
|
28
|
+
test('only indexes allowed artifact types inside workspace', async () => {
|
|
29
|
+
const context = await createMethodContext();
|
|
30
|
+
const workspaceRoot = path.join(context.openclawRoot, 'workspace');
|
|
31
|
+
|
|
32
|
+
await fs.writeFile(path.join(workspaceRoot, 'SOUL.md'), '# root noise\n', 'utf8');
|
|
33
|
+
await fs.mkdir(path.join(workspaceRoot, 'exports'), { recursive: true });
|
|
34
|
+
await fs.mkdir(path.join(workspaceRoot, 'logs'), { recursive: true });
|
|
35
|
+
await fs.mkdir(path.join(workspaceRoot, 'nested', 'media'), { recursive: true });
|
|
36
|
+
|
|
37
|
+
await fs.writeFile(
|
|
38
|
+
path.join(workspaceRoot, 'exports', 'preview.png'),
|
|
39
|
+
'png-data',
|
|
40
|
+
'utf8'
|
|
41
|
+
);
|
|
42
|
+
await fs.writeFile(
|
|
43
|
+
path.join(workspaceRoot, 'exports', 'report.pdf'),
|
|
44
|
+
'pdf-data',
|
|
45
|
+
'utf8'
|
|
46
|
+
);
|
|
47
|
+
await fs.writeFile(
|
|
48
|
+
path.join(workspaceRoot, 'nested', 'media', 'archive.zip'),
|
|
49
|
+
'zip-data',
|
|
50
|
+
'utf8'
|
|
51
|
+
);
|
|
52
|
+
await fs.writeFile(
|
|
53
|
+
path.join(workspaceRoot, 'nested', 'media', 'video.mp4'),
|
|
54
|
+
'mp4-data',
|
|
55
|
+
'utf8'
|
|
56
|
+
);
|
|
57
|
+
await fs.writeFile(
|
|
58
|
+
path.join(workspaceRoot, 'exports', 'spec.docx'),
|
|
59
|
+
'docx-data',
|
|
60
|
+
'utf8'
|
|
61
|
+
);
|
|
62
|
+
await fs.writeFile(
|
|
63
|
+
path.join(workspaceRoot, 'exports', 'ignored.md'),
|
|
64
|
+
'# markdown noise\n',
|
|
65
|
+
'utf8'
|
|
66
|
+
);
|
|
67
|
+
await fs.writeFile(
|
|
68
|
+
path.join(workspaceRoot, 'exports', 'ignored.json'),
|
|
69
|
+
'{"noise":true}\n',
|
|
70
|
+
'utf8'
|
|
71
|
+
);
|
|
72
|
+
await fs.writeFile(
|
|
73
|
+
path.join(workspaceRoot, 'logs', 'ignored.pdf'),
|
|
74
|
+
'log pdf noise',
|
|
75
|
+
'utf8'
|
|
76
|
+
);
|
|
77
|
+
await fs.writeFile(
|
|
78
|
+
path.join(workspaceRoot, 'artifacts.json'),
|
|
79
|
+
'[]',
|
|
80
|
+
'utf8'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const result = await refreshArtifacts({}, context) as {
|
|
84
|
+
scope: string;
|
|
85
|
+
count: number;
|
|
86
|
+
items: Array<{ relativePath: string; fileName: string }>;
|
|
87
|
+
manifestPath: string;
|
|
88
|
+
workspaceRoot: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
assert.equal(result.scope, 'workspace');
|
|
92
|
+
assert.equal(result.count, 5);
|
|
93
|
+
assert.deepEqual(
|
|
94
|
+
result.items.map((item) => item.relativePath),
|
|
95
|
+
[
|
|
96
|
+
'nested/media/archive.zip',
|
|
97
|
+
'exports/preview.png',
|
|
98
|
+
'exports/report.pdf',
|
|
99
|
+
'exports/spec.docx',
|
|
100
|
+
'nested/media/video.mp4'
|
|
101
|
+
]
|
|
102
|
+
);
|
|
103
|
+
assert.equal(result.workspaceRoot, workspaceRoot);
|
|
104
|
+
assert.equal(
|
|
105
|
+
result.manifestPath,
|
|
106
|
+
path.join(workspaceRoot, 'artifacts.json')
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('list reads workspace manifest without taskId', async () => {
|
|
111
|
+
const context = await createMethodContext();
|
|
112
|
+
const workspaceRoot = path.join(context.openclawRoot, 'workspace');
|
|
113
|
+
|
|
114
|
+
await fs.mkdir(path.join(workspaceRoot, 'exports'), { recursive: true });
|
|
115
|
+
await fs.writeFile(path.join(workspaceRoot, 'exports', 'me.pdf'), 'pdf-data', 'utf8');
|
|
116
|
+
|
|
117
|
+
const refreshed = await refreshArtifacts({}, context) as {
|
|
118
|
+
count: number;
|
|
119
|
+
items: Array<{ relativePath: string }>;
|
|
120
|
+
};
|
|
121
|
+
assert.equal(refreshed.count, 1);
|
|
122
|
+
|
|
123
|
+
const listed = await listArtifacts({ refresh: false }, context) as {
|
|
124
|
+
scope: string;
|
|
125
|
+
count: number;
|
|
126
|
+
items: Array<{ relativePath: string }>;
|
|
127
|
+
manifestPath: string;
|
|
128
|
+
workspaceRoot: string;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
assert.equal(listed.scope, 'workspace');
|
|
132
|
+
assert.equal(listed.count, 1);
|
|
133
|
+
assert.deepEqual(listed.items.map((item) => item.relativePath), ['exports/me.pdf']);
|
|
134
|
+
assert.equal(listed.manifestPath, path.join(workspaceRoot, 'artifacts.json'));
|
|
135
|
+
assert.equal(listed.workspaceRoot, workspaceRoot);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('ensureUploaded uploads local artifact and updates manifest', async () => {
|
|
139
|
+
const context = await createMethodContext();
|
|
140
|
+
const workspaceRoot = path.join(context.openclawRoot, 'workspace');
|
|
141
|
+
const artifactPath = path.join(workspaceRoot, 'exports', 'me.pdf');
|
|
142
|
+
|
|
143
|
+
await writeApiCoreBotConfig(context.openclawRoot, 'https://api.example.com');
|
|
144
|
+
await fs.mkdir(path.dirname(artifactPath), { recursive: true });
|
|
145
|
+
await fs.writeFile(artifactPath, 'pdf-data', 'utf8');
|
|
146
|
+
|
|
147
|
+
const refreshed = await refreshArtifacts({}, context) as {
|
|
148
|
+
items: Array<{ id: string; relativePath: string }>;
|
|
149
|
+
};
|
|
150
|
+
const artifactId = refreshed.items[0]?.id;
|
|
151
|
+
assert.ok(artifactId);
|
|
152
|
+
|
|
153
|
+
const calls: Array<{ url: string; method: string }> = [];
|
|
154
|
+
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
155
|
+
const url = typeof input === 'string'
|
|
156
|
+
? input
|
|
157
|
+
: input instanceof URL
|
|
158
|
+
? input.toString()
|
|
159
|
+
: input.url;
|
|
160
|
+
const method = init?.method ?? (input instanceof Request ? input.method : 'GET');
|
|
161
|
+
calls.push({ url, method });
|
|
162
|
+
|
|
163
|
+
if (url === 'https://api.example.com/api-core-bot/front/s3/get-presigned-post') {
|
|
164
|
+
assert.equal(init?.body !== undefined, true);
|
|
165
|
+
const parsedBody = JSON.parse(String(init?.body)) as Record<string, unknown>;
|
|
166
|
+
assert.equal(parsedBody.dir, 'artifacts/');
|
|
167
|
+
assert.equal(parsedBody.filename, 'me.pdf');
|
|
168
|
+
assert.equal(parsedBody.file_name, undefined);
|
|
169
|
+
return new Response(JSON.stringify({
|
|
170
|
+
code: 0,
|
|
171
|
+
message: '',
|
|
172
|
+
success: true,
|
|
173
|
+
data: {
|
|
174
|
+
url: 'https://upload.example.com',
|
|
175
|
+
fields: {
|
|
176
|
+
key: 'artifacts/me.pdf',
|
|
177
|
+
policy: 'policy-token'
|
|
178
|
+
},
|
|
179
|
+
file_key: 'artifacts/me.pdf',
|
|
180
|
+
file_url: 'https://cdn.example.com/artifacts/me.pdf'
|
|
181
|
+
}
|
|
182
|
+
}), {
|
|
183
|
+
status: 200,
|
|
184
|
+
headers: { 'Content-Type': 'application/json' }
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (url === 'https://upload.example.com') {
|
|
189
|
+
assert.ok(init?.body instanceof FormData);
|
|
190
|
+
return new Response(null, { status: 204 });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
throw new Error(`Unexpected fetch call: ${url}`);
|
|
194
|
+
}) as typeof globalThis.fetch;
|
|
195
|
+
|
|
196
|
+
const ensured = await ensureArtifactUploaded({
|
|
197
|
+
artifactId,
|
|
198
|
+
presignedPostBody: {
|
|
199
|
+
dir: 'artifacts/'
|
|
200
|
+
}
|
|
201
|
+
}, context) as {
|
|
202
|
+
ok: boolean;
|
|
203
|
+
uploaded: boolean;
|
|
204
|
+
downloadUrl: string;
|
|
205
|
+
objectKey: string;
|
|
206
|
+
item: {
|
|
207
|
+
storageStatus: string;
|
|
208
|
+
fileUrl: string | null;
|
|
209
|
+
objectKey: string | null;
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
assert.equal(ensured.ok, true);
|
|
214
|
+
assert.equal(ensured.uploaded, true);
|
|
215
|
+
assert.equal(ensured.downloadUrl, 'https://cdn.example.com/artifacts/me.pdf');
|
|
216
|
+
assert.equal(ensured.objectKey, 'artifacts/me.pdf');
|
|
217
|
+
assert.equal(ensured.item.storageStatus, 'uploaded');
|
|
218
|
+
assert.equal(ensured.item.fileUrl, 'https://cdn.example.com/artifacts/me.pdf');
|
|
219
|
+
assert.equal(ensured.item.objectKey, 'artifacts/me.pdf');
|
|
220
|
+
assert.deepEqual(calls, [
|
|
221
|
+
{
|
|
222
|
+
url: 'https://api.example.com/api-core-bot/front/s3/get-presigned-post',
|
|
223
|
+
method: 'POST'
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
url: 'https://upload.example.com',
|
|
227
|
+
method: 'POST'
|
|
228
|
+
}
|
|
229
|
+
]);
|
|
230
|
+
|
|
231
|
+
const listed = await listArtifacts({ refresh: false }, context) as {
|
|
232
|
+
items: Array<{
|
|
233
|
+
relativePath: string;
|
|
234
|
+
storageStatus: string;
|
|
235
|
+
fileUrl: string | null;
|
|
236
|
+
objectKey: string | null;
|
|
237
|
+
}>;
|
|
238
|
+
};
|
|
239
|
+
assert.equal(listed.items.length, 1);
|
|
240
|
+
assert.equal(listed.items[0]?.relativePath, 'exports/me.pdf');
|
|
241
|
+
assert.equal(listed.items[0]?.storageStatus, 'uploaded');
|
|
242
|
+
assert.equal(listed.items[0]?.fileUrl, 'https://cdn.example.com/artifacts/me.pdf');
|
|
243
|
+
assert.equal(listed.items[0]?.objectKey, 'artifacts/me.pdf');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('ensureUploaded reuses existing uploaded artifact without fetching', async () => {
|
|
247
|
+
const context = await createMethodContext();
|
|
248
|
+
const workspaceRoot = path.join(context.openclawRoot, 'workspace');
|
|
249
|
+
const artifactPath = path.join(workspaceRoot, 'exports', 'me.pdf');
|
|
250
|
+
|
|
251
|
+
await fs.mkdir(path.dirname(artifactPath), { recursive: true });
|
|
252
|
+
await fs.writeFile(artifactPath, 'pdf-data', 'utf8');
|
|
253
|
+
|
|
254
|
+
const refreshed = await refreshArtifacts({}, context) as {
|
|
255
|
+
items: Array<{ id: string }>;
|
|
256
|
+
};
|
|
257
|
+
const artifactId = refreshed.items[0]?.id;
|
|
258
|
+
assert.ok(artifactId);
|
|
259
|
+
|
|
260
|
+
await markArtifactUploaded({
|
|
261
|
+
artifactId,
|
|
262
|
+
objectKey: 'artifacts/me.pdf',
|
|
263
|
+
fileUrl: 'https://cdn.example.com/artifacts/me.pdf'
|
|
264
|
+
}, context);
|
|
265
|
+
|
|
266
|
+
globalThis.fetch = (async () => {
|
|
267
|
+
throw new Error('fetch should not be called for existing uploaded artifact');
|
|
268
|
+
}) as typeof globalThis.fetch;
|
|
269
|
+
|
|
270
|
+
const ensured = await ensureArtifactUploaded({ artifactId }, context) as {
|
|
271
|
+
ok: boolean;
|
|
272
|
+
uploaded: boolean;
|
|
273
|
+
downloadUrl: string;
|
|
274
|
+
item: {
|
|
275
|
+
storageStatus: string;
|
|
276
|
+
fileUrl: string | null;
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
assert.equal(ensured.ok, true);
|
|
281
|
+
assert.equal(ensured.uploaded, false);
|
|
282
|
+
assert.equal(ensured.downloadUrl, 'https://cdn.example.com/artifacts/me.pdf');
|
|
283
|
+
assert.equal(ensured.item.storageStatus, 'uploaded');
|
|
284
|
+
assert.equal(ensured.item.fileUrl, 'https://cdn.example.com/artifacts/me.pdf');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
async function createMethodContext(): Promise<{ projectRoot: string; openclawRoot: string }> {
|
|
289
|
+
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'artifacts-task-scope-'));
|
|
290
|
+
tempDirs.push(rootDir);
|
|
291
|
+
|
|
292
|
+
const openclawRoot = path.join(rootDir, '.openclaw');
|
|
293
|
+
const workspaceRoot = path.join(openclawRoot, 'workspace');
|
|
294
|
+
await fs.mkdir(workspaceRoot, { recursive: true });
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
projectRoot: rootDir,
|
|
298
|
+
openclawRoot
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function writeApiCoreBotConfig(openclawRoot: string, baseUrl: string): Promise<void> {
|
|
303
|
+
await fs.writeFile(
|
|
304
|
+
path.join(openclawRoot, 'openclaw.json'),
|
|
305
|
+
JSON.stringify({
|
|
306
|
+
plugins: {
|
|
307
|
+
entries: {
|
|
308
|
+
'rol-websocket-channel': {
|
|
309
|
+
config: {
|
|
310
|
+
apiCoreBot: {
|
|
311
|
+
baseUrl
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}, null, 2),
|
|
318
|
+
'utf8'
|
|
319
|
+
);
|
|
320
|
+
}
|