create-openclaw-bot 5.7.10 → 5.8.1

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.
@@ -1,462 +1,469 @@
1
- // @ts-nocheck
2
- /**
3
- * @fileoverview Centralized bot configuration builders — single source of truth.
4
- *
5
- * Generates openclaw.json, auth-profiles.json, exec-approvals.json, and .env content.
6
- * Used by BOTH the Wizard (IIFE bundle) and CLI (CJS require).
7
- *
8
- * Pattern: same as common-gen.js / workspace-gen.js — IIFE + CJS dual export.
9
- */
10
- (function (root) {
11
-
12
- const _common = (typeof root !== 'undefined' && root.__openclawCommon) || {};
13
-
14
- // ── Helper: slugify a bot name into a safe agent ID ─────────────────────────
15
- function slugify(name) {
16
- return String(name || 'bot').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'bot';
17
- }
18
-
19
- // ── Helper: detect if channel is zalo personal ───────────────────────────────
20
- function isZaloPersonal(channelKey) {
21
- return channelKey === 'zalo-personal';
22
- }
23
-
24
- // ── Helper: generate a random token (works in both browser + Node) ──────────
25
- function generateToken() {
26
- if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
27
- return crypto.randomUUID().replace(/-/g, '');
28
- }
29
- // Fallback for older Node.js
30
- const hex = '0123456789abcdef';
31
- let result = '';
32
- for (let i = 0; i < 32; i++) result += hex[Math.floor(Math.random() * 16)];
33
- return result;
34
- }
35
-
36
-
37
- // ═══════════════════════════════════════════════════════════════════════════════
38
- // buildOpenclawJson — the ONE function that generates the full openclaw.json
39
- // ═══════════════════════════════════════════════════════════════════════════════
40
- /**
41
- * @param {object} opts
42
- * @param {string} opts.channelKey - 'telegram' | 'zalo-personal' | 'zalo-bot'
43
- * @param {string} opts.deployMode - 'docker' | 'native'
44
- * @param {string} opts.providerKey - '9router' | 'openai' | 'ollama' | ...
45
- * @param {object} opts.provider - Provider metadata object from PROVIDERS
46
- * @param {string} opts.model - Primary model ID (e.g. 'smart-route', 'gemma4:e2b')
47
- * @param {boolean} opts.isMultiBot - Multi-bot mode
48
- * @param {Array} opts.agentMetas - [{ agentId, name, desc, persona, token, slashCmd, accountId, workspaceDir }]
49
- * @param {string} opts.groupId - Telegram group ID (multi-bot only)
50
- * @param {Array} opts.selectedSkills - ['browser', 'memory', 'scheduler', ...]
51
- * @param {Array} opts.skills - Full SKILLS registry array
52
- * @param {boolean} opts.hasBrowserDesktop - Browser desktop mode
53
- * @param {boolean} opts.hasBrowserServer - Browser server mode
54
- * @param {number} [opts.gatewayPort=18791]
55
- * @param {Array} [opts.gatewayAllowedOrigins]
56
- * @param {string} [opts.osChoice] - 'windows' | 'macos' | 'vps' | 'ubuntu'
57
- * @param {string} [opts.selectedModel] - For Ollama: specific model selected
58
- */
59
- function buildOpenclawJson(opts) {
60
- const {
61
- channelKey = 'telegram',
62
- deployMode = 'docker',
63
- providerKey = '9router',
64
- provider = {},
65
- model = 'smart-route',
66
- isMultiBot = false,
67
- agentMetas = [],
68
- groupId = '',
69
- selectedSkills = [],
70
- skills = [],
71
- hasBrowserDesktop = false,
72
- hasBrowserServer = false,
73
- gatewayPort = 18791,
74
- gatewayAllowedOrigins = [],
75
- osChoice = '',
76
- selectedModel = '',
77
- } = opts;
78
-
79
- const common = _common;
80
- const is9Router = providerKey === '9router';
81
- const isLocal = !!provider.isLocal;
82
-
83
- // ── agents ────────────────────────────────────────────────────────────────
84
- const agentsList = agentMetas.map((meta) => ({
85
- id: meta.agentId,
86
- ...(meta.name ? { name: meta.name } : {}),
87
- workspace: `.openclaw/${meta.workspaceDir || 'workspace-' + meta.agentId}`,
88
- agentDir: `agents/${meta.agentId}/agent`,
89
- model: { primary: model, fallbacks: [] },
90
- }));
91
-
92
- const cfg = {
93
- meta: { lastTouchedVersion: (_common.OPENCLAW_NPM_SPEC || 'latest').replace('openclaw@', '') },
94
- agents: {
95
- defaults: {
96
- model: { primary: model, fallbacks: [] },
97
- compaction: { mode: 'safeguard' },
98
- timeoutSeconds: isLocal ? 900 : 120,
99
- ...(isLocal ? { llm: { idleTimeoutSeconds: 300 } } : {}),
100
- },
101
- list: agentsList,
102
- },
103
- };
104
-
105
- // ── models.providers ──────────────────────────────────────────────────────
106
- if (is9Router && common.build9RouterProviderConfig) {
107
- cfg.models = {
108
- mode: 'merge',
109
- providers: {
110
- '9router': common.build9RouterProviderConfig(
111
- common.get9RouterBaseUrl ? common.get9RouterBaseUrl(deployMode) : 'http://9router:20128/v1'
112
- ),
113
- },
114
- };
115
- } else if (isLocal) {
116
- const ollamaBaseUrl = deployMode === 'docker' ? 'http://ollama:11434' : 'http://localhost:11434';
117
- const OLLAMA_MODELS = (typeof root !== 'undefined' && root.__openclawData && root.__openclawData.OLLAMA_MODELS)
118
- || (typeof _OLLAMA_MODELS !== 'undefined' ? _OLLAMA_MODELS : []);
119
- const modelList = selectedModel
120
- ? [{ id: selectedModel, name: selectedModel, contextWindow: 128000, maxTokens: 8192 }]
121
- : OLLAMA_MODELS;
122
- cfg.models = {
123
- mode: 'merge',
124
- providers: {
125
- ollama: {
126
- baseUrl: ollamaBaseUrl,
127
- api: 'ollama',
128
- apiKey: 'ollama-local',
129
- models: modelList,
130
- },
131
- },
132
- };
133
- }
134
-
135
- // ── commands ──────────────────────────────────────────────────────────────
136
- cfg.commands = { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' };
137
-
138
- // ── bindings (multi-bot or Zalo) ─────────────────────────────────────────
139
- if (isMultiBot && channelKey === 'telegram') {
140
- cfg.bindings = agentMetas.map((meta) => ({
141
- agentId: meta.agentId,
142
- match: { channel: 'telegram', accountId: meta.accountId || 'default' },
143
- }));
144
- }
145
-
146
- // ── channels ─────────────────────────────────────────────────────────────
147
- cfg.channels = buildChannelConfig({
148
- channelKey, isMultiBot, groupId, agentMetas, botName: agentMetas[0]?.name || 'Bot',
149
- agentId: agentMetas[0]?.agentId || 'bot',
150
- });
151
-
152
- // ── tools ────────────────────────────────────────────────────────────────
153
- cfg.tools = { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } };
154
- if (isMultiBot) {
155
- cfg.tools.agentToAgent = {
156
- enabled: true,
157
- allow: agentMetas.map((meta) => meta.agentId),
158
- };
159
- }
160
-
161
- // ── gateway ──────────────────────────────────────────────────────────────
162
- cfg.gateway = {
163
- port: gatewayPort,
164
- mode: 'local',
165
- bind: (deployMode === 'docker' || osChoice === 'vps') ? 'custom' : 'loopback',
166
- ...(deployMode === 'docker' || osChoice === 'vps' ? { customBindHost: '0.0.0.0' } : {}),
167
- controlUi: {
168
- allowedOrigins: gatewayAllowedOrigins.length > 0
169
- ? gatewayAllowedOrigins
170
- : [`http://localhost:${gatewayPort}`, `http://127.0.0.1:${gatewayPort}`, `http://0.0.0.0:${gatewayPort}`],
171
- },
172
- auth: { mode: 'token', token: generateToken() },
173
- };
174
-
175
- // ── browser ──────────────────────────────────────────────────────────────
176
- if (hasBrowserDesktop) {
177
- cfg.browser = {
178
- enabled: true,
179
- defaultProfile: 'host-chrome',
180
- profiles: { 'host-chrome': { cdpUrl: 'http://127.0.0.1:9222', color: '#4285F4' } },
181
- };
182
- } else if (hasBrowserServer) {
183
- cfg.browser = { enabled: true };
184
- }
185
-
186
- // ── skills ───────────────────────────────────────────────────────────────
187
- const skillEntries = buildSkillsEntries(skills, selectedSkills);
188
- if (Object.keys(skillEntries).length > 0) {
189
- cfg.skills = { entries: skillEntries };
190
- }
191
-
192
- // ── plugins (memory-core dreaming + openclaw-zalo-mod) ────────────────────────────
193
- const pluginsConfig = buildPluginsConfig({
194
- channelKey,
195
- selectedSkills,
196
- botName: agentMetas[0]?.name || 'Bot',
197
- agentId: agentMetas[0]?.agentId || 'bot',
198
- });
199
- cfg.plugins = pluginsConfig.plugins;
200
-
201
- // ── bindings for zalouser ────────────────────────────────────────────────
202
- if (isZaloPersonal(channelKey)) {
203
- cfg.bindings = cfg.bindings || [];
204
- const firstAgentId = agentMetas[0]?.agentId || 'bot';
205
- if (!cfg.bindings.some(b => b.match && b.match.channel === 'zalouser')) {
206
- cfg.bindings.push({ agentId: firstAgentId, match: { channel: 'zalouser' } });
207
- }
208
- }
209
-
210
- return cfg;
211
- }
212
-
213
-
214
- // ═══════════════════════════════════════════════════════════════════════════════
215
- // buildChannelConfig — returns the full `channels: { ... }` object
216
- // ═══════════════════════════════════════════════════════════════════════════════
217
- function buildChannelConfig(opts) {
218
- const { channelKey, isMultiBot, groupId, agentMetas = [], botName, agentId } = opts;
219
- const channels = {};
220
-
221
- if (channelKey === 'telegram') {
222
- const telegramConfig = {
223
- enabled: true,
224
- defaultAccount: 'default',
225
- dmPolicy: 'open',
226
- allowFrom: ['*'],
227
- replyToMode: 'first',
228
- reactionLevel: 'minimal',
229
- actions: {
230
- sendMessage: true,
231
- reactions: true,
232
- },
233
- accounts: {},
234
- };
235
-
236
- if (isMultiBot) {
237
- // Multiple accounts — each bot gets its own account keyed by accountId
238
- telegramConfig.accounts = {};
239
- for (const meta of agentMetas) {
240
- telegramConfig.accounts[meta.accountId || 'default'] = {
241
- botToken: meta.token || '<your_bot_token>',
242
- };
243
- }
244
- telegramConfig.groupPolicy = groupId ? 'allowlist' : 'open';
245
- telegramConfig.groupAllowFrom = ['*'];
246
- telegramConfig.groups = {
247
- [groupId || '*']: { enabled: true, requireMention: false },
248
- };
249
- } else {
250
- // Single bot
251
- telegramConfig.accounts = {
252
- default: {
253
- botToken: (agentMetas[0] && agentMetas[0].token) || '<your_bot_token>',
254
- },
255
- };
256
- }
257
-
258
- channels.telegram = telegramConfig;
259
- } else if (isZaloPersonal(channelKey)) {
260
- // Zalo Personal matches live Mkt/Williams configs
261
- channels.zalouser = {
262
- enabled: true,
263
- defaultAccount: 'default',
264
- accounts: {
265
- default: {
266
- dmPolicy: 'open',
267
- allowFrom: ['*'],
268
- groupPolicy: 'allowlist',
269
- groupAllowFrom: ['*'],
270
- },
271
- },
272
- dmPolicy: 'open',
273
- allowFrom: ['*'],
274
- groupPolicy: 'allowlist',
275
- groupAllowFrom: ['*'],
276
- historyLimit: 50,
277
- groups: {
278
- '*': { enabled: true, requireMention: false },
279
- },
280
- };
281
- } else if (channelKey === 'zalo-bot') {
282
- channels.zalo = { enabled: true, provider: 'official_account' };
283
- }
284
-
285
- return channels;
286
- }
287
-
288
-
289
- // ═══════════════════════════════════════════════════════════════════════════════
290
- // buildPluginsConfig — returns { plugins: { ... } }
291
- // ═══════════════════════════════════════════════════════════════════════════════
292
- function buildPluginsConfig(opts) {
293
- const { channelKey, selectedSkills = [], botName = 'Bot', agentId = 'bot' } = opts;
294
-
295
- const entries = {};
296
-
297
- // memory-core with dreaming always present
298
- entries['memory-core'] = {
299
- config: {
300
- dreaming: {
301
- enabled: selectedSkills.includes('memory'),
302
- },
303
- },
304
- };
305
-
306
- const allow = ['memory-core'];
307
-
308
- // Zalo Personal channel is native; install openclaw-zalo-mod manually via ClawHub when needed.
309
- if (isZaloPersonal(channelKey)) {
310
- allow.push('zalouser');
311
- }
312
-
313
- const plugins = { entries };
314
- plugins.allow = allow;
315
-
316
- return { plugins };
317
- }
318
-
319
-
320
- // ═══════════════════════════════════════════════════════════════════════════════
321
- // buildSkillsEntries — returns { slug: { enabled: true } } map
322
- // ═══════════════════════════════════════════════════════════════════════════════
323
- function buildSkillsEntries(skills, selectedSkillIds) {
324
- const entries = {};
325
- if (!skills || !selectedSkillIds) return entries;
326
-
327
- for (const skill of skills) {
328
- const skillId = skill.value || skill.id;
329
- if (!selectedSkillIds.includes(skillId)) continue;
330
- // Skills without a slug are native (browser, scheduler) — not in skills.entries
331
- const slug = skill.slug;
332
- if (!slug) continue;
333
- // Skip browser-automation slug (handled by browser config)
334
- if (slug === 'browser-automation') continue;
335
- entries[slug] = { enabled: true };
336
- }
337
-
338
- return entries;
339
- }
340
-
341
-
342
- // ═══════════════════════════════════════════════════════════════════════════════
343
- // buildExecApprovalsJson — exec-approvals.json content
344
- // ═══════════════════════════════════════════════════════════════════════════════
345
- function buildExecApprovalsJson(opts) {
346
- const { agentMetas = [] } = opts;
347
- const agentEntries = {};
348
- agentEntries.main = { security: 'full', ask: 'off', askFallback: 'full', autoAllowSkills: true };
349
- for (const meta of agentMetas) {
350
- agentEntries[meta.agentId] = { security: 'full', ask: 'off', askFallback: 'full', autoAllowSkills: true };
351
- }
352
- return {
353
- version: 1,
354
- defaults: { security: 'full', ask: 'off', askFallback: 'full' },
355
- agents: agentEntries,
356
- };
357
- }
358
-
359
-
360
- // ═══════════════════════════════════════════════════════════════════════════════
361
- // buildEnvFileContent .env file content for a single bot
362
- // ═══════════════════════════════════════════════════════════════════════════════
363
- /**
364
- * @param {object} opts
365
- * @param {object} opts.provider - Provider metadata
366
- * @param {string} opts.providerKeyVal - API key value
367
- * @param {string} opts.channelKey - Channel type
368
- * @param {string} opts.botToken - Bot token
369
- * @param {boolean} opts.isMultiBot
370
- * @param {string} opts.groupId
371
- * @param {Array} opts.selectedSkills
372
- * @param {string} opts.ttsOpenaiKey
373
- * @param {string} opts.ttsElevenKey
374
- * @param {string} opts.smtpHost
375
- * @param {string} opts.smtpPort
376
- * @param {string} opts.smtpUser
377
- * @param {string} opts.smtpPass
378
- * @param {boolean} opts.isSharedEnv - If true, omit per-bot token (multi-bot shared .env)
379
- */
380
- function buildEnvFileContent(opts) {
381
- const {
382
- provider = {},
383
- providerKeyVal = '',
384
- channelKey = 'telegram',
385
- botToken = '',
386
- isMultiBot = false,
387
- groupId = '',
388
- selectedSkills = [],
389
- ttsOpenaiKey = '',
390
- ttsElevenKey = '',
391
- smtpHost = 'smtp.gmail.com',
392
- smtpPort = '587',
393
- smtpUser = '',
394
- smtpPass = '',
395
- isSharedEnv = false,
396
- } = opts;
397
-
398
- const lines = [];
399
-
400
- if (provider.isLocal) {
401
- lines.push('OLLAMA_HOST=http://localhost:11434');
402
- lines.push('OLLAMA_API_KEY=ollama-local');
403
- } else if (provider.isProxy) {
404
- lines.push('# 9Router: no API key needed');
405
- } else if (provider.envKey) {
406
- lines.push(`${provider.envKey}=${providerKeyVal || '<your_api_key>'}`);
407
- }
408
-
409
- if (!isSharedEnv) {
410
- if (channelKey === 'telegram') {
411
- lines.push(`TELEGRAM_BOT_TOKEN=${botToken || '<your_bot_token>'}`);
412
- if (isMultiBot && groupId) lines.push(`TELEGRAM_GROUP_ID=${groupId}`);
413
- } else if (channelKey === 'zalo-bot') {
414
- lines.push('ZALO_APP_ID=');
415
- lines.push('ZALO_APP_SECRET=');
416
- lines.push(`ZALO_BOT_TOKEN=${botToken || '<your_zalo_bot_token>'}`);
417
- }
418
- }
419
-
420
- if (selectedSkills.includes('tts')) {
421
- lines.push('');
422
- lines.push('# --- Text-To-Speech ---');
423
- if (ttsOpenaiKey) lines.push(`OPENAI_API_KEY=${ttsOpenaiKey}`);
424
- if (ttsElevenKey) lines.push(`ELEVENLABS_API_KEY=${ttsElevenKey}`);
425
- }
426
-
427
- if (selectedSkills.includes('email')) {
428
- lines.push('');
429
- lines.push('# --- Email ---');
430
- lines.push(`SMTP_HOST=${smtpHost}`);
431
- lines.push(`SMTP_PORT=${smtpPort}`);
432
- lines.push(`SMTP_USER=${smtpUser}`);
433
- lines.push(`SMTP_PASS=${smtpPass}`);
434
- }
435
-
436
- return lines.join('\n') + '\n';
437
- }
438
-
439
-
440
- // ═══════════════════════════════════════════════════════════════════════════════
441
- // Export
442
- // ═══════════════════════════════════════════════════════════════════════════════
443
- const exports = {
444
- slugify,
445
- isZaloPersonal,
446
- generateToken,
447
- buildOpenclawJson,
448
- buildChannelConfig,
449
- buildPluginsConfig,
450
- buildSkillsEntries,
451
- buildExecApprovalsJson,
452
- buildEnvFileContent,
453
- };
454
-
455
- if (typeof root !== 'undefined') {
456
- root.__openclawBotConfig = exports;
457
- }
458
-
459
- })(typeof globalThis !== 'undefined' ? globalThis : {});
460
- if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globalThis.__openclawBotConfig) {
461
- Object.assign(exports, globalThis.__openclawBotConfig);
462
- }
1
+ // @ts-nocheck
2
+ /**
3
+ * @fileoverview Centralized bot configuration builders — single source of truth.
4
+ *
5
+ * Generates openclaw.json, auth-profiles.json, exec-approvals.json, and .env content.
6
+ * Used by BOTH the Wizard (IIFE bundle) and CLI (CJS require).
7
+ *
8
+ * Pattern: same as common-gen.js / workspace-gen.js — IIFE + CJS dual export.
9
+ */
10
+ (function (root) {
11
+
12
+ const _common = (typeof root !== 'undefined' && root.__openclawCommon) || {};
13
+
14
+ // ── Helper: slugify a bot name into a safe agent ID ─────────────────────────
15
+ function slugify(name) {
16
+ return String(name || 'bot').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'bot';
17
+ }
18
+
19
+ // ── Helper: detect if channel is zalo personal ───────────────────────────────
20
+ function isZaloPersonal(channelKey) {
21
+ return channelKey === 'zalo-personal';
22
+ }
23
+
24
+ // ── Helper: generate a random token (works in both browser + Node) ──────────
25
+ function generateToken() {
26
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
27
+ return crypto.randomUUID().replace(/-/g, '');
28
+ }
29
+ // Fallback for older Node.js
30
+ const hex = '0123456789abcdef';
31
+ let result = '';
32
+ for (let i = 0; i < 32; i++) result += hex[Math.floor(Math.random() * 16)];
33
+ return result;
34
+ }
35
+
36
+
37
+ // ═══════════════════════════════════════════════════════════════════════════════
38
+ // buildOpenclawJson — the ONE function that generates the full openclaw.json
39
+ // ═══════════════════════════════════════════════════════════════════════════════
40
+ /**
41
+ * @param {object} opts
42
+ * @param {string} opts.channelKey - 'telegram' | 'zalo-personal' | 'zalo-bot'
43
+ * @param {string} opts.deployMode - 'docker' | 'native'
44
+ * @param {string} opts.providerKey - '9router' | 'openai' | 'ollama' | ...
45
+ * @param {object} opts.provider - Provider metadata object from PROVIDERS
46
+ * @param {string} opts.model - Primary model ID (e.g. 'smart-route', 'gemma4:e2b')
47
+ * @param {boolean} opts.isMultiBot - Multi-bot mode
48
+ * @param {Array} opts.agentMetas - [{ agentId, name, desc, persona, token, slashCmd, accountId, workspaceDir }]
49
+ * @param {string} opts.groupId - Telegram group ID (multi-bot only)
50
+ * @param {Array} opts.selectedSkills - ['browser', 'memory', 'scheduler', ...]
51
+ * @param {Array} opts.skills - Full SKILLS registry array
52
+ * @param {boolean} opts.hasBrowserDesktop - Browser desktop mode
53
+ * @param {boolean} opts.hasBrowserServer - Browser server mode
54
+ * @param {number} [opts.gatewayPort=18789]
55
+ * @param {Array} [opts.gatewayAllowedOrigins]
56
+ * @param {string} [opts.osChoice] - 'windows' | 'macos' | 'vps' | 'ubuntu'
57
+ * @param {string} [opts.selectedModel] - For Ollama: specific model selected
58
+ */
59
+ function buildOpenclawJson(opts) {
60
+ const {
61
+ channelKey = 'telegram',
62
+ deployMode = 'docker',
63
+ providerKey = '9router',
64
+ provider = {},
65
+ model = 'smart-route',
66
+ isMultiBot = false,
67
+ agentMetas = [],
68
+ groupId = '',
69
+ selectedSkills = [],
70
+ skills = [],
71
+ hasBrowserDesktop = false,
72
+ hasBrowserServer = false,
73
+ gatewayPort = 18789,
74
+ gatewayAllowedOrigins = [],
75
+ osChoice = '',
76
+ selectedModel = '',
77
+ routerPort,
78
+ } = opts;
79
+
80
+ const common = _common;
81
+ const is9Router = providerKey === '9router';
82
+ const isLocal = !!provider.isLocal;
83
+
84
+ // ── agents ────────────────────────────────────────────────────────────────
85
+ const agentsList = agentMetas.map((meta) => ({
86
+ id: meta.agentId,
87
+ ...(meta.name ? { name: meta.name } : {}),
88
+ workspace: `/root/project/.openclaw/${meta.workspaceDir || 'workspace-' + meta.agentId}`,
89
+ agentDir: `agents/${meta.agentId}/agent`,
90
+ model: { primary: model, fallbacks: [] },
91
+ }));
92
+
93
+ const cfg = {
94
+ meta: { lastTouchedVersion: (_common.OPENCLAW_NPM_SPEC || 'latest').replace('openclaw@', '') },
95
+ agents: {
96
+ defaults: {
97
+ model: { primary: model, fallbacks: [] },
98
+ compaction: { mode: 'safeguard' },
99
+ timeoutSeconds: isLocal ? 900 : 120,
100
+ ...(isLocal ? { llm: { idleTimeoutSeconds: 300 } } : {}),
101
+ },
102
+ list: agentsList,
103
+ },
104
+ };
105
+
106
+ // ── models.providers ──────────────────────────────────────────────────────
107
+ if (is9Router && common.build9RouterProviderConfig) {
108
+ cfg.models = {
109
+ mode: 'merge',
110
+ providers: {
111
+ '9router': common.build9RouterProviderConfig(
112
+ common.get9RouterBaseUrl ? common.get9RouterBaseUrl(deployMode, routerPort) : `http://9router:${routerPort || 20128}/v1`
113
+ ),
114
+ },
115
+ };
116
+ } else if (isLocal) {
117
+ const ollamaBaseUrl = deployMode === 'docker' ? 'http://ollama:11434' : 'http://localhost:11434';
118
+ const OLLAMA_MODELS = (typeof root !== 'undefined' && root.__openclawData && root.__openclawData.OLLAMA_MODELS)
119
+ || (typeof _OLLAMA_MODELS !== 'undefined' ? _OLLAMA_MODELS : []);
120
+ const modelList = selectedModel
121
+ ? [{ id: selectedModel, name: selectedModel, contextWindow: 128000, maxTokens: 8192 }]
122
+ : OLLAMA_MODELS;
123
+ cfg.models = {
124
+ mode: 'merge',
125
+ providers: {
126
+ ollama: {
127
+ baseUrl: ollamaBaseUrl,
128
+ api: 'ollama',
129
+ apiKey: 'ollama-local',
130
+ models: modelList,
131
+ },
132
+ },
133
+ };
134
+ }
135
+
136
+ // ── commands ──────────────────────────────────────────────────────────────
137
+ cfg.commands = { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' };
138
+ if (selectedSkills.includes('scheduler')) {
139
+ cfg.commands.ownerAllowFrom = ['*'];
140
+ }
141
+
142
+ // ── bindings (multi-bot or Zalo) ─────────────────────────────────────────
143
+ if (isMultiBot && channelKey === 'telegram') {
144
+ cfg.bindings = agentMetas.map((meta) => ({
145
+ agentId: meta.agentId,
146
+ match: { channel: 'telegram', accountId: meta.accountId || 'default' },
147
+ }));
148
+ }
149
+
150
+ // ── channels ─────────────────────────────────────────────────────────────
151
+ cfg.channels = buildChannelConfig({
152
+ channelKey, isMultiBot, groupId, agentMetas, botName: agentMetas[0]?.name || 'Bot',
153
+ agentId: agentMetas[0]?.agentId || 'bot',
154
+ });
155
+
156
+ // ── tools ────────────────────────────────────────────────────────────────
157
+ cfg.tools = { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } };
158
+ if (selectedSkills.includes('scheduler')) {
159
+ cfg.tools.alsoAllow = ['group:automation'];
160
+ }
161
+ if (isMultiBot) {
162
+ cfg.tools.agentToAgent = {
163
+ enabled: true,
164
+ allow: agentMetas.map((meta) => meta.agentId),
165
+ };
166
+ }
167
+
168
+ // ── gateway ──────────────────────────────────────────────────────────────
169
+ cfg.gateway = {
170
+ port: gatewayPort,
171
+ mode: 'local',
172
+ bind: (deployMode === 'docker' || osChoice === 'vps') ? 'custom' : 'loopback',
173
+ ...(deployMode === 'docker' || osChoice === 'vps' ? { customBindHost: '0.0.0.0' } : {}),
174
+ controlUi: {
175
+ allowedOrigins: gatewayAllowedOrigins.length > 0
176
+ ? gatewayAllowedOrigins
177
+ : [`http://localhost:${gatewayPort}`, `http://127.0.0.1:${gatewayPort}`, `http://0.0.0.0:${gatewayPort}`],
178
+ },
179
+ auth: { mode: 'token', token: generateToken() },
180
+ };
181
+
182
+ // ── browser ──────────────────────────────────────────────────────────────
183
+ if (hasBrowserDesktop) {
184
+ cfg.browser = {
185
+ enabled: true,
186
+ defaultProfile: 'host-chrome',
187
+ profiles: { 'host-chrome': { cdpUrl: 'http://127.0.0.1:9222', color: '#4285F4' } },
188
+ };
189
+ } else if (hasBrowserServer) {
190
+ cfg.browser = { enabled: true };
191
+ }
192
+
193
+ // ── skills ───────────────────────────────────────────────────────────────
194
+ const skillEntries = buildSkillsEntries(skills, selectedSkills);
195
+ if (Object.keys(skillEntries).length > 0) {
196
+ cfg.skills = { entries: skillEntries };
197
+ }
198
+
199
+ // ── plugins (memory-core dreaming + openclaw-zalo-mod) ────────────────────────────
200
+ const pluginsConfig = buildPluginsConfig({
201
+ channelKey,
202
+ selectedSkills,
203
+ botName: agentMetas[0]?.name || 'Bot',
204
+ agentId: agentMetas[0]?.agentId || 'bot',
205
+ });
206
+ cfg.plugins = pluginsConfig.plugins;
207
+
208
+ // ── bindings for zalouser ────────────────────────────────────────────────
209
+ if (isZaloPersonal(channelKey)) {
210
+ cfg.bindings = cfg.bindings || [];
211
+ const firstAgentId = agentMetas[0]?.agentId || 'bot';
212
+ if (!cfg.bindings.some(b => b.match && b.match.channel === 'zalouser')) {
213
+ cfg.bindings.push({ agentId: firstAgentId, match: { channel: 'zalouser' } });
214
+ }
215
+ }
216
+
217
+ return cfg;
218
+ }
219
+
220
+
221
+ // ═══════════════════════════════════════════════════════════════════════════════
222
+ // buildChannelConfig returns the full `channels: { ... }` object
223
+ // ═══════════════════════════════════════════════════════════════════════════════
224
+ function buildChannelConfig(opts) {
225
+ const { channelKey, isMultiBot, groupId, agentMetas = [], botName, agentId } = opts;
226
+ const channels = {};
227
+
228
+ if (channelKey === 'telegram') {
229
+ const telegramConfig = {
230
+ enabled: true,
231
+ defaultAccount: 'default',
232
+ dmPolicy: 'open',
233
+ allowFrom: ['*'],
234
+ replyToMode: 'first',
235
+ reactionLevel: 'minimal',
236
+ actions: {
237
+ sendMessage: true,
238
+ reactions: true,
239
+ },
240
+ accounts: {},
241
+ };
242
+
243
+ if (isMultiBot) {
244
+ // Multiple accounts each bot gets its own account keyed by accountId
245
+ telegramConfig.accounts = {};
246
+ for (const meta of agentMetas) {
247
+ telegramConfig.accounts[meta.accountId || 'default'] = {
248
+ botToken: meta.token || '<your_bot_token>',
249
+ };
250
+ }
251
+ telegramConfig.groupPolicy = groupId ? 'allowlist' : 'open';
252
+ telegramConfig.groupAllowFrom = ['*'];
253
+ telegramConfig.groups = {
254
+ [groupId || '*']: { enabled: true, requireMention: false },
255
+ };
256
+ } else {
257
+ // Single bot
258
+ telegramConfig.accounts = {
259
+ default: {
260
+ botToken: (agentMetas[0] && agentMetas[0].token) || '<your_bot_token>',
261
+ },
262
+ };
263
+ }
264
+
265
+ channels.telegram = telegramConfig;
266
+ } else if (isZaloPersonal(channelKey)) {
267
+ // Zalo Personal — matches live Mkt/Williams configs
268
+ channels.zalouser = {
269
+ enabled: true,
270
+ defaultAccount: 'default',
271
+ accounts: {
272
+ default: {
273
+ dmPolicy: 'open',
274
+ allowFrom: ['*'],
275
+ groupPolicy: 'allowlist',
276
+ groupAllowFrom: ['*'],
277
+ },
278
+ },
279
+ dmPolicy: 'open',
280
+ allowFrom: ['*'],
281
+ groupPolicy: 'allowlist',
282
+ groupAllowFrom: ['*'],
283
+ historyLimit: 50,
284
+ groups: {
285
+ '*': { enabled: true, requireMention: false },
286
+ },
287
+ };
288
+ } else if (channelKey === 'zalo-bot') {
289
+ channels.zalo = { enabled: true, provider: 'official_account' };
290
+ }
291
+
292
+ return channels;
293
+ }
294
+
295
+
296
+ // ═══════════════════════════════════════════════════════════════════════════════
297
+ // buildPluginsConfig returns { plugins: { ... } }
298
+ // ═══════════════════════════════════════════════════════════════════════════════
299
+ function buildPluginsConfig(opts) {
300
+ const { channelKey, selectedSkills = [], botName = 'Bot', agentId = 'bot' } = opts;
301
+
302
+ const entries = {};
303
+
304
+ // memory-core with dreaming — always present
305
+ entries['memory-core'] = {
306
+ config: {
307
+ dreaming: {
308
+ enabled: selectedSkills.includes('memory'),
309
+ },
310
+ },
311
+ };
312
+
313
+ const allow = ['memory-core'];
314
+
315
+ // Zalo Personal channel is native; install openclaw-zalo-mod manually via ClawHub when needed.
316
+ if (isZaloPersonal(channelKey)) {
317
+ allow.push('zalouser');
318
+ }
319
+
320
+ const plugins = { entries };
321
+ plugins.allow = allow;
322
+
323
+ return { plugins };
324
+ }
325
+
326
+
327
+ // ═══════════════════════════════════════════════════════════════════════════════
328
+ // buildSkillsEntries returns { slug: { enabled: true } } map
329
+ // ═══════════════════════════════════════════════════════════════════════════════
330
+ function buildSkillsEntries(skills, selectedSkillIds) {
331
+ const entries = {};
332
+ if (!skills || !selectedSkillIds) return entries;
333
+
334
+ for (const skill of skills) {
335
+ const skillId = skill.value || skill.id;
336
+ if (!selectedSkillIds.includes(skillId)) continue;
337
+ // Skills without a slug are native (browser, scheduler) — not in skills.entries
338
+ const slug = skill.slug;
339
+ if (!slug) continue;
340
+ // Skip browser-automation slug (handled by browser config)
341
+ if (slug === 'browser-automation') continue;
342
+ entries[slug] = { enabled: true };
343
+ }
344
+
345
+ return entries;
346
+ }
347
+
348
+
349
+ // ═══════════════════════════════════════════════════════════════════════════════
350
+ // buildExecApprovalsJson exec-approvals.json content
351
+ // ═══════════════════════════════════════════════════════════════════════════════
352
+ function buildExecApprovalsJson(opts) {
353
+ const { agentMetas = [] } = opts;
354
+ const agentEntries = {};
355
+ agentEntries.main = { security: 'full', ask: 'off', askFallback: 'full', autoAllowSkills: true };
356
+ for (const meta of agentMetas) {
357
+ agentEntries[meta.agentId] = { security: 'full', ask: 'off', askFallback: 'full', autoAllowSkills: true };
358
+ }
359
+ return {
360
+ version: 1,
361
+ defaults: { security: 'full', ask: 'off', askFallback: 'full' },
362
+ agents: agentEntries,
363
+ };
364
+ }
365
+
366
+
367
+ // ═══════════════════════════════════════════════════════════════════════════════
368
+ // buildEnvFileContent .env file content for a single bot
369
+ // ═══════════════════════════════════════════════════════════════════════════════
370
+ /**
371
+ * @param {object} opts
372
+ * @param {object} opts.provider - Provider metadata
373
+ * @param {string} opts.providerKeyVal - API key value
374
+ * @param {string} opts.channelKey - Channel type
375
+ * @param {string} opts.botToken - Bot token
376
+ * @param {boolean} opts.isMultiBot
377
+ * @param {string} opts.groupId
378
+ * @param {Array} opts.selectedSkills
379
+ * @param {string} opts.ttsOpenaiKey
380
+ * @param {string} opts.ttsElevenKey
381
+ * @param {string} opts.smtpHost
382
+ * @param {string} opts.smtpPort
383
+ * @param {string} opts.smtpUser
384
+ * @param {string} opts.smtpPass
385
+ * @param {boolean} opts.isSharedEnv - If true, omit per-bot token (multi-bot shared .env)
386
+ */
387
+ function buildEnvFileContent(opts) {
388
+ const {
389
+ provider = {},
390
+ providerKeyVal = '',
391
+ channelKey = 'telegram',
392
+ botToken = '',
393
+ isMultiBot = false,
394
+ groupId = '',
395
+ selectedSkills = [],
396
+ ttsOpenaiKey = '',
397
+ ttsElevenKey = '',
398
+ smtpHost = 'smtp.gmail.com',
399
+ smtpPort = '587',
400
+ smtpUser = '',
401
+ smtpPass = '',
402
+ isSharedEnv = false,
403
+ } = opts;
404
+
405
+ const lines = [];
406
+
407
+ if (provider.isLocal) {
408
+ lines.push('OLLAMA_HOST=http://localhost:11434');
409
+ lines.push('OLLAMA_API_KEY=ollama-local');
410
+ } else if (provider.isProxy) {
411
+ lines.push('# 9Router: no API key needed');
412
+ } else if (provider.envKey) {
413
+ lines.push(`${provider.envKey}=${providerKeyVal || '<your_api_key>'}`);
414
+ }
415
+
416
+ if (!isSharedEnv) {
417
+ if (channelKey === 'telegram') {
418
+ lines.push(`TELEGRAM_BOT_TOKEN=${botToken || '<your_bot_token>'}`);
419
+ if (isMultiBot && groupId) lines.push(`TELEGRAM_GROUP_ID=${groupId}`);
420
+ } else if (channelKey === 'zalo-bot') {
421
+ lines.push('ZALO_APP_ID=');
422
+ lines.push('ZALO_APP_SECRET=');
423
+ lines.push(`ZALO_BOT_TOKEN=${botToken || '<your_zalo_bot_token>'}`);
424
+ }
425
+ }
426
+
427
+ if (selectedSkills.includes('tts')) {
428
+ lines.push('');
429
+ lines.push('# --- Text-To-Speech ---');
430
+ if (ttsOpenaiKey) lines.push(`OPENAI_API_KEY=${ttsOpenaiKey}`);
431
+ if (ttsElevenKey) lines.push(`ELEVENLABS_API_KEY=${ttsElevenKey}`);
432
+ }
433
+
434
+ if (selectedSkills.includes('email')) {
435
+ lines.push('');
436
+ lines.push('# --- Email ---');
437
+ lines.push(`SMTP_HOST=${smtpHost}`);
438
+ lines.push(`SMTP_PORT=${smtpPort}`);
439
+ lines.push(`SMTP_USER=${smtpUser}`);
440
+ lines.push(`SMTP_PASS=${smtpPass}`);
441
+ }
442
+
443
+ return lines.join('\n') + '\n';
444
+ }
445
+
446
+
447
+ // ═══════════════════════════════════════════════════════════════════════════════
448
+ // Export
449
+ // ═══════════════════════════════════════════════════════════════════════════════
450
+ const exports = {
451
+ slugify,
452
+ isZaloPersonal,
453
+ generateToken,
454
+ buildOpenclawJson,
455
+ buildChannelConfig,
456
+ buildPluginsConfig,
457
+ buildSkillsEntries,
458
+ buildExecApprovalsJson,
459
+ buildEnvFileContent,
460
+ };
461
+
462
+ if (typeof root !== 'undefined') {
463
+ root.__openclawBotConfig = exports;
464
+ }
465
+
466
+ })(typeof globalThis !== 'undefined' ? globalThis : {});
467
+ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globalThis.__openclawBotConfig) {
468
+ Object.assign(exports, globalThis.__openclawBotConfig);
469
+ }