create-openclaw-bot 5.8.24 → 5.10.0
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/README.md +294 -183
- package/README.vi.md +294 -183
- package/dist/cli.js +172 -162
- package/dist/server/local-server.js +4260 -3503
- package/dist/setup/data/channels.js +1 -1
- package/dist/setup/data/header.js +80 -80
- package/dist/setup/data/plugins.js +1 -1
- package/dist/setup/data/providers.js +1 -1
- package/dist/setup/shared/bot-config-gen.js +534 -511
- package/dist/setup/shared/common-gen.js +314 -314
- package/dist/setup/shared/docker-gen.js +603 -586
- package/dist/setup/shared/install-gen.js +566 -566
- package/dist/setup/shared/workspace-gen.js +679 -648
- package/dist/web/app.js +1600 -1414
- package/dist/web/index.html +14 -14
- package/dist/web/lark.svg +1 -0
- package/dist/web/styles.css +1548 -1376
- package/node_modules/wrap-ansi/index.js +0 -0
- package/package.json +3 -2
|
@@ -1,586 +1,603 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
(function (root) {
|
|
3
|
-
const common = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon) || {};
|
|
4
|
-
const SUPPORTED_CODEX_MODELS = common.SUPPORTED_CODEX_MODELS || ['cx/gpt-5.4', 'cx/gpt-5.3-codex', 'cx/gpt-5.2', 'cx/gpt-5.4-mini'];
|
|
5
|
-
const SMART_ROUTE_PROVIDER_MODELS = common.SMART_ROUTE_PROVIDER_MODELS || { codex: SUPPORTED_CODEX_MODELS };
|
|
6
|
-
const SMART_ROUTE_PROVIDER_ORDER = common.SMART_ROUTE_PROVIDER_ORDER || ['codex'];
|
|
7
|
-
|
|
8
|
-
function encodeBase64Utf8(value) {
|
|
9
|
-
if (typeof Buffer !== 'undefined') {
|
|
10
|
-
return Buffer.from(String(value), 'utf8').toString('base64');
|
|
11
|
-
}
|
|
12
|
-
return btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function indentBlock(text, spaces) {
|
|
16
|
-
const prefix = ' '.repeat(spaces);
|
|
17
|
-
return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function build9RouterSmartRouteSyncScript() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"const
|
|
26
|
-
"const
|
|
27
|
-
"const
|
|
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
|
-
"const
|
|
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
|
-
"
|
|
86
|
-
"",
|
|
87
|
-
"
|
|
88
|
-
"",
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
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
|
-
'
|
|
131
|
-
'
|
|
132
|
-
'
|
|
133
|
-
'
|
|
134
|
-
'
|
|
135
|
-
'
|
|
136
|
-
'
|
|
137
|
-
'
|
|
138
|
-
'
|
|
139
|
-
'
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
].join('
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
add(path.join(process.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
function patchFile(filePath,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
(value)=>value.replace('let b=a.
|
|
162
|
-
(value)=>value.replace('let
|
|
163
|
-
(value)=>value.replace('let b=a.
|
|
164
|
-
(value)=>value.replace('
|
|
165
|
-
(value)=>value.replace('
|
|
166
|
-
(value)=>value.replace('
|
|
167
|
-
(value)=>value.replace('
|
|
168
|
-
(value)=>value.replace(
|
|
169
|
-
(value)=>value.replace(
|
|
170
|
-
(value)=>value.replace(
|
|
171
|
-
(value)=>value.replace(/
|
|
172
|
-
(value)=>value.replace(/find\\(a=>"
|
|
173
|
-
(value)=>value.replace(/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
: '';
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
: ''
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
'
|
|
252
|
-
'
|
|
253
|
-
'
|
|
254
|
-
'
|
|
255
|
-
'
|
|
256
|
-
' [ -e "$
|
|
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
|
-
runtimeParts.
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
-
|
|
490
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
volumes:
|
|
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
|
-
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
(function (root) {
|
|
3
|
+
const common = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon) || {};
|
|
4
|
+
const SUPPORTED_CODEX_MODELS = common.SUPPORTED_CODEX_MODELS || ['cx/gpt-5.4', 'cx/gpt-5.3-codex', 'cx/gpt-5.2', 'cx/gpt-5.4-mini'];
|
|
5
|
+
const SMART_ROUTE_PROVIDER_MODELS = common.SMART_ROUTE_PROVIDER_MODELS || { codex: SUPPORTED_CODEX_MODELS };
|
|
6
|
+
const SMART_ROUTE_PROVIDER_ORDER = common.SMART_ROUTE_PROVIDER_ORDER || ['codex'];
|
|
7
|
+
|
|
8
|
+
function encodeBase64Utf8(value) {
|
|
9
|
+
if (typeof Buffer !== 'undefined') {
|
|
10
|
+
return Buffer.from(String(value), 'utf8').toString('base64');
|
|
11
|
+
}
|
|
12
|
+
return btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function indentBlock(text, spaces) {
|
|
16
|
+
const prefix = ' '.repeat(spaces);
|
|
17
|
+
return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function build9RouterSmartRouteSyncScript() {
|
|
21
|
+
// First-install auto-sync: đăng nhập 9router bằng MẬT KHẨU MẶC ĐỊNH 123456 (9router cấp),
|
|
22
|
+
// gom model của các provider đang active → tạo combo 'smart-route' MỘT LẦN rồi DỪNG.
|
|
23
|
+
// KHÔNG tự tắt Require login nữa (require login giữ ON, pass mặc định 123456, user đổi sau).
|
|
24
|
+
const lines = [
|
|
25
|
+
"const fs = require('fs');",
|
|
26
|
+
"const INTERVAL = 30000;",
|
|
27
|
+
"const MAX_ATTEMPTS = 20; // ~10 phút; tạo combo lần đầu rồi dừng, không lặp vĩnh viễn",
|
|
28
|
+
"const DEFAULT_PASSWORD = '123456'; // mật khẩu mặc định 9router",
|
|
29
|
+
// Docker mounts the DB at /root/.9router; native installs pass NINEROUTER_DB_PATH so the
|
|
30
|
+
// same sync script works on the host without hardcoding the container path.
|
|
31
|
+
"const DB_PATH = process.env.NINEROUTER_DB_PATH || '/root/.9router/db/data.sqlite';",
|
|
32
|
+
"const PORT = process.env.PORT || 20128;",
|
|
33
|
+
"const COMBO_NAME = 'smart-route';",
|
|
34
|
+
"const API_BASE = `http://localhost:${PORT}`;",
|
|
35
|
+
"",
|
|
36
|
+
"// Đăng nhập bằng mật khẩu mặc định → lấy cookie auth_token để gọi API quản trị.",
|
|
37
|
+
"async function login() {",
|
|
38
|
+
" try {",
|
|
39
|
+
" const r = await fetch(`${API_BASE}/api/auth/login`, {",
|
|
40
|
+
" method: 'POST',",
|
|
41
|
+
" headers: { 'Content-Type': 'application/json' },",
|
|
42
|
+
" body: JSON.stringify({ password: DEFAULT_PASSWORD })",
|
|
43
|
+
" });",
|
|
44
|
+
" if (!r.ok) return '';",
|
|
45
|
+
" const sc = r.headers.get('set-cookie') || '';",
|
|
46
|
+
" const m = sc.match(/auth_token=[^;]+/);",
|
|
47
|
+
" return m ? m[0] : '';",
|
|
48
|
+
" } catch (e) { return ''; }",
|
|
49
|
+
"}",
|
|
50
|
+
"",
|
|
51
|
+
"async function trySync() {",
|
|
52
|
+
" if (!fs.existsSync(DB_PATH)) return false;",
|
|
53
|
+
" const cookie = await login(); // mk mặc định 123456; nếu user đã đổi thì login fail",
|
|
54
|
+
" const authHeaders = cookie ? { Cookie: cookie } : {};",
|
|
55
|
+
"",
|
|
56
|
+
" // Combo đã tồn tại? → coi như xong (chỉ tạo lần đầu).",
|
|
57
|
+
" try {",
|
|
58
|
+
" const r = await fetch(`${API_BASE}/api/combos`, { headers: authHeaders });",
|
|
59
|
+
" if (r.status === 401) return false;",
|
|
60
|
+
" const d = await r.json();",
|
|
61
|
+
" if ((d.combos || []).some(c => c.name === COMBO_NAME)) return true;",
|
|
62
|
+
" } catch (e) { return false; }",
|
|
63
|
+
"",
|
|
64
|
+
" // Provider đang active.",
|
|
65
|
+
" let activeProviders = [];",
|
|
66
|
+
" try {",
|
|
67
|
+
" const r = await fetch(`${API_BASE}/api/providers`, { headers: authHeaders });",
|
|
68
|
+
" const d = await r.json();",
|
|
69
|
+
" const conns = d.connections || d.providerConnections || [];",
|
|
70
|
+
" activeProviders = [...new Set(",
|
|
71
|
+
" conns.filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider)",
|
|
72
|
+
" )];",
|
|
73
|
+
" } catch (e) { return false; }",
|
|
74
|
+
" if (!activeProviders.length) return false;",
|
|
75
|
+
"",
|
|
76
|
+
" // Model của provider active (bỏ embedding/image/tts/…).",
|
|
77
|
+
" let models = [];",
|
|
78
|
+
" try {",
|
|
79
|
+
" const r = await fetch(`${API_BASE}/api/models`, { headers: authHeaders });",
|
|
80
|
+
" const d = await r.json();",
|
|
81
|
+
" if (Array.isArray(d.models)) {",
|
|
82
|
+
" models = [...new Set(d.models",
|
|
83
|
+
" .filter(m => activeProviders.includes(m.provider))",
|
|
84
|
+
" .filter(m => !/(embedding|image|tts|stt|audio|vision)/i.test(m.model))",
|
|
85
|
+
" .map(m => m.fullModel))];",
|
|
86
|
+
" }",
|
|
87
|
+
" } catch (e) { return false; }",
|
|
88
|
+
" if (!models.length) return false;",
|
|
89
|
+
"",
|
|
90
|
+
" // Tạo combo smart-route (lần đầu).",
|
|
91
|
+
" try {",
|
|
92
|
+
" await fetch(`${API_BASE}/api/combos`, {",
|
|
93
|
+
" method: 'POST',",
|
|
94
|
+
" headers: { 'Content-Type': 'application/json', ...authHeaders },",
|
|
95
|
+
" body: JSON.stringify({ name: COMBO_NAME, models })",
|
|
96
|
+
" });",
|
|
97
|
+
" console.log('[sync-combo] Created smart-route with ' + models.length + ' models');",
|
|
98
|
+
" return true;",
|
|
99
|
+
" } catch (e) { return false; }",
|
|
100
|
+
"}",
|
|
101
|
+
"",
|
|
102
|
+
"let attempts = 0;",
|
|
103
|
+
"let timer = null;",
|
|
104
|
+
"async function tick() {",
|
|
105
|
+
" attempts++;",
|
|
106
|
+
" let done = false;",
|
|
107
|
+
" try { done = await trySync(); } catch (e) {}",
|
|
108
|
+
" if (done || attempts >= MAX_ATTEMPTS) {",
|
|
109
|
+
" if (timer) clearInterval(timer);",
|
|
110
|
+
" console.log('[sync-combo] finished (attempts=' + attempts + ', done=' + done + ')');",
|
|
111
|
+
" }",
|
|
112
|
+
"}",
|
|
113
|
+
"timer = setInterval(tick, INTERVAL);",
|
|
114
|
+
"setTimeout(tick, 10000);",
|
|
115
|
+
];
|
|
116
|
+
return lines.join('\n');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function build9RouterPatchScript() {
|
|
120
|
+
return `const fs=require('fs');const path=require('path');const cp=require('child_process');
|
|
121
|
+
const MODELS=${JSON.stringify(SUPPORTED_CODEX_MODELS.map((model) => model.replace('cx/', '')))};
|
|
122
|
+
const MODEL_NAMES={"gpt-5.4":"GPT 5.4","gpt-5.4-mini":"GPT 5.4 Mini","gpt-5.3-codex":"GPT 5.3 Codex","gpt-5.2":"GPT 5.2"};
|
|
123
|
+
const SELF_TEST_BLOCK=[
|
|
124
|
+
'codex: {',
|
|
125
|
+
' url: "https://chatgpt.com/backend-api/codex/responses",',
|
|
126
|
+
' method: "POST",',
|
|
127
|
+
' authHeader: "Authorization",',
|
|
128
|
+
' authPrefix: "Bearer ",',
|
|
129
|
+
' extraHeaders: { "Content-Type": "application/json", "originator": "codex-cli", "User-Agent": "codex-cli/1.0.18 (macOS; arm64)" },',
|
|
130
|
+
' body: JSON.stringify({',
|
|
131
|
+
' model: "gpt-5.2",',
|
|
132
|
+
' instructions: "You are a coding assistant.",',
|
|
133
|
+
' input: [{ role: "user", content: [{ type: "input_text", text: "Reply with exactly: ok" }] }],',
|
|
134
|
+
' stream: true,',
|
|
135
|
+
' store: false,',
|
|
136
|
+
' }),',
|
|
137
|
+
' acceptStatuses: [200, 400],',
|
|
138
|
+
' refreshable: true,',
|
|
139
|
+
' },'
|
|
140
|
+
].join('\\n');
|
|
141
|
+
const roots=new Set();
|
|
142
|
+
function add(p){if(p)roots.add(p);}
|
|
143
|
+
try{const npmRoot=cp.execSync('npm root -g',{stdio:['ignore','pipe','ignore'],encoding:'utf8'}).trim();if(npmRoot)add(path.join(npmRoot,'9router'));}catch{}
|
|
144
|
+
add(path.join(process.env.APPDATA||'','npm','node_modules','9router'));
|
|
145
|
+
add('/usr/local/lib/node_modules/9router');
|
|
146
|
+
add('/usr/lib/node_modules/9router');
|
|
147
|
+
add(path.join(process.cwd(),'node_modules','9router'));
|
|
148
|
+
function patchFile(filePath, transform){if(!fs.existsSync(filePath))return false;const before=fs.readFileSync(filePath,'utf8');const after=transform(before);if(!after||after===before)return false;fs.writeFileSync(filePath,after);return true;}
|
|
149
|
+
function patchText(text,replacers){let next=text;for(const replacer of replacers){next=replacer(next);}return next===text?null:next;}
|
|
150
|
+
function patchProviderModels(root){return patchFile(path.join(root,'open-sse','config','providerModels.js'),(text)=>text.replace(/cx:\\s*\\[[\\s\\S]*?\\],/,()=>{const lines=MODELS.map((id)=>' { id: "'+id+'", name: "'+(MODEL_NAMES[id]||id)+'" },');return 'cx: [ // OpenAI Codex\\n'+lines.join('\\n')+'\\n ],';}));}
|
|
151
|
+
function patchCodexLikeFile(filePath){return patchFile(filePath,(text)=>{if(text.includes('max_output_tokens'))return text;return patchText(text,[
|
|
152
|
+
(value)=>value.replace(/delete (\\w+)\\.max_tokens,delete \\1\\.user/g,'delete $1.max_tokens,delete $1.max_output_tokens,delete $1.user'),
|
|
153
|
+
(value)=>value.replace(/delete (\\w+)\\.max_tokens;(\\s*)delete \\1\\.user/g,'delete $1.max_tokens;$2delete $1.max_output_tokens;$2delete $1.user'),
|
|
154
|
+
(value)=>value.replace(' delete body.max_tokens;\\n',' delete body.max_tokens;\\n delete body.max_output_tokens;\\n')
|
|
155
|
+
]);});}
|
|
156
|
+
function patchCodexExecutor(root){let touched=0;touched+=patchCodexLikeFile(path.join(root,'open-sse','executors','codex.js'))?1:0;const chunksDir=path.join(root,'app','.next','server','chunks');if(fs.existsSync(chunksDir)){for(const entry of fs.readdirSync(chunksDir)){if(!entry.endsWith('.js'))continue;touched+=patchCodexLikeFile(path.join(chunksDir,entry))?1:0;}}return touched;}
|
|
157
|
+
function patchResponsesNullGuard(root){let touched=0;const chunksDir=path.join(root,'app','.next','server','chunks');if(!fs.existsSync(chunksDir))return touched;for(const entry of fs.readdirSync(chunksDir)){if(!entry.endsWith('.js'))continue;touched+=patchFile(path.join(chunksDir,entry),(text)=>patchText(text,[
|
|
158
|
+
(value)=>value.replace('let b=a.content.find(a=>"output_text"===a.type);','let b=a.content.find(a=>a&&"output_text"===a.type);'),
|
|
159
|
+
(value)=>value.replace('let c=a.content.find(a=>"string"==typeof a.text);','let c=a.content.find(a=>a&&"string"==typeof a.text);'),
|
|
160
|
+
(value)=>value.replace('let b=a.filter(a=>a?.type==="message");','let b=a.filter(a=>a&&a?.type==="message");'),
|
|
161
|
+
(value)=>value.replace('for(let a of j){let b=a.type||(a.role?"message":null);','for(let a of j){let b=a&&(a.type||(a.role?"message":null));'),
|
|
162
|
+
(value)=>value.replace('for(let a of b.messages||[]){if("system"===a.role){','for(let a of b.messages||[])if(a){if("system"===a.role){'),
|
|
163
|
+
(value)=>value.replace('let b=Array.isArray(a.content)?a.content.map(a=>"input_text"===a.type||"output_text"===a.type?{type:"text",text:a.text}:"input_image"===a.type?{type:"image_url",image_url:{url:a.image_url||a.file_id||"",detail:a.detail||"auto"}}:a):a.content;','let b=Array.isArray(a.content)?a.content.map(a=>a&&("input_text"===a.type||"output_text"===a.type)?{type:"text",text:a.text}:a&&"input_image"===a.type?{type:"image_url",image_url:{url:a.image_url||a.file_id||"",detail:a.detail||"auto"}}:a).filter(Boolean):a.content;'),
|
|
164
|
+
(value)=>value.replace('c="string"==typeof a.content?[{type:b,text:a.content}]:Array.isArray(a.content)?a.content.map(a=>{if("text"===a.type)return{type:b,text:a.text};if("image_url"===a.type)return{type:"input_image",image_url:"string"==typeof a.image_url?a.image_url:a.image_url?.url,detail:a.image_url?.detail||"auto"};if("input_image"===a.type)return a;let c=a.text||a.content||JSON.stringify(a);return{type:b,text:"string"==typeof c?c:JSON.stringify(c)}}):[];','c="string"==typeof a.content?[{type:b,text:a.content}]:Array.isArray(a.content)?a.content.map(a=>{if(!a)return null;if("text"===a.type)return{type:b,text:a.text};if("image_url"===a.type)return{type:"input_image",image_url:"string"==typeof a.image_url?a.image_url:a.image_url?.url,detail:a.image_url?.detail||"auto"};if("input_image"===a.type)return a;let c=a.text||a.content||JSON.stringify(a);return{type:b,text:"string"==typeof c?c:JSON.stringify(c)}}).filter(Boolean):[];'),
|
|
165
|
+
(value)=>value.replace('b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>{if(a.function)return a;let b=a.name;return b&&"string"==typeof b&&""!==b.trim()?{type:"function",function:{name:b,description:String(a.description||""),parameters:i(a.parameters),strict:a.strict}}:null}).filter(Boolean))','b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>{if(!a)return null;if(a.function)return a;let b=a.name;return b&&"string"==typeof b&&""!==b.trim()?{type:"function",function:{name:b,description:String(a.description||""),parameters:i(a.parameters),strict:a.strict}}:null}).filter(Boolean))'),
|
|
166
|
+
(value)=>value.replace('b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>"function"===a.type?{type:"function",name:a.function.name,description:String(a.function.description||""),parameters:i(a.function.parameters),strict:a.function.strict}:a)),','b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>a&&"function"===a.type?{type:"function",name:a.function.name,description:String(a.function.description||""),parameters:i(a.function.parameters),strict:a.function.strict}:a).filter(Boolean)),'),
|
|
167
|
+
(value)=>value.replace('filter(a=>"function_call"===a.type)','filter(a=>a&&"function_call"===a.type)'),
|
|
168
|
+
(value)=>value.replace(/filter\\(a=>"text"===a\\.type\\)/g,'filter(a=>a&&"text"===a.type)'),
|
|
169
|
+
(value)=>value.replace(/find\\(a=>"message_stop"===a\\.type\\)/g,'find(a=>a&&"message_stop"===a.type)'),
|
|
170
|
+
(value)=>value.replace(/find\\(a=>"content_block_delta"===a\\.type\\)/g,'find(a=>a&&"content_block_delta"===a.type)'),
|
|
171
|
+
(value)=>value.replace(/find\\(a=>"message_delta"===a\\.type\\)/g,'find(a=>a&&"message_delta"===a.type)'),
|
|
172
|
+
(value)=>value.replace(/find\\(a=>"message_start"===a\\.type\\)/g,'find(a=>a&&"message_start"===a.type)'),
|
|
173
|
+
(value)=>value.replace(/for\\(let e of a\\.content\\)(?!if\\(e\\))/g,'for(let e of a.content)if(e)')
|
|
174
|
+
] ))?1:0;}return touched;}
|
|
175
|
+
function patchSelfTest(root){return patchFile(path.join(root,'src','app','api','providers','[id]','test','testUtils.js'),(text)=>{if(text.includes('model: "gpt-5.2"')&&text.includes('store: false')&&text.includes('acceptStatuses: [200, 400]'))return text;return text.replace(/codex:\\s*\\{[\\s\\S]*?refreshable:\\s*true,\\s*\\},/,SELF_TEST_BLOCK);});}
|
|
176
|
+
let touched=0;
|
|
177
|
+
for(const root of roots){if(!root||!fs.existsSync(root))continue;touched+=patchProviderModels(root)?1:0;touched+=patchCodexExecutor(root)?1:0;touched+=patchResponsesNullGuard(root)?1:0;touched+=patchSelfTest(root)?1:0;}
|
|
178
|
+
if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}else{console.log('[patch-9router] No compatible 9router source files found to patch.');}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function build9RouterComposeEntrypointScript(routerPort) {
|
|
182
|
+
const port = routerPort || 20128;
|
|
183
|
+
const nineRouterSpec = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon && globalThis.__openclawCommon.NINE_ROUTER_NPM_SPEC) || '9router@latest';
|
|
184
|
+
return [
|
|
185
|
+
`npm install -g ` + nineRouterSpec,
|
|
186
|
+
'node /tmp/patch-9router.js || true',
|
|
187
|
+
'node -e "const fs=require(\'fs\'),path=require(\'path\'); const DB_PATH=\'/root/.9router/db/data.sqlite\'; const dir=path.dirname(DB_PATH); if(!fs.existsSync(dir))fs.mkdirSync(dir,{recursive:true}); try{ const {DatabaseSync}=require(\'node:sqlite\'); const db=new DatabaseSync(DB_PATH); db.prepare(\'CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)\').run(); const existing=db.prepare(\'SELECT * FROM settings WHERE id = 1\').get(); if(!existing){ db.prepare(\'INSERT INTO settings (id, data) VALUES (1, ?)\').run(JSON.stringify({requireLogin:true})); } db.close(); }catch(e){}" || true',
|
|
188
|
+
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
189
|
+
`exec 9router -n -l -H 0.0.0.0 -p ${port} --skip-update`
|
|
190
|
+
].join('\n');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function buildGatewayPatchCmd() {
|
|
194
|
+
return `node -e \\"const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const gp=Number(process.env.OPENCLAW_GATEWAY_PORT||process.env.OPENCLAW_PORT)||c.gateway?.port||18789;const a=new Set(['http://localhost:'+gp,'http://127.0.0.1:'+gp,'http://0.0.0.0:'+gp]);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://' + entry.address + ':'+gp);}}const p9=c.models&&c.models.providers&&c.models.providers['9router'];if(p9){p9.request=Object.assign({},p9.request,{allowPrivateNetwork:true});}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:gp,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\"`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function buildDockerArtifacts(options) {
|
|
198
|
+
const {
|
|
199
|
+
openClawNpmSpec,
|
|
200
|
+
openClawRuntimePackages,
|
|
201
|
+
is9Router,
|
|
202
|
+
isLocal,
|
|
203
|
+
isMultiBot,
|
|
204
|
+
hasBrowser = false,
|
|
205
|
+
selectedModel,
|
|
206
|
+
agentId,
|
|
207
|
+
allSkills = [],
|
|
208
|
+
dockerfilePlugins = [],
|
|
209
|
+
dockerfileSkillInstallMode = 'none',
|
|
210
|
+
runtimeCommandParts = [],
|
|
211
|
+
volumeMount = '../../.openclaw:/home/node/project/.openclaw\n - ../../:/mnt/project',
|
|
212
|
+
singleComposeName = 'oc-bot',
|
|
213
|
+
multiComposeName = 'oc-multibot',
|
|
214
|
+
singleAppContainerName = 'openclaw-bot',
|
|
215
|
+
multiAppContainerName = 'openclaw-multibot',
|
|
216
|
+
singleRouterContainerName = '9router',
|
|
217
|
+
multiRouterContainerName = '9router-multibot',
|
|
218
|
+
singleOllamaContainerName = 'ollama',
|
|
219
|
+
multiOllamaContainerName = 'ollama-multibot',
|
|
220
|
+
plainSingleExtraHosts = false,
|
|
221
|
+
multiOllamaNumParallel = 1,
|
|
222
|
+
singleOllamaNumParallel = 1,
|
|
223
|
+
gatewayPort = 18789,
|
|
224
|
+
routerPort = 20128,
|
|
225
|
+
osChoice = '',
|
|
226
|
+
} = options;
|
|
227
|
+
// Windows bind-mounts give ClawHub-installed plugins world-writable perms (which openclaw
|
|
228
|
+
// blocks), so on Windows we isolate extensions in a named volume. On macOS/Linux bind-mounts
|
|
229
|
+
// are fine, so keep extensions under the .openclaw bind mount → plugins stay visible/synced
|
|
230
|
+
// on the host (e.g. you can see zalo-mod in .openclaw/extensions).
|
|
231
|
+
const useExtensionsVolume = osChoice === 'win';
|
|
232
|
+
const extVolMount = useExtensionsVolume ? '\n - openclaw-extensions:/home/node/project/.openclaw/extensions' : '';
|
|
233
|
+
const extVolDecl = useExtensionsVolume ? '\n openclaw-extensions:' : '';
|
|
234
|
+
const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
|
|
235
|
+
? `\n# Install skills (ClawHub)\n${allSkills.map((skill) => `RUN openclaw skills install ${skill} || echo "Warning: Failed to install ${skill} due to rate limits."`).join('\n')}\n`
|
|
236
|
+
: '';
|
|
237
|
+
const pluginLines = dockerfilePlugins.length > 0
|
|
238
|
+
? `\n# Install plugins (ClawHub)\n${dockerfilePlugins.map((p) => `RUN openclaw plugins install ${p} || echo "Warning: Failed to install plugin ${p}"`).join('\n')}\n`
|
|
239
|
+
: '';
|
|
240
|
+
const patchLine = `RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const files=fs.readdirSync(dir).filter(n=>/\\.js$/.test(n));let patched=0;for(const file of files){const p=path.join(dir,file);let s='';try{s=fs.readFileSync(p,'utf8');}catch{continue;}if(s.includes(to)||!s.includes(from))continue;s=s.replace(from,to);fs.writeFileSync(p,s);patched++;}if(!patched){process.exit(0);}"`;
|
|
241
|
+
|
|
242
|
+
// Dynamic runtime configuration: backup config before any first-run install, restore after.
|
|
243
|
+
// Missing plugin install may touch openclaw.json, so preserve critical fields.
|
|
244
|
+
const backupConfigScript = `const fs=require('fs'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)){fs.copyFileSync(p,b);}`;
|
|
245
|
+
|
|
246
|
+
const restoreConfigScript = `const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)&&fs.existsSync(b)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const bk=JSON.parse(fs.readFileSync(b,'utf8'));const keep=['agents','channels','bindings','commands','models','browser','skills','plugins','tools'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const gp=Number(process.env.OPENCLAW_GATEWAY_PORT||process.env.OPENCLAW_PORT)||c.gateway?.port||bk.gateway?.port||18789;const a=new Set(['http://localhost:'+gp,'http://127.0.0.1:'+gp,'http://0.0.0.0:'+gp]);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://'+entry.address+':'+gp);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:gp,bind:'custom',customBindHost:'0.0.0.0',mode:c.gateway?.mode||bk.gateway?.mode||'local',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));fs.unlinkSync(b);}`;
|
|
247
|
+
const securityCompatScript = `const fs=require('fs'),path=require('path');const scopes=['operator.admin','operator.pairing','operator.approvals'];function uniq(a){return Array.from(new Set([...(Array.isArray(a)?a:[]),...scopes]));}function walk(v){if(!v||typeof v!=='object')return;if(Array.isArray(v)){v.forEach(walk);return;}if(Array.isArray(v.scopes)||Array.isArray(v.approvedScopes)){v.scopes=uniq(v.scopes);v.approvedScopes=uniq(v.approvedScopes);}Object.values(v).forEach(walk);}const home=process.env.OPENCLAW_HOME||path.join(process.cwd(),'.openclaw');const state=process.env.OPENCLAW_STATE_DIR||home;const cfgPath=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(cfgPath)){const c=JSON.parse(fs.readFileSync(cfgPath,'utf8'));const p=c.models&&c.models.providers&&c.models.providers['9router'];if(p){p.request=Object.assign({},p.request,{allowPrivateNetwork:true});}fs.writeFileSync(cfgPath,JSON.stringify(c,null,2));}for(const root of Array.from(new Set([home,state]))){const f=path.join(root,'devices','paired.json');if(fs.existsSync(f)){const d=JSON.parse(fs.readFileSync(f,'utf8'));walk(d);fs.writeFileSync(f,JSON.stringify(d,null,2));}}`;
|
|
248
|
+
|
|
249
|
+
const runtimeParts = runtimeCommandParts.filter(Boolean);
|
|
250
|
+
const runtimePrelude = [
|
|
251
|
+
'export OPENCLAW_HOME="${OPENCLAW_HOME:-$PWD/.openclaw}"',
|
|
252
|
+
'export OPENCLAW_STATE_DIR="${OPENCLAW_STATE_DIR:-$OPENCLAW_HOME}"',
|
|
253
|
+
'mkdir -p "$OPENCLAW_HOME" "$OPENCLAW_STATE_DIR"',
|
|
254
|
+
'if [ "$OPENCLAW_STATE_DIR" != "$OPENCLAW_HOME" ]; then',
|
|
255
|
+
' for path in "$OPENCLAW_HOME"/*; do',
|
|
256
|
+
' [ -e "$path" ] || continue',
|
|
257
|
+
' name="$(basename "$path")"',
|
|
258
|
+
' [ "$name" = "plugin-runtime-deps" ] && continue',
|
|
259
|
+
' [ "$name" = "logs" ] && continue',
|
|
260
|
+
' [ -e "$OPENCLAW_STATE_DIR/$name" ] || ln -s "$path" "$OPENCLAW_STATE_DIR/$name"',
|
|
261
|
+
' done',
|
|
262
|
+
'fi',
|
|
263
|
+
'ensure_plugin() {',
|
|
264
|
+
' id="$1"',
|
|
265
|
+
' spec="$2"',
|
|
266
|
+
' if [ -d "$OPENCLAW_HOME/extensions/$id" ]; then',
|
|
267
|
+
' echo "[entrypoint] plugin $id already installed"',
|
|
268
|
+
' return 0',
|
|
269
|
+
' fi',
|
|
270
|
+
' echo "[entrypoint] plugin $id missing; installing $spec"',
|
|
271
|
+
' openclaw plugins install "$spec" 2>/dev/null || echo "[entrypoint] warning: failed to install plugin $spec"',
|
|
272
|
+
'}',
|
|
273
|
+
'ensure_zalouser() {',
|
|
274
|
+
' NPM_DIR="$OPENCLAW_HOME/npm"',
|
|
275
|
+
' PKG_DIR="$NPM_DIR/node_modules/@openclaw/zalouser"',
|
|
276
|
+
' if [ -d "$PKG_DIR" ] || [ -d "$OPENCLAW_HOME/extensions/zalouser" ]; then',
|
|
277
|
+
' echo "[entrypoint] zalouser plugin already installed (npm or extensions) — skipping"',
|
|
278
|
+
' else',
|
|
279
|
+
' echo "[entrypoint] zalouser plugin missing; installing via npm"',
|
|
280
|
+
' mkdir -p "$NPM_DIR"',
|
|
281
|
+
' cd "$NPM_DIR"',
|
|
282
|
+
' npm init -y 2>/dev/null || true',
|
|
283
|
+
' npm install @openclaw/zalouser@latest 2>/dev/null || echo "[entrypoint] warning: failed to install @openclaw/zalouser"',
|
|
284
|
+
' cd /home/node/project',
|
|
285
|
+
' fi',
|
|
286
|
+
'}',
|
|
287
|
+
'ensure_skill() {',
|
|
288
|
+
' id="$1"',
|
|
289
|
+
' if find "$OPENCLAW_HOME" -maxdepth 4 -type d -path "*/skills/$id" -print -quit 2>/dev/null | grep -q .; then',
|
|
290
|
+
' echo "[entrypoint] skill $id already installed"',
|
|
291
|
+
' return 0',
|
|
292
|
+
' fi',
|
|
293
|
+
' echo "[entrypoint] skill $id missing; installing"',
|
|
294
|
+
' openclaw skills install "$id" 2>/dev/null || echo "[entrypoint] warning: failed to install skill $id"',
|
|
295
|
+
'}',
|
|
296
|
+
'echo "[entrypoint] ensuring runtime assets, then starting gateway"',
|
|
297
|
+
];
|
|
298
|
+
runtimeParts.unshift(...runtimePrelude);
|
|
299
|
+
// Backup config BEFORE plugin installs (runtimeCommandParts may contain plugin install commands)
|
|
300
|
+
runtimeParts.unshift(`node - <<'NODE'\n${backupConfigScript}\nNODE`);
|
|
301
|
+
// Restore config AFTER plugin installs (which may clobber openclaw.json)
|
|
302
|
+
runtimeParts.push(`node - <<'NODE'\n${restoreConfigScript}\nNODE`);
|
|
303
|
+
runtimeParts.push(`node - <<'NODE'\n${securityCompatScript}\nNODE`);
|
|
304
|
+
// Zalouser stability: patch watchdog tolerance and add auto-restart monitor
|
|
305
|
+
runtimeParts.push([
|
|
306
|
+
'# Patch zalouser watchdog tolerance (35s -> 90s) to survive provider auth pre-warming',
|
|
307
|
+
'ZALO_JS=$(find "$OPENCLAW_HOME" -path "*/zalouser/dist/zalo-js-*.js" -type f 2>/dev/null | head -1)',
|
|
308
|
+
'if [ -n "$ZALO_JS" ] && grep -q "35e3" "$ZALO_JS" 2>/dev/null; then',
|
|
309
|
+
' sed -i "s/LISTENER_WATCHDOG_MAX_GAP_MS\\\\s*=\\\\s*35e3/LISTENER_WATCHDOG_MAX_GAP_MS = 90e3/" "$ZALO_JS"',
|
|
310
|
+
' echo "[entrypoint] patched zalouser watchdog gap: 35s -> 90s"',
|
|
311
|
+
'fi',
|
|
312
|
+
'# Automatically run Zalo sticker-mention patch if skill is present',
|
|
313
|
+
'for MENTIONS_JS in $(find "$OPENCLAW_HOME" -maxdepth 5 -path "*/skills/*/mentions.js" -type f 2>/dev/null); do',
|
|
314
|
+
' if [ -f "$MENTIONS_JS" ]; then',
|
|
315
|
+
' echo "[entrypoint] Running patch: $MENTIONS_JS"',
|
|
316
|
+
' node "$MENTIONS_JS" || echo "[entrypoint] Warning: failed to run patch $MENTIONS_JS"',
|
|
317
|
+
' fi',
|
|
318
|
+
'done',
|
|
319
|
+
].join('\n'));
|
|
320
|
+
// Expose zalouser's ZCA API map (globalThis.__zcaApiByProfile) BEFORE the gateway imports
|
|
321
|
+
// zalouser, so openclaw-zalo-mod can reach the live Zalo API (dashboard Sync Account, group
|
|
322
|
+
// admin lookups, …). zalo-mod's own runtime patch lands AFTER zalouser is already imported,
|
|
323
|
+
// so it never takes effect on the running module — patching here (post sticker-mention restore,
|
|
324
|
+
// pre gateway-run) is the reliable fix.
|
|
325
|
+
{
|
|
326
|
+
const exposeZcaScript = [
|
|
327
|
+
"const fs=require('fs'),cp=require('child_process');",
|
|
328
|
+
"let files=[];",
|
|
329
|
+
"try{ files=cp.execSync('find \"'+(process.env.OPENCLAW_HOME||'')+'/npm\" -path \"*/@openclaw/zalouser/dist/zalo-js-*.js\" -type f 2>/dev/null',{encoding:'utf8'}).split('\\n').filter(Boolean); }catch(e){}",
|
|
330
|
+
"const reps=[['const apiByProfile = /* @__PURE__ */ new Map();','const apiByProfile = globalThis.__zcaApiByProfile || (globalThis.__zcaApiByProfile = /* @__PURE__ */ new Map());'],['const apiByProfile = new Map();','const apiByProfile = globalThis.__zcaApiByProfile || (globalThis.__zcaApiByProfile = new Map());']];",
|
|
331
|
+
"for(const f of files){ try{ let s=fs.readFileSync(f,'utf8'); if(s.includes('globalThis.__zcaApiByProfile')) continue; for(const pair of reps){ if(s.includes(pair[0])){ s=s.replace(pair[0],pair[1]); fs.writeFileSync(f,s); console.log('[entrypoint] exposed __zcaApiByProfile for zalo-mod in '+f); break; } } }catch(e){} }",
|
|
332
|
+
].join('\n');
|
|
333
|
+
runtimeParts.push(`# Expose zalouser ZCA API map for openclaw-zalo-mod (before gateway imports zalouser)\nnode - <<'NODE'\n${exposeZcaScript}\nNODE`);
|
|
334
|
+
}
|
|
335
|
+
runtimeParts.push([
|
|
336
|
+
'# Zalo channel auto-restart monitor (background)',
|
|
337
|
+
'(',
|
|
338
|
+
' sleep 180',
|
|
339
|
+
' while true; do',
|
|
340
|
+
' sleep 60',
|
|
341
|
+
' STATUS=$(openclaw channels status 2>/dev/null | grep -i "Zalo Personal" || true)',
|
|
342
|
+
' if echo "$STATUS" | grep -qi "stopped"; then',
|
|
343
|
+
' echo "[zalo-monitor] Zalo channel stopped - restarting container in 5s"',
|
|
344
|
+
' sleep 5',
|
|
345
|
+
' kill 1 2>/dev/null || true',
|
|
346
|
+
' fi',
|
|
347
|
+
' done',
|
|
348
|
+
') &',
|
|
349
|
+
].join('\n'));
|
|
350
|
+
runtimeParts.push('openclaw gateway run');
|
|
351
|
+
const runtimeScript = ['#!/bin/sh', 'set -e', ...runtimeParts].join('\n');
|
|
352
|
+
let browserInstall = '';
|
|
353
|
+
if (hasBrowser) {
|
|
354
|
+
browserInstall = '\n# Install browser and system dependencies for Playwright\nRUN npx playwright install-deps chromium && npx playwright install chromium\n';
|
|
355
|
+
}
|
|
356
|
+
const dockerfile = `FROM node:22-slim
|
|
357
|
+
|
|
358
|
+
RUN apt-get update && apt-get install -y git curl python3 && rm -rf /var/lib/apt/lists/*
|
|
359
|
+
|
|
360
|
+
ARG OPENCLAW_VER="${openClawNpmSpec}"
|
|
361
|
+
ARG CACHE_BUST=""
|
|
362
|
+
RUN echo "CACHE_BUST=$CACHE_BUST" && npm install -g $OPENCLAW_VER ${openClawRuntimePackages}${skillLines}${pluginLines}
|
|
363
|
+
${patchLine}${browserInstall}
|
|
364
|
+
|
|
365
|
+
COPY entrypoint.sh /usr/local/bin/openclaw-entrypoint.sh
|
|
366
|
+
RUN chmod +x /usr/local/bin/openclaw-entrypoint.sh
|
|
367
|
+
WORKDIR /home/node/project
|
|
368
|
+
|
|
369
|
+
EXPOSE ${gatewayPort}
|
|
370
|
+
|
|
371
|
+
CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
|
|
372
|
+
|
|
373
|
+
const syncScript = build9RouterSmartRouteSyncScript();
|
|
374
|
+
const patchScript = build9RouterPatchScript();
|
|
375
|
+
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(routerPort);
|
|
376
|
+
const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
|
|
377
|
+
|
|
378
|
+
const appEnvironmentBlock = ` environment:\n - HOME=/home/node/project/.openclaw\n - OPENCLAW_HOME=/home/node/project/.openclaw\n - OPENCLAW_STATE_DIR=/home/node/project/.openclaw\n - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1\n - OPENCLAW_SETUP_OS=${osChoice || ''}\n - OPENCLAW_BROWSER_HOST_OS=${osChoice || ''}\n - OPENCLAW_GATEWAY_PORT=${gatewayPort}\n - OPENCLAW_PORT=${gatewayPort}\n tmpfs:\n - /home/node/project/.openclaw/plugin-runtime-deps\n`;
|
|
379
|
+
|
|
380
|
+
let compose;
|
|
381
|
+
if (isMultiBot) {
|
|
382
|
+
const dependsOn = is9Router
|
|
383
|
+
? ' depends_on:\n - 9router\n'
|
|
384
|
+
: isLocal
|
|
385
|
+
? ' depends_on:\n ollama:\n condition: service_healthy\n'
|
|
386
|
+
: '';
|
|
387
|
+
const extraHosts = `${extraHostsBlock}\n`;
|
|
388
|
+
if (is9Router) {
|
|
389
|
+
compose = `name: ${multiComposeName}
|
|
390
|
+
services:
|
|
391
|
+
ai-bot:
|
|
392
|
+
build: .
|
|
393
|
+
container_name: ${multiAppContainerName}
|
|
394
|
+
restart: always
|
|
395
|
+
env_file:
|
|
396
|
+
- ../../.env
|
|
397
|
+
${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
|
|
398
|
+
- ${volumeMount}
|
|
399
|
+
ports:
|
|
400
|
+
- "127.0.0.1:${gatewayPort}:${gatewayPort}"
|
|
401
|
+
|
|
402
|
+
9router:
|
|
403
|
+
image: node:22-slim
|
|
404
|
+
container_name: ${multiRouterContainerName}
|
|
405
|
+
restart: always
|
|
406
|
+
entrypoint:
|
|
407
|
+
- /bin/sh
|
|
408
|
+
- -c
|
|
409
|
+
- |
|
|
410
|
+
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
411
|
+
environment:
|
|
412
|
+
- PORT=${routerPort}
|
|
413
|
+
- HOSTNAME=0.0.0.0
|
|
414
|
+
- CI=true
|
|
415
|
+
volumes:
|
|
416
|
+
- 9router-data:/root/.9router
|
|
417
|
+
- ./sync.js:/tmp/sync.js:ro
|
|
418
|
+
- ./patch-9router.js:/tmp/patch-9router.js:ro
|
|
419
|
+
ports:
|
|
420
|
+
- "127.0.0.1:${routerPort}:${routerPort}"
|
|
421
|
+
|
|
422
|
+
volumes:
|
|
423
|
+
9router-data:`;
|
|
424
|
+
} else if (isLocal) {
|
|
425
|
+
const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
|
|
426
|
+
compose = `name: ${multiComposeName}
|
|
427
|
+
services:
|
|
428
|
+
ai-bot:
|
|
429
|
+
build: .
|
|
430
|
+
container_name: ${multiAppContainerName}
|
|
431
|
+
restart: always
|
|
432
|
+
env_file:
|
|
433
|
+
- ../../.env
|
|
434
|
+
${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
|
|
435
|
+
- ${volumeMount}
|
|
436
|
+
ports:
|
|
437
|
+
- "127.0.0.1:${gatewayPort}:${gatewayPort}"
|
|
438
|
+
|
|
439
|
+
ollama:
|
|
440
|
+
image: ollama/ollama:latest
|
|
441
|
+
container_name: ${multiOllamaContainerName}
|
|
442
|
+
restart: always
|
|
443
|
+
environment:
|
|
444
|
+
- OLLAMA_KEEP_ALIVE=24h
|
|
445
|
+
- OLLAMA_NUM_PARALLEL=${multiOllamaNumParallel}
|
|
446
|
+
volumes:
|
|
447
|
+
- ollama-data:/root/.ollama
|
|
448
|
+
entrypoint:
|
|
449
|
+
- /bin/sh
|
|
450
|
+
- -c
|
|
451
|
+
- |
|
|
452
|
+
ollama serve &
|
|
453
|
+
until ollama list > /dev/null 2>&1; do sleep 1; done
|
|
454
|
+
ollama pull ${ollamaModelTag}
|
|
455
|
+
wait
|
|
456
|
+
healthcheck:
|
|
457
|
+
test: ["CMD-SHELL", "ollama list > /dev/null 2>&1"]
|
|
458
|
+
interval: 10s
|
|
459
|
+
timeout: 5s
|
|
460
|
+
retries: 10
|
|
461
|
+
start_period: 30s
|
|
462
|
+
|
|
463
|
+
volumes:
|
|
464
|
+
ollama-data:`;
|
|
465
|
+
} else {
|
|
466
|
+
compose = `name: ${multiComposeName}
|
|
467
|
+
services:
|
|
468
|
+
ai-bot:
|
|
469
|
+
build: .
|
|
470
|
+
container_name: ${multiAppContainerName}
|
|
471
|
+
restart: always
|
|
472
|
+
env_file:
|
|
473
|
+
- ../../.env
|
|
474
|
+
${appEnvironmentBlock}${extraHosts} volumes:
|
|
475
|
+
- ${volumeMount}
|
|
476
|
+
ports:
|
|
477
|
+
- "127.0.0.1:${gatewayPort}:${gatewayPort}"`;
|
|
478
|
+
}
|
|
479
|
+
} else if (is9Router) {
|
|
480
|
+
compose = `name: ${singleComposeName}
|
|
481
|
+
services:
|
|
482
|
+
ai-bot:
|
|
483
|
+
build: .
|
|
484
|
+
container_name: ${singleAppContainerName}
|
|
485
|
+
restart: always
|
|
486
|
+
env_file:
|
|
487
|
+
- ../../.env
|
|
488
|
+
depends_on:
|
|
489
|
+
- 9router
|
|
490
|
+
${appEnvironmentBlock}${extraHostsBlock}\n volumes:
|
|
491
|
+
- ${volumeMount}
|
|
492
|
+
- openclaw-plugins:/home/node/project/.openclaw/npm${extVolMount}
|
|
493
|
+
ports:
|
|
494
|
+
- "127.0.0.1:${gatewayPort}:${gatewayPort}"
|
|
495
|
+
|
|
496
|
+
9router:
|
|
497
|
+
image: node:22-slim
|
|
498
|
+
container_name: ${singleRouterContainerName}
|
|
499
|
+
restart: always
|
|
500
|
+
entrypoint:
|
|
501
|
+
- /bin/sh
|
|
502
|
+
- -c
|
|
503
|
+
- |
|
|
504
|
+
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
505
|
+
environment:
|
|
506
|
+
- PORT=${routerPort}
|
|
507
|
+
- HOSTNAME=0.0.0.0
|
|
508
|
+
- CI=true
|
|
509
|
+
volumes:
|
|
510
|
+
- 9router-data:/root/.9router
|
|
511
|
+
- ./sync.js:/tmp/sync.js:ro
|
|
512
|
+
- ./patch-9router.js:/tmp/patch-9router.js:ro
|
|
513
|
+
ports:
|
|
514
|
+
- "127.0.0.1:${routerPort}:${routerPort}"
|
|
515
|
+
|
|
516
|
+
volumes:
|
|
517
|
+
9router-data:
|
|
518
|
+
openclaw-plugins:${extVolDecl}`;
|
|
519
|
+
} else if (isLocal) {
|
|
520
|
+
const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
|
|
521
|
+
compose = `name: ${singleComposeName}
|
|
522
|
+
services:
|
|
523
|
+
ai-bot:
|
|
524
|
+
build: .
|
|
525
|
+
container_name: ${singleAppContainerName}
|
|
526
|
+
restart: always
|
|
527
|
+
env_file: ../../.env
|
|
528
|
+
${appEnvironmentBlock} depends_on:
|
|
529
|
+
ollama:
|
|
530
|
+
condition: service_healthy
|
|
531
|
+
${extraHostsBlock}\n ports:
|
|
532
|
+
- "127.0.0.1:${gatewayPort}:${gatewayPort}"
|
|
533
|
+
volumes:
|
|
534
|
+
- ${volumeMount}
|
|
535
|
+
|
|
536
|
+
ollama:
|
|
537
|
+
image: ollama/ollama:latest
|
|
538
|
+
container_name: ${singleOllamaContainerName}
|
|
539
|
+
restart: always
|
|
540
|
+
environment:
|
|
541
|
+
- OLLAMA_KEEP_ALIVE=24h
|
|
542
|
+
- OLLAMA_NUM_PARALLEL=${singleOllamaNumParallel}
|
|
543
|
+
volumes:
|
|
544
|
+
- ollama-data:/root/.ollama
|
|
545
|
+
entrypoint:
|
|
546
|
+
- /bin/sh
|
|
547
|
+
- -c
|
|
548
|
+
- |
|
|
549
|
+
ollama serve &
|
|
550
|
+
until ollama list > /dev/null 2>&1; do sleep 1; done
|
|
551
|
+
ollama pull ${ollamaModelTag}
|
|
552
|
+
wait
|
|
553
|
+
healthcheck:
|
|
554
|
+
test: ["CMD-SHELL", "ollama list > /dev/null 2>&1"]
|
|
555
|
+
interval: 10s
|
|
556
|
+
timeout: 5s
|
|
557
|
+
retries: 10
|
|
558
|
+
start_period: 30s
|
|
559
|
+
|
|
560
|
+
volumes:
|
|
561
|
+
ollama-data:`;
|
|
562
|
+
} else {
|
|
563
|
+
compose = `name: ${singleComposeName}
|
|
564
|
+
services:
|
|
565
|
+
ai-bot:
|
|
566
|
+
build: .
|
|
567
|
+
container_name: ${singleAppContainerName}
|
|
568
|
+
restart: always
|
|
569
|
+
env_file:
|
|
570
|
+
- ../../.env
|
|
571
|
+
${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''} volumes:
|
|
572
|
+
- ${volumeMount}
|
|
573
|
+
ports:
|
|
574
|
+
- "127.0.0.1:${gatewayPort}:${gatewayPort}"`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
dockerfile,
|
|
579
|
+
compose,
|
|
580
|
+
entrypointScript: runtimeScript,
|
|
581
|
+
syncScript,
|
|
582
|
+
patchScript,
|
|
583
|
+
docker9RouterEntrypointScript,
|
|
584
|
+
gatewayPatchCmd: buildGatewayPatchCmd(),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
root.__openclawDockerGen = {
|
|
589
|
+
encodeBase64Utf8,
|
|
590
|
+
indentBlock,
|
|
591
|
+
build9RouterSmartRouteSyncScript,
|
|
592
|
+
build9RouterPatchScript,
|
|
593
|
+
build9RouterComposeEntrypointScript,
|
|
594
|
+
buildGatewayPatchCmd,
|
|
595
|
+
buildDockerArtifacts,
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
})(typeof globalThis !== 'undefined' ? globalThis : {});
|
|
599
|
+
if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globalThis.__openclawDockerGen) {
|
|
600
|
+
Object.assign(exports, globalThis.__openclawDockerGen);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
|