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.
- 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
- package/dist/index.js +617 -617
- package/dist/message-handler.js +515 -503
- package/dist/src/admin/cli.js +43 -43
- package/dist/src/admin/jsonrpc.js +60 -60
- package/dist/src/admin/lib/fs.js +30 -30
- package/dist/src/admin/lib/paths.js +80 -80
- package/dist/src/admin/methods/admin.js +60 -60
- package/dist/src/admin/methods/agents-extended.js +251 -251
- package/dist/src/admin/methods/artifacts.js +736 -642
- package/dist/src/admin/methods/artifacts.test.js +210 -191
- package/dist/src/admin/methods/cron.js +250 -250
- package/dist/src/admin/methods/index.js +104 -102
- package/dist/src/admin/methods/mem9.js +309 -270
- package/dist/src/admin/methods/mem9.test.js +34 -0
- package/dist/src/admin/methods/memory.js +363 -363
- package/dist/src/admin/methods/models-extended.js +190 -190
- package/dist/src/admin/methods/models.js +195 -195
- package/dist/src/admin/methods/pairing.js +268 -268
- package/dist/src/admin/methods/sessions-extended.js +215 -215
- package/dist/src/admin/methods/sessions.js +75 -75
- package/dist/src/admin/methods/skills-extended.js +157 -157
- package/dist/src/admin/methods/skills-toggle.js +183 -183
- package/dist/src/admin/methods/skills.js +528 -528
- package/dist/src/admin/methods/system.js +271 -180
- package/dist/src/admin/methods/usage.js +1170 -1170
- package/dist/src/admin/types.js +1 -1
- package/dist/src/mqtt/connection-manager.js +209 -209
- package/dist/src/mqtt/index.js +5 -5
- package/dist/src/mqtt/mqtt-client.js +110 -110
- package/dist/src/mqtt/mqtt.test.js +418 -418
- package/dist/src/mqtt/types.js +2 -2
- package/dist/src/shared/context.js +24 -24
- package/dist/src/shared/wrapper.js +23 -23
- package/message-handler.ts +15 -1
- package/openclaw.plugin.json +73 -0
- package/package.json +1 -1
- package/src/admin/methods/artifacts.test.ts +35 -0
- package/src/admin/methods/artifacts.ts +140 -2
- package/src/admin/methods/index.ts +3 -1
- package/src/admin/methods/mem9.test.ts +39 -0
- package/src/admin/methods/mem9.ts +48 -1
- package/src/admin/methods/system.ts +129 -1
|
@@ -1,642 +1,736 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { ensureDir, pathExists, readJsonFile } from '../lib/fs.js';
|
|
5
|
-
import { ensureInside } from '../lib/paths.js';
|
|
6
|
-
import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
|
|
7
|
-
const INLINE_CONTENT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
8
|
-
const MIN_ARTIFACT_SIZE_BYTES = 1;
|
|
9
|
-
const ARTIFACT_MANIFEST_FILE = 'artifacts.json';
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'.
|
|
39
|
-
'.
|
|
40
|
-
'.
|
|
41
|
-
'.
|
|
42
|
-
'.
|
|
43
|
-
'.
|
|
44
|
-
'.
|
|
45
|
-
'.
|
|
46
|
-
'.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'.
|
|
50
|
-
'.
|
|
51
|
-
'.
|
|
52
|
-
'.
|
|
53
|
-
'.
|
|
54
|
-
'.
|
|
55
|
-
'.
|
|
56
|
-
'.
|
|
57
|
-
'.
|
|
58
|
-
'.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
'.
|
|
62
|
-
'.
|
|
63
|
-
'.
|
|
64
|
-
'.
|
|
65
|
-
'.
|
|
66
|
-
'.
|
|
67
|
-
'.
|
|
68
|
-
'.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
const manifest =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
return
|
|
489
|
-
}
|
|
490
|
-
function
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { ensureDir, pathExists, readJsonFile } from '../lib/fs.js';
|
|
5
|
+
import { ensureInside } from '../lib/paths.js';
|
|
6
|
+
import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
|
|
7
|
+
const INLINE_CONTENT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
8
|
+
const MIN_ARTIFACT_SIZE_BYTES = 1;
|
|
9
|
+
const ARTIFACT_MANIFEST_FILE = 'artifacts.json';
|
|
10
|
+
const MARKDOWN_SCAN_STATE_FILE = 'md-scan-state.json';
|
|
11
|
+
const PLUGIN_WORKSPACE_STATE_DIR = path.join('.openclaw', 'rol-websocket-channel');
|
|
12
|
+
const IGNORE_EXTENSIONS = new Set(['.tmp', '.part', '.crdownload']);
|
|
13
|
+
const IGNORE_FILE_NAMES = new Set(['.ds_store', 'thumbs.db', ARTIFACT_MANIFEST_FILE]);
|
|
14
|
+
const IGNORE_MARKDOWN_FILE_NAMES = new Set([
|
|
15
|
+
'agents.md',
|
|
16
|
+
'heartbeat.md',
|
|
17
|
+
'identity.md',
|
|
18
|
+
'memory.md',
|
|
19
|
+
'soul.md',
|
|
20
|
+
'tools.md',
|
|
21
|
+
'user.md'
|
|
22
|
+
]);
|
|
23
|
+
const IGNORE_DIRECTORY_NAMES = new Set([
|
|
24
|
+
'.git',
|
|
25
|
+
'.cache',
|
|
26
|
+
'cache',
|
|
27
|
+
'caches',
|
|
28
|
+
'tmp',
|
|
29
|
+
'temp',
|
|
30
|
+
'logs',
|
|
31
|
+
'log',
|
|
32
|
+
'node_modules',
|
|
33
|
+
'sessions',
|
|
34
|
+
'session',
|
|
35
|
+
'history'
|
|
36
|
+
]);
|
|
37
|
+
const CATEGORY_BY_EXTENSION = {
|
|
38
|
+
'.png': 'image',
|
|
39
|
+
'.jpg': 'image',
|
|
40
|
+
'.jpeg': 'image',
|
|
41
|
+
'.webp': 'image',
|
|
42
|
+
'.gif': 'image',
|
|
43
|
+
'.svg': 'image',
|
|
44
|
+
'.mp4': 'video',
|
|
45
|
+
'.mov': 'video',
|
|
46
|
+
'.webm': 'video',
|
|
47
|
+
'.avi': 'video',
|
|
48
|
+
'.mkv': 'video',
|
|
49
|
+
'.m4v': 'video',
|
|
50
|
+
'.pdf': 'document',
|
|
51
|
+
'.doc': 'document',
|
|
52
|
+
'.docx': 'document',
|
|
53
|
+
'.md': 'document',
|
|
54
|
+
'.zip': 'archive',
|
|
55
|
+
'.7z': 'archive',
|
|
56
|
+
'.tar': 'archive',
|
|
57
|
+
'.gz': 'archive',
|
|
58
|
+
'.rar': 'archive'
|
|
59
|
+
};
|
|
60
|
+
const MIME_BY_EXTENSION = {
|
|
61
|
+
'.png': 'image/png',
|
|
62
|
+
'.jpg': 'image/jpeg',
|
|
63
|
+
'.jpeg': 'image/jpeg',
|
|
64
|
+
'.webp': 'image/webp',
|
|
65
|
+
'.gif': 'image/gif',
|
|
66
|
+
'.svg': 'image/svg+xml',
|
|
67
|
+
'.mp4': 'video/mp4',
|
|
68
|
+
'.mov': 'video/quicktime',
|
|
69
|
+
'.webm': 'video/webm',
|
|
70
|
+
'.avi': 'video/x-msvideo',
|
|
71
|
+
'.mkv': 'video/x-matroska',
|
|
72
|
+
'.m4v': 'video/x-m4v',
|
|
73
|
+
'.pdf': 'application/pdf',
|
|
74
|
+
'.doc': 'application/msword',
|
|
75
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
76
|
+
'.md': 'text/markdown',
|
|
77
|
+
'.zip': 'application/zip',
|
|
78
|
+
'.7z': 'application/x-7z-compressed',
|
|
79
|
+
'.tar': 'application/x-tar',
|
|
80
|
+
'.gz': 'application/gzip',
|
|
81
|
+
'.rar': 'application/vnd.rar'
|
|
82
|
+
};
|
|
83
|
+
export const listArtifacts = async (params, context) => {
|
|
84
|
+
const objectParams = expectOptionalObject(params);
|
|
85
|
+
const refresh = objectParams.refresh !== false;
|
|
86
|
+
const manifest = refresh
|
|
87
|
+
? await refreshArtifactManifest(context)
|
|
88
|
+
: await readOrRefreshArtifactManifest(context);
|
|
89
|
+
return {
|
|
90
|
+
scope: 'workspace',
|
|
91
|
+
count: manifest.items.length,
|
|
92
|
+
manifestPath: manifest.manifestPath,
|
|
93
|
+
workspaceRoot: manifest.workspacePaths.workspaceRoot,
|
|
94
|
+
items: manifest.items.map(artifactToJsonValue)
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
export const refreshArtifacts = async (params, context) => {
|
|
98
|
+
const objectParams = expectOptionalObject(params);
|
|
99
|
+
const manifest = await refreshArtifactManifest(context);
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
scope: 'workspace',
|
|
103
|
+
count: manifest.items.length,
|
|
104
|
+
manifestPath: manifest.manifestPath,
|
|
105
|
+
workspaceRoot: manifest.workspacePaths.workspaceRoot,
|
|
106
|
+
items: manifest.items.map(artifactToJsonValue)
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
export const getArtifactContent = async (params, context) => {
|
|
110
|
+
const objectParams = expectObject(params);
|
|
111
|
+
const maxInlineBytes = normalizeMaxInlineBytes(objectParams.maxInlineBytes);
|
|
112
|
+
const artifact = await resolveArtifact(objectParams, context);
|
|
113
|
+
if (artifact.sizeBytes > maxInlineBytes) {
|
|
114
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Artifact is too large for inline transfer', {
|
|
115
|
+
artifactId: artifact.id,
|
|
116
|
+
sizeBytes: artifact.sizeBytes,
|
|
117
|
+
maxInlineBytes
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const content = await fs.readFile(artifact.localPath);
|
|
121
|
+
return {
|
|
122
|
+
scope: 'workspace',
|
|
123
|
+
artifact: {
|
|
124
|
+
...artifactToJsonValue(artifact),
|
|
125
|
+
contentBase64: content.toString('base64')
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
export const ensureArtifactUploaded = async (params, context) => {
|
|
130
|
+
const objectParams = expectObject(params);
|
|
131
|
+
const artifact = await resolveArtifact(objectParams, context);
|
|
132
|
+
if (artifact.storageStatus === 'uploaded' && artifact.fileUrl) {
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
scope: 'workspace',
|
|
136
|
+
uploaded: false,
|
|
137
|
+
artifactId: artifact.id,
|
|
138
|
+
objectKey: artifact.objectKey ?? null,
|
|
139
|
+
downloadUrl: artifact.fileUrl,
|
|
140
|
+
item: artifactToJsonValue(artifact)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const endpointConfig = await resolveApiCoreBotEndpoint(context.openclawRoot, objectParams.baseUrl, objectParams.authToken);
|
|
144
|
+
const presignedPostBody = buildPresignedPostBody(objectParams.presignedPostBody, artifact);
|
|
145
|
+
const presignedResponse = await postJson(`${endpointConfig.baseUrl}/api-core-bot/front/s3/get-presigned-post`, presignedPostBody, endpointConfig.authToken);
|
|
146
|
+
const uploadTarget = resolvePresignedPostUploadTarget(presignedResponse);
|
|
147
|
+
await uploadArtifactToPresignedPost(uploadTarget, artifact);
|
|
148
|
+
const objectKey = uploadTarget.objectKey ?? artifact.objectKey ?? null;
|
|
149
|
+
const fileUrl = uploadTarget.fileUrl ?? artifact.fileUrl ?? buildPublicFileUrl(uploadTarget.uploadUrl, objectKey);
|
|
150
|
+
if (!fileUrl) {
|
|
151
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'Unable to determine uploaded artifact download URL', {
|
|
152
|
+
artifactId: artifact.id,
|
|
153
|
+
objectKey
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const manifest = await readOrRefreshArtifactManifest(context);
|
|
157
|
+
const updated = await persistUploadedArtifact(manifest.manifestPath, manifest.items, artifact.id, {
|
|
158
|
+
objectKey,
|
|
159
|
+
fileUrl,
|
|
160
|
+
record: artifact.record
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
ok: true,
|
|
164
|
+
scope: 'workspace',
|
|
165
|
+
uploaded: true,
|
|
166
|
+
artifactId: artifact.id,
|
|
167
|
+
objectKey,
|
|
168
|
+
downloadUrl: fileUrl,
|
|
169
|
+
item: artifactToJsonValue(updated)
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
export const getArtifactPresignedPost = async (params, context) => {
|
|
173
|
+
const objectParams = expectObject(params);
|
|
174
|
+
const body = isObject(objectParams.body) ? objectParams.body : null;
|
|
175
|
+
if (!body) {
|
|
176
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Missing required parameter: body');
|
|
177
|
+
}
|
|
178
|
+
const endpointConfig = await resolveApiCoreBotEndpoint(context.openclawRoot, objectParams.baseUrl, objectParams.authToken);
|
|
179
|
+
const response = await postJson(`${endpointConfig.baseUrl}/api-core-bot/front/s3/get-presigned-post`, body, endpointConfig.authToken);
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
endpoint: '/api-core-bot/front/s3/get-presigned-post',
|
|
183
|
+
data: response
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
export const createArtifactRecord = async (params, context) => {
|
|
187
|
+
const objectParams = expectObject(params);
|
|
188
|
+
const body = isObject(objectParams.body) ? objectParams.body : null;
|
|
189
|
+
if (!body) {
|
|
190
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Missing required parameter: body');
|
|
191
|
+
}
|
|
192
|
+
const endpoint = expectString(objectParams.endpoint, 'endpoint');
|
|
193
|
+
if (!endpoint.startsWith('/api-core-bot/front/')) {
|
|
194
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'endpoint must start with /api-core-bot/front/');
|
|
195
|
+
}
|
|
196
|
+
const endpointConfig = await resolveApiCoreBotEndpoint(context.openclawRoot, objectParams.baseUrl, objectParams.authToken);
|
|
197
|
+
const response = await postJson(`${endpointConfig.baseUrl}${endpoint}`, body, endpointConfig.authToken);
|
|
198
|
+
return {
|
|
199
|
+
ok: true,
|
|
200
|
+
endpoint,
|
|
201
|
+
data: response
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
export const markArtifactUploaded = async (params, context) => {
|
|
205
|
+
const objectParams = expectObject(params);
|
|
206
|
+
const objectKey = optionalTrimmedString(objectParams.objectKey);
|
|
207
|
+
const fileUrl = optionalTrimmedString(objectParams.fileUrl);
|
|
208
|
+
if (!objectKey && !fileUrl) {
|
|
209
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'At least one of objectKey or fileUrl is required');
|
|
210
|
+
}
|
|
211
|
+
const manifest = await readOrRefreshArtifactManifest(context);
|
|
212
|
+
const artifact = findArtifact(manifest.items, objectParams);
|
|
213
|
+
if (!artifact) {
|
|
214
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Artifact not found', {
|
|
215
|
+
artifactId: objectParams.artifactId,
|
|
216
|
+
relativePath: objectParams.relativePath
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
const updated = await persistUploadedArtifact(manifest.manifestPath, manifest.items, artifact.id, {
|
|
220
|
+
objectKey: objectKey ?? artifact.objectKey ?? null,
|
|
221
|
+
fileUrl: fileUrl ?? artifact.fileUrl ?? null,
|
|
222
|
+
record: objectParams.record === undefined ? artifact.record : objectParams.record
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
ok: true,
|
|
226
|
+
scope: 'workspace',
|
|
227
|
+
artifactId: artifact.id,
|
|
228
|
+
item: artifactToJsonValue(updated)
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
export function classifyArtifactCategory(fileName) {
|
|
232
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
233
|
+
return CATEGORY_BY_EXTENSION[ext] ?? 'other';
|
|
234
|
+
}
|
|
235
|
+
export function shouldIgnoreArtifactFile(fileName) {
|
|
236
|
+
const normalized = fileName.trim().toLowerCase();
|
|
237
|
+
if (!normalized) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (normalized.startsWith('.')) {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
if (IGNORE_FILE_NAMES.has(normalized)) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
const ext = path.extname(normalized);
|
|
247
|
+
return IGNORE_EXTENSIONS.has(ext);
|
|
248
|
+
}
|
|
249
|
+
async function refreshArtifactManifest(context) {
|
|
250
|
+
const workspacePaths = await ensureWorkspacePaths(context.openclawRoot);
|
|
251
|
+
const { workspaceRoot, manifestPath } = workspacePaths;
|
|
252
|
+
const existing = await readExistingManifest(manifestPath);
|
|
253
|
+
const existingByPath = new Map(existing.map((item) => [item.relativePath, item]));
|
|
254
|
+
const loadedMarkdownScanState = await readMarkdownScanState(workspacePaths.markdownScanStatePath);
|
|
255
|
+
const hadMarkdownScanState = loadedMarkdownScanState !== null;
|
|
256
|
+
const markdownScanState = loadedMarkdownScanState ?? createMarkdownScanState();
|
|
257
|
+
const files = await collectArtifactFiles(workspaceRoot);
|
|
258
|
+
const items = [];
|
|
259
|
+
for (const fullPath of files) {
|
|
260
|
+
const stat = await fs.stat(fullPath);
|
|
261
|
+
if (!stat.isFile() || stat.size < MIN_ARTIFACT_SIZE_BYTES) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const relativePath = path.relative(workspaceRoot, fullPath).replace(/\\/g, '/');
|
|
265
|
+
const existingItem = existingByPath.get(relativePath);
|
|
266
|
+
const fileName = path.basename(fullPath);
|
|
267
|
+
const isMarkdown = isMarkdownArtifactFile(fileName);
|
|
268
|
+
if (isMarkdown) {
|
|
269
|
+
if (!shouldIncludeMarkdownArtifactFile(relativePath, fileName, stat, markdownScanState, hadMarkdownScanState)) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else if (!shouldIncludeArtifactFile(fileName)) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const ext = normalizeExtension(fileName);
|
|
277
|
+
const createdAt = normalizeTimestamp(stat.birthtime, stat.mtime);
|
|
278
|
+
const updatedAt = stat.mtime.toISOString();
|
|
279
|
+
items.push({
|
|
280
|
+
id: existingItem?.id ?? buildArtifactId(relativePath),
|
|
281
|
+
fileName,
|
|
282
|
+
relativePath,
|
|
283
|
+
localPath: fullPath,
|
|
284
|
+
category: classifyArtifactCategory(fileName),
|
|
285
|
+
mimeType: MIME_BY_EXTENSION[ext ?? ''] ?? 'application/octet-stream',
|
|
286
|
+
ext,
|
|
287
|
+
sizeBytes: stat.size,
|
|
288
|
+
storageStatus: existingItem?.storageStatus ?? 'local_only',
|
|
289
|
+
source: existingItem?.source ?? 'generated',
|
|
290
|
+
previewable: isPreviewableCategory(classifyArtifactCategory(fileName)),
|
|
291
|
+
createdAt: existingItem?.createdAt ?? createdAt,
|
|
292
|
+
updatedAt,
|
|
293
|
+
objectKey: existingItem?.objectKey ?? null,
|
|
294
|
+
fileUrl: existingItem?.fileUrl ?? null,
|
|
295
|
+
uploadedAt: existingItem?.uploadedAt ?? null,
|
|
296
|
+
record: existingItem?.record
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
items.sort((a, b) => a.fileName.localeCompare(b.fileName));
|
|
300
|
+
markdownScanState.lastScanAt = new Date().toISOString();
|
|
301
|
+
await writeMarkdownScanState(workspacePaths.markdownScanStatePath, markdownScanState);
|
|
302
|
+
await writeArtifactManifest(manifestPath, items);
|
|
303
|
+
return { manifestPath, items, workspacePaths };
|
|
304
|
+
}
|
|
305
|
+
async function readOrRefreshArtifactManifest(context) {
|
|
306
|
+
const workspacePaths = await ensureWorkspacePaths(context.openclawRoot);
|
|
307
|
+
const { manifestPath } = workspacePaths;
|
|
308
|
+
if (!(await pathExists(manifestPath))) {
|
|
309
|
+
return await refreshArtifactManifest(context);
|
|
310
|
+
}
|
|
311
|
+
const items = await readExistingManifest(manifestPath);
|
|
312
|
+
return { manifestPath, items, workspacePaths };
|
|
313
|
+
}
|
|
314
|
+
async function readExistingManifest(manifestPath) {
|
|
315
|
+
if (!(await pathExists(manifestPath))) {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
const parsed = await readJsonFile(manifestPath);
|
|
319
|
+
if (!Array.isArray(parsed)) {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
const items = [];
|
|
323
|
+
for (const item of parsed) {
|
|
324
|
+
if (isArtifactRecord(item)) {
|
|
325
|
+
items.push(item);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return items;
|
|
329
|
+
}
|
|
330
|
+
async function writeArtifactManifest(manifestPath, items) {
|
|
331
|
+
await fs.writeFile(manifestPath, JSON.stringify(items.map((item) => artifactToJsonValue(item)), null, 2), 'utf8');
|
|
332
|
+
}
|
|
333
|
+
async function readMarkdownScanState(statePath) {
|
|
334
|
+
if (!(await pathExists(statePath))) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
const parsed = await readJsonFile(statePath);
|
|
338
|
+
if (!isObject(parsed)) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const files = {};
|
|
342
|
+
if (isObject(parsed.files)) {
|
|
343
|
+
for (const [relativePath, value] of Object.entries(parsed.files)) {
|
|
344
|
+
if (isMarkdownScanStateFile(value)) {
|
|
345
|
+
files[relativePath] = value;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
initializedAt: typeof parsed.initializedAt === 'string' ? parsed.initializedAt : new Date().toISOString(),
|
|
351
|
+
lastScanAt: typeof parsed.lastScanAt === 'string' ? parsed.lastScanAt : new Date().toISOString(),
|
|
352
|
+
files
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
async function writeMarkdownScanState(statePath, state) {
|
|
356
|
+
await ensureDir(path.dirname(statePath));
|
|
357
|
+
await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
358
|
+
}
|
|
359
|
+
async function persistUploadedArtifact(manifestPath, items, artifactId, updates) {
|
|
360
|
+
const now = new Date().toISOString();
|
|
361
|
+
let updatedArtifact = null;
|
|
362
|
+
const updatedItems = items.map((item) => {
|
|
363
|
+
if (!isArtifactRecord(item) || item.id !== artifactId) {
|
|
364
|
+
return item;
|
|
365
|
+
}
|
|
366
|
+
updatedArtifact = {
|
|
367
|
+
...item,
|
|
368
|
+
storageStatus: 'uploaded',
|
|
369
|
+
objectKey: updates.objectKey ?? item.objectKey ?? null,
|
|
370
|
+
fileUrl: updates.fileUrl ?? item.fileUrl ?? null,
|
|
371
|
+
uploadedAt: now,
|
|
372
|
+
updatedAt: now,
|
|
373
|
+
...(updates.record === undefined ? {} : { record: updates.record })
|
|
374
|
+
};
|
|
375
|
+
return updatedArtifact;
|
|
376
|
+
});
|
|
377
|
+
await writeArtifactManifest(manifestPath, updatedItems);
|
|
378
|
+
if (!updatedArtifact) {
|
|
379
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'Artifact not found while persisting upload state', {
|
|
380
|
+
artifactId
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
return updatedArtifact;
|
|
384
|
+
}
|
|
385
|
+
async function collectArtifactFiles(rootDir) {
|
|
386
|
+
const files = [];
|
|
387
|
+
const entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
388
|
+
for (const entry of entries) {
|
|
389
|
+
if (entry.isDirectory() && shouldIgnoreArtifactDirectory(entry.name)) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (entry.isFile() && shouldIgnoreArtifactFile(entry.name)) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const fullPath = path.join(rootDir, entry.name);
|
|
396
|
+
if (entry.isDirectory()) {
|
|
397
|
+
files.push(...await collectArtifactFiles(fullPath));
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (entry.isFile()) {
|
|
401
|
+
files.push(fullPath);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return files;
|
|
405
|
+
}
|
|
406
|
+
async function resolveArtifact(params, context) {
|
|
407
|
+
const manifest = await readOrRefreshArtifactManifest(context);
|
|
408
|
+
const artifact = findArtifact(manifest.items, params);
|
|
409
|
+
if (!artifact) {
|
|
410
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Artifact not found', {
|
|
411
|
+
artifactId: params.artifactId,
|
|
412
|
+
relativePath: params.relativePath
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
const checkedPath = ensureInside(manifest.workspacePaths.workspaceRoot, artifact.localPath);
|
|
416
|
+
if (!(await pathExists(checkedPath))) {
|
|
417
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Artifact file does not exist', {
|
|
418
|
+
localPath: checkedPath
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
...artifact,
|
|
423
|
+
localPath: checkedPath
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function findArtifact(items, selector) {
|
|
427
|
+
const artifactId = optionalTrimmedString(selector.artifactId);
|
|
428
|
+
const relativePath = normalizeRelativePath(optionalTrimmedString(selector.relativePath));
|
|
429
|
+
if (!artifactId && !relativePath) {
|
|
430
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Either artifactId or relativePath is required');
|
|
431
|
+
}
|
|
432
|
+
return items.find((item) => {
|
|
433
|
+
if (artifactId && item.id === artifactId) {
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
return Boolean(relativePath && item.relativePath === relativePath);
|
|
437
|
+
}) ?? null;
|
|
438
|
+
}
|
|
439
|
+
function resolveWorkspaceRoot(openclawRoot) {
|
|
440
|
+
const workspaceRoot = path.join(openclawRoot, 'workspace');
|
|
441
|
+
return ensureInside(openclawRoot, workspaceRoot);
|
|
442
|
+
}
|
|
443
|
+
async function ensureWorkspacePaths(openclawRoot) {
|
|
444
|
+
const workspaceRoot = resolveWorkspaceRoot(openclawRoot);
|
|
445
|
+
const manifestPath = ensureInside(workspaceRoot, path.join(workspaceRoot, ARTIFACT_MANIFEST_FILE));
|
|
446
|
+
const markdownScanStatePath = ensureInside(workspaceRoot, path.join(workspaceRoot, PLUGIN_WORKSPACE_STATE_DIR, MARKDOWN_SCAN_STATE_FILE));
|
|
447
|
+
await ensureDir(workspaceRoot);
|
|
448
|
+
return {
|
|
449
|
+
workspaceRoot,
|
|
450
|
+
manifestPath,
|
|
451
|
+
markdownScanStatePath
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function createMarkdownScanState() {
|
|
455
|
+
const now = new Date().toISOString();
|
|
456
|
+
return {
|
|
457
|
+
initializedAt: now,
|
|
458
|
+
lastScanAt: now,
|
|
459
|
+
files: {}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function buildArtifactId(relativePath) {
|
|
463
|
+
return `art_${createHash('sha1').update(relativePath).digest('hex').slice(0, 12)}`;
|
|
464
|
+
}
|
|
465
|
+
function normalizeExtension(fileName) {
|
|
466
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
467
|
+
return ext || null;
|
|
468
|
+
}
|
|
469
|
+
function isMarkdownArtifactFile(fileName) {
|
|
470
|
+
return path.extname(fileName).toLowerCase() === '.md';
|
|
471
|
+
}
|
|
472
|
+
function normalizeRelativePath(value) {
|
|
473
|
+
if (!value) {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
return value.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
477
|
+
}
|
|
478
|
+
function normalizeTimestamp(created, fallback) {
|
|
479
|
+
return Number.isNaN(created.getTime()) ? fallback.toISOString() : created.toISOString();
|
|
480
|
+
}
|
|
481
|
+
function normalizeMaxInlineBytes(value) {
|
|
482
|
+
if (value === undefined) {
|
|
483
|
+
return INLINE_CONTENT_LIMIT_BYTES;
|
|
484
|
+
}
|
|
485
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
486
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'maxInlineBytes must be a positive number');
|
|
487
|
+
}
|
|
488
|
+
return Math.floor(value);
|
|
489
|
+
}
|
|
490
|
+
function isPreviewableCategory(category) {
|
|
491
|
+
return category === 'image' || category === 'video' || category === 'document';
|
|
492
|
+
}
|
|
493
|
+
function shouldIgnoreArtifactDirectory(dirName) {
|
|
494
|
+
const normalized = dirName.trim().toLowerCase();
|
|
495
|
+
return !normalized || normalized.startsWith('.') || IGNORE_DIRECTORY_NAMES.has(normalized);
|
|
496
|
+
}
|
|
497
|
+
function shouldIncludeArtifactFile(fileName) {
|
|
498
|
+
if (shouldIgnoreArtifactFile(fileName)) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
return classifyArtifactCategory(fileName) !== 'other';
|
|
502
|
+
}
|
|
503
|
+
function shouldIncludeMarkdownArtifactFile(relativePath, fileName, stat, state, hadState) {
|
|
504
|
+
if (shouldIgnoreArtifactFile(fileName)) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
if (IGNORE_MARKDOWN_FILE_NAMES.has(fileName.trim().toLowerCase())) {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
const existing = state.files[relativePath];
|
|
511
|
+
if (existing) {
|
|
512
|
+
existing.sizeBytes = stat.size;
|
|
513
|
+
existing.mtimeMs = stat.mtimeMs;
|
|
514
|
+
return !existing.baseline;
|
|
515
|
+
}
|
|
516
|
+
state.files[relativePath] = {
|
|
517
|
+
sizeBytes: stat.size,
|
|
518
|
+
mtimeMs: stat.mtimeMs,
|
|
519
|
+
firstSeenAt: new Date().toISOString(),
|
|
520
|
+
baseline: !hadState
|
|
521
|
+
};
|
|
522
|
+
return hadState;
|
|
523
|
+
}
|
|
524
|
+
function isMarkdownScanStateFile(value) {
|
|
525
|
+
if (!value || Array.isArray(value) || typeof value !== 'object') {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
const objectValue = value;
|
|
529
|
+
return (typeof objectValue.sizeBytes === 'number' &&
|
|
530
|
+
typeof objectValue.mtimeMs === 'number' &&
|
|
531
|
+
typeof objectValue.firstSeenAt === 'string' &&
|
|
532
|
+
typeof objectValue.baseline === 'boolean');
|
|
533
|
+
}
|
|
534
|
+
function isArtifactRecord(value) {
|
|
535
|
+
if (!value || Array.isArray(value) || typeof value !== 'object') {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
const objectValue = value;
|
|
539
|
+
return (typeof objectValue.id === 'string' &&
|
|
540
|
+
typeof objectValue.fileName === 'string' &&
|
|
541
|
+
typeof objectValue.relativePath === 'string' &&
|
|
542
|
+
typeof objectValue.localPath === 'string');
|
|
543
|
+
}
|
|
544
|
+
function artifactToJsonValue(item) {
|
|
545
|
+
return {
|
|
546
|
+
id: item.id,
|
|
547
|
+
fileName: item.fileName,
|
|
548
|
+
relativePath: item.relativePath,
|
|
549
|
+
localPath: item.localPath,
|
|
550
|
+
category: item.category,
|
|
551
|
+
mimeType: item.mimeType,
|
|
552
|
+
ext: item.ext,
|
|
553
|
+
sizeBytes: item.sizeBytes,
|
|
554
|
+
storageStatus: item.storageStatus,
|
|
555
|
+
source: item.source,
|
|
556
|
+
previewable: item.previewable,
|
|
557
|
+
createdAt: item.createdAt,
|
|
558
|
+
updatedAt: item.updatedAt,
|
|
559
|
+
objectKey: item.objectKey ?? null,
|
|
560
|
+
fileUrl: item.fileUrl ?? null,
|
|
561
|
+
uploadedAt: item.uploadedAt ?? null,
|
|
562
|
+
record: item.record ?? null
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function expectObject(value) {
|
|
566
|
+
if (!value || Array.isArray(value) || typeof value !== 'object') {
|
|
567
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'Params must be an object');
|
|
568
|
+
}
|
|
569
|
+
return value;
|
|
570
|
+
}
|
|
571
|
+
function expectOptionalObject(value) {
|
|
572
|
+
if (value === undefined) {
|
|
573
|
+
return {};
|
|
574
|
+
}
|
|
575
|
+
return expectObject(value);
|
|
576
|
+
}
|
|
577
|
+
function buildPresignedPostBody(providedBody, artifact) {
|
|
578
|
+
const body = isObject(providedBody) ? { ...providedBody } : {};
|
|
579
|
+
if (body.filename === undefined) {
|
|
580
|
+
body.filename = artifact.fileName;
|
|
581
|
+
}
|
|
582
|
+
return body;
|
|
583
|
+
}
|
|
584
|
+
function expectString(value, fieldName) {
|
|
585
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
586
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Field '${fieldName}' must be a non-empty string`);
|
|
587
|
+
}
|
|
588
|
+
return value.trim();
|
|
589
|
+
}
|
|
590
|
+
function optionalTrimmedString(value) {
|
|
591
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
|
|
592
|
+
}
|
|
593
|
+
function isObject(value) {
|
|
594
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
595
|
+
}
|
|
596
|
+
async function resolveApiCoreBotEndpoint(openclawRoot, overrideBaseUrl, overrideAuthToken) {
|
|
597
|
+
const config = await readJsonFile(path.join(openclawRoot, 'openclaw.json'));
|
|
598
|
+
const pluginConfig = config.plugins?.entries?.['rol-websocket-channel']?.config?.apiCoreBot ?? {};
|
|
599
|
+
const baseUrl = (overrideBaseUrl && overrideBaseUrl.trim()) || pluginConfig.baseUrl;
|
|
600
|
+
if (!baseUrl || !baseUrl.trim()) {
|
|
601
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'apiCoreBot.baseUrl is not configured');
|
|
602
|
+
}
|
|
603
|
+
const authToken = (overrideAuthToken && overrideAuthToken.trim()) || pluginConfig.authToken;
|
|
604
|
+
return {
|
|
605
|
+
baseUrl: baseUrl.replace(/\/+$/, ''),
|
|
606
|
+
authToken
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
async function postJson(url, body, authToken) {
|
|
610
|
+
const headers = {
|
|
611
|
+
'Content-Type': 'application/json'
|
|
612
|
+
};
|
|
613
|
+
if (authToken && authToken.trim()) {
|
|
614
|
+
headers.Authorization = `Bearer ${authToken.trim()}`;
|
|
615
|
+
}
|
|
616
|
+
const response = await fetch(url, {
|
|
617
|
+
method: 'POST',
|
|
618
|
+
headers,
|
|
619
|
+
body: JSON.stringify(body)
|
|
620
|
+
});
|
|
621
|
+
const payload = await response.json().catch(async () => await response.text());
|
|
622
|
+
if (!response.ok) {
|
|
623
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, `Request failed: ${response.status}`, {
|
|
624
|
+
url,
|
|
625
|
+
status: response.status,
|
|
626
|
+
payload
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
if (isObject(payload) && payload.success === false) {
|
|
630
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, pickStringValue(payload, ['message', 'msg']) ?? 'Request failed', {
|
|
631
|
+
url,
|
|
632
|
+
payload
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
return payload;
|
|
636
|
+
}
|
|
637
|
+
function resolvePresignedPostUploadTarget(payload) {
|
|
638
|
+
for (const candidate of collectPresignedPostCandidates(payload)) {
|
|
639
|
+
const uploadUrl = pickStringValue(candidate, ['url', 'uploadUrl', 'postUrl', 'presignedPostUrl']);
|
|
640
|
+
const rawFields = candidate.fields ?? candidate.formData ?? candidate.form ?? candidate.params;
|
|
641
|
+
const fields = toStringRecord(rawFields);
|
|
642
|
+
if (!uploadUrl || !fields) {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
uploadUrl,
|
|
647
|
+
fields,
|
|
648
|
+
objectKey: pickStringValue(candidate, ['objectKey', 'fileKey', 'file_key', 'source_file_key', 'key']) ?? fields.key ?? null,
|
|
649
|
+
fileUrl: pickStringValue(candidate, ['fileUrl', 'file_url', 'downloadUrl', 'source_file_url', 'location']) ?? null
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'Presigned post response is missing upload url or fields', { payload });
|
|
653
|
+
}
|
|
654
|
+
function collectPresignedPostCandidates(payload) {
|
|
655
|
+
const candidates = [];
|
|
656
|
+
const queue = [payload];
|
|
657
|
+
while (queue.length > 0) {
|
|
658
|
+
const current = queue.shift();
|
|
659
|
+
if (!isObject(current)) {
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
candidates.push(current);
|
|
663
|
+
for (const key of ['data', 'result', 'presignedPost']) {
|
|
664
|
+
const nested = current[key];
|
|
665
|
+
if (isObject(nested)) {
|
|
666
|
+
queue.push(nested);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return candidates;
|
|
671
|
+
}
|
|
672
|
+
function pickStringValue(source, keys) {
|
|
673
|
+
for (const key of keys) {
|
|
674
|
+
const value = source[key];
|
|
675
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
676
|
+
return value.trim();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
function toStringRecord(value) {
|
|
682
|
+
if (!isObject(value)) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
const result = {};
|
|
686
|
+
for (const [key, item] of Object.entries(value)) {
|
|
687
|
+
if (item === null) {
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (typeof item === 'string') {
|
|
691
|
+
result[key] = item;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (typeof item === 'number' || typeof item === 'boolean') {
|
|
695
|
+
result[key] = String(item);
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
return result;
|
|
701
|
+
}
|
|
702
|
+
async function uploadArtifactToPresignedPost(target, artifact) {
|
|
703
|
+
const formData = new FormData();
|
|
704
|
+
for (const [key, value] of Object.entries(target.fields)) {
|
|
705
|
+
formData.append(key, value);
|
|
706
|
+
}
|
|
707
|
+
const content = await fs.readFile(artifact.localPath);
|
|
708
|
+
formData.append('file', new Blob([content], { type: artifact.mimeType }), artifact.fileName);
|
|
709
|
+
const response = await fetch(target.uploadUrl, {
|
|
710
|
+
method: 'POST',
|
|
711
|
+
body: formData
|
|
712
|
+
});
|
|
713
|
+
if (!response.ok) {
|
|
714
|
+
const payload = await response.text().catch(() => '');
|
|
715
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, `Artifact upload failed: ${response.status}`, {
|
|
716
|
+
url: target.uploadUrl,
|
|
717
|
+
status: response.status,
|
|
718
|
+
payload
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function buildPublicFileUrl(uploadUrl, objectKey) {
|
|
723
|
+
if (!objectKey) {
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
try {
|
|
727
|
+
const url = new URL(uploadUrl);
|
|
728
|
+
url.pathname = `${url.pathname.replace(/\/+$/, '')}/${objectKey.replace(/^\/+/, '')}`;
|
|
729
|
+
url.search = '';
|
|
730
|
+
url.hash = '';
|
|
731
|
+
return url.toString();
|
|
732
|
+
}
|
|
733
|
+
catch {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
}
|