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.
- package/README.md +61 -219
- package/README.vi.md +63 -216
- package/dist/cli.js +92 -2777
- package/dist/legacy-cli.js +2812 -0
- package/dist/server/local-server.js +2568 -0
- package/dist/setup/data/header.js +80 -80
- package/dist/setup/data/index.js +0 -1
- package/dist/setup/data/plugins.js +8 -1
- package/dist/setup/data/skills.js +2 -10
- package/dist/setup/shared/bot-config-gen.js +469 -462
- package/dist/setup/shared/common-gen.js +313 -315
- package/dist/setup/shared/docker-gen.js +193 -124
- package/dist/setup/shared/install-gen.js +566 -566
- package/dist/setup/shared/workspace-gen.js +813 -525
- package/dist/setup.js +729 -499
- package/dist/web/app.js +1247 -0
- package/dist/web/bvvbank.jpg +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/momo.jpg +0 -0
- package/dist/web/openclaw-logo.png +0 -0
- package/dist/web/openclaw-logo.svg +1 -0
- package/dist/web/styles.css +1375 -0
- package/package.json +40 -39
package/dist/setup.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/* ============================================
|
|
2
|
-
OpenClaw Setup Wizard
|
|
2
|
+
OpenClaw Setup Wizard  Logic v2
|
|
3
3
|
Multi-model, Multi-plugin, Multi-channel
|
|
4
4
|
============================================ */
|
|
5
|
-
// AUTO-GENERATED by build.mjs
|
|
5
|
+
// AUTO-GENERATED by build.mjs  edit files in src/setup/ instead
|
|
6
6
|
|
|
7
7
|
(function () {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
-
//
|
|
10
|
+
// ââ€â‚¬Ã¢â€â‚¬ Globals: CDN logos, state, shared utils (setup/data/header.js) ââ€â‚¬
|
|
11
11
|
// @ts-nocheck
|
|
12
12
|
/* eslint-disable no-undef, no-unused-vars */
|
|
13
13
|
/**
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
|| 'grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api';
|
|
77
77
|
|
|
78
78
|
function getGatewayAllowedOrigins(port) {
|
|
79
|
-
const normalizedPort = Number(port) ||
|
|
79
|
+
const normalizedPort = Number(port) || 18789;
|
|
80
80
|
const origins = new Set([
|
|
81
81
|
`http://localhost:${normalizedPort}`,
|
|
82
82
|
`http://127.0.0.1:${normalizedPort}`,
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
return Array.from(origins);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
//
|
|
92
|
+
// ââ€â‚¬Ã¢â€â‚¬ PROVIDERS object (setup/data/providers.js) ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
93
93
|
// @ts-nocheck
|
|
94
94
|
/* eslint-disable no-undef, no-unused-vars */
|
|
95
95
|
/**
|
|
@@ -212,7 +212,7 @@
|
|
|
212
212
|
};
|
|
213
213
|
|
|
214
214
|
|
|
215
|
-
//
|
|
215
|
+
// ââ€â‚¬Ã¢â€â‚¬ CHANNELS, system prompts, security rules (setup/data/channels.js)
|
|
216
216
|
// @ts-nocheck
|
|
217
217
|
/* eslint-disable no-undef, no-unused-vars */
|
|
218
218
|
/**
|
|
@@ -383,7 +383,7 @@
|
|
|
383
383
|
- ✅ Limit exposed ports (only 38789)`,
|
|
384
384
|
};
|
|
385
385
|
|
|
386
|
-
//
|
|
386
|
+
// ââ€â‚¬Ã¢â€â‚¬ PLUGINS list (setup/data/plugins.js) ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
387
387
|
// @ts-nocheck
|
|
388
388
|
/* eslint-disable no-undef, no-unused-vars */
|
|
389
389
|
/**
|
|
@@ -406,6 +406,13 @@
|
|
|
406
406
|
*/
|
|
407
407
|
// ========== Available Plugins (npm packages — runtime/channel extensions) ==========
|
|
408
408
|
const PLUGINS = [
|
|
409
|
+
{
|
|
410
|
+
id: 'browser-automation',
|
|
411
|
+
name: 'Browser Automation ⭐',
|
|
412
|
+
icon: '🌐',
|
|
413
|
+
descVi: 'Smart Search + Điều khiển trình duyệt Chrome/Chromium (ẩn & thật)', descEn: 'Smart Search + Chrome/Chromium browser control (headless & real)',
|
|
414
|
+
package: 'openclaw-browser-automation',
|
|
415
|
+
},
|
|
409
416
|
{
|
|
410
417
|
id: 'telegram-multibot-relay',
|
|
411
418
|
name: 'Telegram Multi-Bot Relay',
|
|
@@ -445,7 +452,7 @@
|
|
|
445
452
|
];
|
|
446
453
|
|
|
447
454
|
|
|
448
|
-
//
|
|
455
|
+
// ââ€â‚¬Ã¢â€â‚¬ SKILLS list (setup/data/skills.js) ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
449
456
|
// @ts-nocheck
|
|
450
457
|
/* eslint-disable no-undef, no-unused-vars */
|
|
451
458
|
/**
|
|
@@ -468,14 +475,6 @@
|
|
|
468
475
|
*/
|
|
469
476
|
// ========== Available Skills (ClawHub registry — agent capabilities) ==========
|
|
470
477
|
const SKILLS = [
|
|
471
|
-
{
|
|
472
|
-
id: 'browser',
|
|
473
|
-
name: 'Browser Automation ⭐(Khuyên dùng)',
|
|
474
|
-
icon: '🌐',
|
|
475
|
-
descVi: 'Tự động thao tác trình duyệt (Playwright)', descEn: 'Automated browser control (Playwright)',
|
|
476
|
-
slug: 'browser-automation',
|
|
477
|
-
noteVi: 'Cần bật Chrome Debug Mode trên máy host', noteEn: 'Requires Chrome Debug Mode on host',
|
|
478
|
-
},
|
|
479
478
|
{
|
|
480
479
|
id: 'memory',
|
|
481
480
|
name: 'Long-term Memory ⭐(Khuyên dùng)',
|
|
@@ -574,7 +573,7 @@
|
|
|
574
573
|
className: 'plugin-card__badge plugin-card__badge--recommended'
|
|
575
574
|
};
|
|
576
575
|
}
|
|
577
|
-
if (skill.id === '
|
|
576
|
+
if (skill.id === 'scheduler') {
|
|
578
577
|
return {
|
|
579
578
|
text: isVi ? 'Khuyên dùng' : 'Recommended',
|
|
580
579
|
className: 'plugin-card__badge plugin-card__badge--recommended'
|
|
@@ -616,10 +615,10 @@
|
|
|
616
615
|
}
|
|
617
616
|
|
|
618
617
|
|
|
619
|
-
//
|
|
618
|
+
// ââ€â‚¬Ã¢â€â‚¬ Shared runtime constants, relay helpers, auth profile builders (setup/shared/common-gen.js)
|
|
620
619
|
// @ts-nocheck
|
|
621
620
|
(function (root) {
|
|
622
|
-
const OPENCLAW_NPM_SPEC = 'openclaw@
|
|
621
|
+
const OPENCLAW_NPM_SPEC = 'openclaw@latest';
|
|
623
622
|
const OPENCLAW_RUNTIME_PACKAGES = 'grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api';
|
|
624
623
|
const NINE_ROUTER_NPM_SPEC = '9router@latest';
|
|
625
624
|
const NINE_ROUTER_PORT = 20128;
|
|
@@ -861,8 +860,9 @@ If setup reported a plugin install error, run this after the bot is running:
|
|
|
861
860
|
return JSON.stringify(buildAuthProfilesJson(options), null, 2);
|
|
862
861
|
}
|
|
863
862
|
|
|
864
|
-
function get9RouterBaseUrl(deployMode = 'native') {
|
|
865
|
-
|
|
863
|
+
function get9RouterBaseUrl(deployMode = 'native', routerPort) {
|
|
864
|
+
const port = routerPort || NINE_ROUTER_PORT;
|
|
865
|
+
return deployMode === 'docker' ? `http://9router:${port}/v1` : `http://localhost:${port}/v1`;
|
|
866
866
|
}
|
|
867
867
|
|
|
868
868
|
function build9RouterProviderConfig(baseUrl = `${NINE_ROUTER_API_BASE_URL}/v1`) {
|
|
@@ -870,6 +870,9 @@ If setup reported a plugin install error, run this after the bot is running:
|
|
|
870
870
|
baseUrl,
|
|
871
871
|
apiKey: NINE_ROUTER_PROXY_API_KEY,
|
|
872
872
|
api: 'openai-completions',
|
|
873
|
+
request: {
|
|
874
|
+
allowPrivateNetwork: true,
|
|
875
|
+
},
|
|
873
876
|
models: [
|
|
874
877
|
{
|
|
875
878
|
id: 'smart-route',
|
|
@@ -877,18 +880,12 @@ If setup reported a plugin install error, run this after the bot is running:
|
|
|
877
880
|
contextWindow: 200000,
|
|
878
881
|
maxTokens: 8192,
|
|
879
882
|
},
|
|
880
|
-
...SUPPORTED_CODEX_MODELS.map((id) => ({
|
|
881
|
-
id,
|
|
882
|
-
name: `Codex ${id.slice(3).replace(/-/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase())}`,
|
|
883
|
-
contextWindow: 200000,
|
|
884
|
-
maxTokens: 8192,
|
|
885
|
-
})),
|
|
886
883
|
],
|
|
887
884
|
};
|
|
888
885
|
}
|
|
889
886
|
|
|
890
|
-
function buildGatewayConfig(port =
|
|
891
|
-
const normalizedPort = Number(port) ||
|
|
887
|
+
function buildGatewayConfig(port = 18789, deployMode = 'native', allowedOrigins = [], osChoice = '') {
|
|
888
|
+
const normalizedPort = Number(port) || 18789;
|
|
892
889
|
const cfg = {
|
|
893
890
|
port: normalizedPort,
|
|
894
891
|
mode: 'local',
|
|
@@ -933,7 +930,7 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
|
|
|
933
930
|
Object.assign(exports, globalThis.__openclawCommon);
|
|
934
931
|
}
|
|
935
932
|
|
|
936
|
-
//
|
|
933
|
+
// ââ€â‚¬Ã¢â€â‚¬ Shared workspace file builders (IDENTITY, SOUL, AGENTS, TOOLS, TEAMS...) (setup/shared/workspace-gen.js)
|
|
937
934
|
/** @typedef {typeof globalThis & { __openclawWorkspace?: Record<string, Function> }} OpenClawWorkspaceRoot */
|
|
938
935
|
|
|
939
936
|
const workspaceRoot = /** @type {OpenClawWorkspaceRoot} */ (
|
|
@@ -1061,6 +1058,226 @@ function buildBootstrapDoc(options = {}) {
|
|
|
1061
1058
|
: `# BOOTSTRAP\n\n## Startup role\n- You are **${botName}**.\n- Your identity, role, and style are already defined in **IDENTITY.md**, **SOUL.md**, and **AGENTS.md**.\n- Use those files as the highest-priority source of truth for your persona in this workspace.\n\n## Mandatory\n- Do not ask the user to redefine your name, vibe, persona, signature emoji, or assistant style.\n- Do not say you just woke up, just came online, are choosing your identity, or any similar onboarding line.\n- Do not behave like a blank freshly-created bot.\n- If the user only sends a short opener like "hi" or "alo", greet briefly and reply in your existing role.\n\n## Telegram\n- Before EVERY user-visible Telegram reply, if the current turn exposes a native reaction tool/action, use it to add \`👍\` first unless \`👍\` is already present.\n- Never claim that you reacted unless the tool actually ran and the reaction is real on the chat.\n- If the target chat does not support real reactions or the tool is not exposed, still reply normally and do not mention the technical limitation.\n`;
|
|
1062
1059
|
}
|
|
1063
1060
|
|
|
1061
|
+
function buildSearchToolJs() {
|
|
1062
|
+
return `/**
|
|
1063
|
+
* search-tool.js — Stealth search via Playwright Headless Chromium or CDP fallback
|
|
1064
|
+
* Zero tokens, no API keys, concurrent multi-engine scraping (Google + Bing + DuckDuckGo).
|
|
1065
|
+
* Usage: node search-tool.js "<query>" [limit]
|
|
1066
|
+
*/
|
|
1067
|
+
let playwright;
|
|
1068
|
+
try {
|
|
1069
|
+
playwright = require('playwright-core');
|
|
1070
|
+
} catch (e) {
|
|
1071
|
+
try {
|
|
1072
|
+
playwright = require('/usr/local/lib/node_modules/openclaw/node_modules/playwright-core');
|
|
1073
|
+
} catch (err) {
|
|
1074
|
+
try {
|
|
1075
|
+
const path = require('path');
|
|
1076
|
+
playwright = require(path.join(process.cwd(), 'node_modules', 'playwright-core'));
|
|
1077
|
+
} catch (x) {
|
|
1078
|
+
console.error(JSON.stringify({ error: 'Playwright not found! Install it or run within OpenClaw environment.' }));
|
|
1079
|
+
process.exit(1);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
const { chromium } = playwright;
|
|
1084
|
+
|
|
1085
|
+
const query = process.argv[2];
|
|
1086
|
+
const limit = parseInt(process.argv[3]) || 5;
|
|
1087
|
+
const CDP_URL = 'http://127.0.0.1:9222';
|
|
1088
|
+
|
|
1089
|
+
if (!query) {
|
|
1090
|
+
console.error(JSON.stringify({ error: 'Usage: node search-tool.js "<query>" [limit]' }));
|
|
1091
|
+
process.exit(1);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
(async () => {
|
|
1095
|
+
let browser;
|
|
1096
|
+
let ctx;
|
|
1097
|
+
let isStandalone = false;
|
|
1098
|
+
try {
|
|
1099
|
+
// Try connecting to active Chrome CDP first
|
|
1100
|
+
try {
|
|
1101
|
+
browser = await chromium.connectOverCDP(CDP_URL, { timeout: 3000 });
|
|
1102
|
+
ctx = browser.contexts()[0];
|
|
1103
|
+
} catch (e) {
|
|
1104
|
+
// Fallback to standalone headless Chromium launch
|
|
1105
|
+
browser = await chromium.launch({
|
|
1106
|
+
headless: true,
|
|
1107
|
+
args: [
|
|
1108
|
+
'--no-sandbox',
|
|
1109
|
+
'--disable-gpu',
|
|
1110
|
+
'--disable-dev-shm-usage',
|
|
1111
|
+
'--disable-blink-features=AutomationControlled'
|
|
1112
|
+
]
|
|
1113
|
+
});
|
|
1114
|
+
isStandalone = true;
|
|
1115
|
+
ctx = await browser.newContext({
|
|
1116
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Run search queries concurrently on three search engines
|
|
1121
|
+
const [googleResults, bingResults, ddgResults] = await Promise.all([
|
|
1122
|
+
// Google
|
|
1123
|
+
(async () => {
|
|
1124
|
+
const page = await ctx.newPage();
|
|
1125
|
+
try {
|
|
1126
|
+
await page.goto('https://www.google.com/search?q=' + encodeURIComponent(query) + '&hl=vi', { waitUntil: 'domcontentloaded', timeout: 10000 });
|
|
1127
|
+
const res = await page.evaluate(() => {
|
|
1128
|
+
const list = [];
|
|
1129
|
+
const links = Array.from(document.querySelectorAll('a h3'));
|
|
1130
|
+
for (const head of links) {
|
|
1131
|
+
const a = head.closest('a');
|
|
1132
|
+
if (!a) continue;
|
|
1133
|
+
const url = a.href;
|
|
1134
|
+
const title = head.textContent || '';
|
|
1135
|
+
let snippet = '';
|
|
1136
|
+
let parent = a.parentElement;
|
|
1137
|
+
while (parent && parent.tagName !== 'DIV') {
|
|
1138
|
+
parent = parent.parentElement;
|
|
1139
|
+
}
|
|
1140
|
+
if (parent) {
|
|
1141
|
+
const descEl = parent.parentElement?.querySelector('.VwiC3b, .yHGvwa, div[style*="-webkit-line-clamp"]');
|
|
1142
|
+
if (descEl) {
|
|
1143
|
+
snippet = descEl.textContent || '';
|
|
1144
|
+
} else {
|
|
1145
|
+
const texts = Array.from(parent.parentElement?.querySelectorAll('div, span') || [])
|
|
1146
|
+
.map(el => el.textContent.trim())
|
|
1147
|
+
.filter(txt => txt.length > 30 && !txt.includes(title));
|
|
1148
|
+
if (texts.length > 0) snippet = texts[0];
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (url && title) {
|
|
1152
|
+
list.push({ title, url, snippet });
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return list;
|
|
1156
|
+
});
|
|
1157
|
+
await page.close();
|
|
1158
|
+
return res;
|
|
1159
|
+
} catch (e) {
|
|
1160
|
+
if (page) await page.close();
|
|
1161
|
+
return [];
|
|
1162
|
+
}
|
|
1163
|
+
})(),
|
|
1164
|
+
|
|
1165
|
+
// Bing
|
|
1166
|
+
(async () => {
|
|
1167
|
+
const page = await ctx.newPage();
|
|
1168
|
+
try {
|
|
1169
|
+
await page.goto('https://www.bing.com/search?q=' + encodeURIComponent(query), { waitUntil: 'domcontentloaded', timeout: 10000 });
|
|
1170
|
+
const res = await page.evaluate(() => {
|
|
1171
|
+
const list = [];
|
|
1172
|
+
const items = document.querySelectorAll('li.b_algo');
|
|
1173
|
+
for (const item of items) {
|
|
1174
|
+
const titleEl = item.querySelector('h2 a');
|
|
1175
|
+
if (!titleEl) continue;
|
|
1176
|
+
const title = titleEl.textContent || '';
|
|
1177
|
+
const url = titleEl.href;
|
|
1178
|
+
let snippet = '';
|
|
1179
|
+
const snippetEl = item.querySelector('.b_caption p, .b_snippet, p');
|
|
1180
|
+
if (snippetEl) {
|
|
1181
|
+
snippet = snippetEl.textContent || '';
|
|
1182
|
+
}
|
|
1183
|
+
if (url && title) {
|
|
1184
|
+
list.push({ title, url, snippet });
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return list;
|
|
1188
|
+
});
|
|
1189
|
+
await page.close();
|
|
1190
|
+
return res;
|
|
1191
|
+
} catch (e) {
|
|
1192
|
+
if (page) await page.close();
|
|
1193
|
+
return [];
|
|
1194
|
+
}
|
|
1195
|
+
})(),
|
|
1196
|
+
|
|
1197
|
+
// DuckDuckGo
|
|
1198
|
+
(async () => {
|
|
1199
|
+
const page = await ctx.newPage();
|
|
1200
|
+
try {
|
|
1201
|
+
await page.goto('https://html.duckduckgo.com/html/?q=' + encodeURIComponent(query), { waitUntil: 'domcontentloaded', timeout: 10000 });
|
|
1202
|
+
const res = await page.evaluate(() => {
|
|
1203
|
+
const list = [];
|
|
1204
|
+
const elements = document.querySelectorAll('.result');
|
|
1205
|
+
for (const el of elements) {
|
|
1206
|
+
const titleEl = el.querySelector('.result__title a');
|
|
1207
|
+
const snippetEl = el.querySelector('.result__snippet');
|
|
1208
|
+
if (titleEl) {
|
|
1209
|
+
list.push({
|
|
1210
|
+
title: titleEl.textContent.trim(),
|
|
1211
|
+
url: titleEl.href,
|
|
1212
|
+
snippet: snippetEl ? snippetEl.textContent.trim() : ''
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return list;
|
|
1217
|
+
});
|
|
1218
|
+
await page.close();
|
|
1219
|
+
return res;
|
|
1220
|
+
} catch (e) {
|
|
1221
|
+
if (page) await page.close();
|
|
1222
|
+
return [];
|
|
1223
|
+
}
|
|
1224
|
+
})()
|
|
1225
|
+
]);
|
|
1226
|
+
|
|
1227
|
+
// Deduplicate results by normalized URL
|
|
1228
|
+
const allResults = [...googleResults, ...bingResults, ...ddgResults];
|
|
1229
|
+
const uniqueResults = [];
|
|
1230
|
+
const seenUrls = new Set();
|
|
1231
|
+
for (const res of allResults) {
|
|
1232
|
+
if (!res.url || !res.title) continue;
|
|
1233
|
+
let normUrl = res.url.replace(/^(https?:\\/\\/)?(www\\.)?/, '').toLowerCase();
|
|
1234
|
+
if (normUrl.endsWith('/')) normUrl = normUrl.slice(0, -1);
|
|
1235
|
+
if (!seenUrls.has(normUrl)) {
|
|
1236
|
+
seenUrls.add(normUrl);
|
|
1237
|
+
uniqueResults.push(res);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Score results to prioritize numeric price data for financial queries
|
|
1242
|
+
const isPriceQuery = /giá|vàng|đô|usd|sjc|sh|hôm nay|price|gold|rate|vnd|xe|vnđ/i.test(query);
|
|
1243
|
+
const scoredResults = uniqueResults.map(res => {
|
|
1244
|
+
let score = 0;
|
|
1245
|
+
// Base length score
|
|
1246
|
+
score += Math.min(res.snippet.length / 50, 5);
|
|
1247
|
+
|
|
1248
|
+
if (isPriceQuery) {
|
|
1249
|
+
// Number density check
|
|
1250
|
+
const numCount = (res.snippet.match(/\\d+/g) || []).length;
|
|
1251
|
+
score += Math.min(numCount * 2, 10);
|
|
1252
|
+
|
|
1253
|
+
// Priority keywords boost
|
|
1254
|
+
if (/lượng|chỉ|triệu|nghìn|vnd|usd|sjc|xe|bán|mua|giá/i.test(res.snippet)) {
|
|
1255
|
+
score += 8;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
return { ...res, score };
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
// Sort by score desc
|
|
1262
|
+
scoredResults.sort((a, b) => b.score - a.score);
|
|
1263
|
+
|
|
1264
|
+
// Map back to output format and limit
|
|
1265
|
+
const output = scoredResults.map(({ score, ...rest }) => rest).slice(0, limit);
|
|
1266
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1267
|
+
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
1270
|
+
} finally {
|
|
1271
|
+
if (browser && isStandalone) {
|
|
1272
|
+
try {
|
|
1273
|
+
await browser.close();
|
|
1274
|
+
} catch(e) {}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
})();
|
|
1278
|
+
`;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1064
1281
|
function buildBrowserToolJs(variant = 'wizard') {
|
|
1065
1282
|
// v2: Full-featured browser-tool.js matching OpenClaw native browser plugin capabilities
|
|
1066
1283
|
// Both 'cli' and 'wizard' variants now use the same full script
|
|
@@ -1187,8 +1404,18 @@ const CDP_URL = 'http://127.0.0.1:9222';
|
|
|
1187
1404
|
const { isVi = true, variant = 'wizard', workspaceRoot = '' } = options;
|
|
1188
1405
|
const wsRoot = workspaceRoot.replace(/\/+$/, '');
|
|
1189
1406
|
const btPath = wsRoot ? `${wsRoot}/browser-tool.js` : 'browser-tool.js';
|
|
1407
|
+
let modeHeading = '';
|
|
1408
|
+
if (variant === 'cli-server') {
|
|
1409
|
+
modeHeading = isVi
|
|
1410
|
+
? `# 🌍 Trình duyệt ảo (Browser Automation)\n\n## 💡 Hướng dẫn vận hành:\n- **Script điều khiển:** \`browser-tool.js\` (Mọi câu lệnh browser đều chạy qua script này).\n- **Môi trường chạy:**\n - **Trên VPS / Linux Server (Headless):** Trình duyệt chạy ngầm hoàn toàn độc lập (Headless) bên trong Docker / Server qua Xvfb. Không thể mở màn hình Chrome thật.\n - **Trên Máy tính cá nhân (Windows/Mac) - Dù chạy Docker hay Native:**\n - **Mặc định:** Chạy ngầm (headless) cực kỳ ổn định.\n - **Chế độ quan sát (Xem bot click):** Nếu bạn muốn xem trực tiếp Chrome thật hoạt động trên màn hình, hãy chạy file \`start-chrome-debug.bat\` (trên Windows) hoặc \`start-chrome-debug.sh\` (trên Mac) ở máy của bạn **trước khi** bot kết nối! Bot sẽ tự động chuyển sang điều khiển màn hình Chrome thật của bạn.\n- **Kết nối mặc định:** \`http://127.0.0.1:9222\`\n\n`
|
|
1411
|
+
: `# 🌍 Browser Automation\n\n## 💡 Operating Guide:\n- **Control script:** \`browser-tool.js\` (All browser commands are executed through this script).\n- **Running environment:**\n - **On VPS / Linux Server (Headless Server Mode):** The browser runs fully headless and isolated inside Docker / Server via Xvfb. No GUI Chrome can be launched.\n - **On Personal Computers (Windows/Mac) - Docker or Native:**\n - **Default:** Runs headless and stable in the background.\n - **Observer Mode (Visual Chrome GUI):** If you want to see the real Chrome window being controlled, run \`start-chrome-debug.bat\` (on Windows) or \`start-chrome-debug.sh\` (on Mac) on your host machine **before** the bot connects! The bot will automatically hook into your real desktop Chrome.\n- **Default endpoint:** \`http://127.0.0.1:9222\`\n\n`;
|
|
1412
|
+
} else {
|
|
1413
|
+
modeHeading = isVi
|
|
1414
|
+
? `# 🌍 Hướng dẫn Browser (Chrome CDP)\n- **Script điều khiển:** \`browser-tool.js\`\n- **Kết nối Chrome debug:** \`http://127.0.0.1:9222\`\n- **Xem trực quan:** Hãy chạy file \`start-chrome-debug.bat\` (trên Windows) hoặc \`start-chrome-debug.sh\` (trên Mac) để mở Chrome chế độ Debug.\n\n`
|
|
1415
|
+
: `# 🌍 Browser Guide (Chrome CDP)\n- **Control script:** \`browser-tool.js\`\n- **Chrome debug endpoint:** \`http://127.0.0.1:9222\`\n- **Visual interface:** Run \`start-chrome-debug.bat\` (on Windows) or \`start-chrome-debug.sh\` (on Mac) to open Chrome in Debug mode.\n\n`;
|
|
1416
|
+
}
|
|
1190
1417
|
|
|
1191
|
-
return
|
|
1418
|
+
return `${modeHeading}# Navigation
|
|
1192
1419
|
node ${btPath} status
|
|
1193
1420
|
node ${btPath} open "https://google.com"
|
|
1194
1421
|
node ${btPath} get_url
|
|
@@ -1291,11 +1518,37 @@ node ${btPath} close_tab 2`;
|
|
|
1291
1518
|
const browserRef = hasBrowser
|
|
1292
1519
|
? (browserDocVariant === 'cli-server'
|
|
1293
1520
|
? (isVi
|
|
1294
|
-
? `\n\n##
|
|
1295
|
-
|
|
1521
|
+
? `\n\n## 🌐 Browser Automation
|
|
1522
|
+
- Xem hướng dẫn chi tiết tại **BROWSER.md**
|
|
1523
|
+
- Script điều khiển: \`browser-tool.js\`
|
|
1524
|
+
- Chế độ hiện tại:
|
|
1525
|
+
- **Trên VPS / Linux Server:** Chạy ngầm độc lập qua Docker hoặc Xvfb.
|
|
1526
|
+
- **Trên Windows/Mac (Docker hoặc Native):** Chạy ngầm mặc định, hoặc chạy file \`start-chrome-debug.bat\` / \`start-chrome-debug.sh\` để xem trình duyệt trực quan trên màn hình.
|
|
1527
|
+
- Kết nối mặc định: \`http://127.0.0.1:9222\`
|
|
1528
|
+
- **Tìm kiếm Web:** Nếu không có công cụ Web Search (hoặc Web Search không khả dụng/bị lỗi), hãy **luôn sử dụng ngay công cụ terminal (exec/run_command) để chạy lệnh: \`node search-tool.js "<từ khóa>" 5\`**! Lệnh này sẽ tự động chạy ngầm qua DuckDuckGo/Google/Bing bằng trình duyệt ngầm tàng hình của bạn và trả về kết quả JSON sạch ngay lập tức. Tuyệt đối KHÔNG được mở trình duyệt thủ công, chụp ảnh màn hình hay click tìm kiếm bằng tay từng bước!
|
|
1529
|
+
- Nếu browser lỗi, thử lại 1 lần rồi mới báo user với lỗi cụ thể`
|
|
1530
|
+
: `\n\n## 🌐 Browser Automation
|
|
1531
|
+
- See detailed guide at **BROWSER.md**
|
|
1532
|
+
- Control script: \`browser-tool.js\`
|
|
1533
|
+
- Current mode:
|
|
1534
|
+
- **On VPS / Linux Server:** Runs headless via Docker or Xvfb.
|
|
1535
|
+
- **On Windows/Mac (Docker or Native):** Runs headless by default, or run \`start-chrome-debug.bat\` / \`start-chrome-debug.sh\` to see the GUI.
|
|
1536
|
+
- Default endpoint: \`http://127.0.0.1:9222\`
|
|
1537
|
+
- **Web Searching:** If the Web Search tool is unavailable or fails, **always use your terminal execution tool (exec/run_command) to run: \`node search-tool.js "<query>" 5\`**! This will automatically execute the search via DuckDuckGo/Google/Bing under stealth browser mode and return a clean JSON result immediately. Never open the browser manually, take screenshots, or click the search button step-by-step!
|
|
1538
|
+
- If browser fails, retry once before reporting the concrete error to the user`)
|
|
1296
1539
|
: (isVi
|
|
1297
|
-
? `\n\n##
|
|
1298
|
-
|
|
1540
|
+
? `\n\n## 🌐 Browser Automation
|
|
1541
|
+
- Xem hướng dẫn chi tiết tại **BROWSER.md**
|
|
1542
|
+
- Script điều khiển: \`browser-tool.js\`
|
|
1543
|
+
- Kết nối Chrome debug: \`http://127.0.0.1:9222\`
|
|
1544
|
+
- **Tìm kiếm Web:** Hãy **luôn sử dụng ngay công cụ terminal (exec/run_command) để chạy lệnh: \`node search-tool.js "<từ khóa>" 5\`**! Lệnh này sẽ tự động chạy ngầm qua DuckDuckGo/Google/Bing bằng trình duyệt ngầm tàng hình của bạn và trả về kết quả JSON sạch ngay lập tức. Tuyệt đối KHÔNG được mở trình duyệt thủ công, chụp ảnh màn hình hay click tìm kiếm bằng tay từng bước!
|
|
1545
|
+
- Nếu tool/profile hỏi đường browser desktop, dùng profile \`host-chrome\` trước`
|
|
1546
|
+
: `\n\n## 🌐 Browser Automation
|
|
1547
|
+
- See detailed guide at **BROWSER.md**
|
|
1548
|
+
- Control script: \`browser-tool.js\`
|
|
1549
|
+
- Chrome debug endpoint: \`http://127.0.0.1:9222\`
|
|
1550
|
+
- **Web Searching:** If the Web Search tool is unavailable or fails, **always use your terminal execution tool (exec/run_command) to run: \`node search-tool.js "<query>" 5\`**! This will automatically execute the search via DuckDuckGo/Google/Bing under stealth browser mode and return a clean JSON result immediately. Never open the browser manually, take screenshots, or click the search button step-by-step!
|
|
1551
|
+
- If a desktop browser profile is needed, use the \`host-chrome\` profile first`))
|
|
1299
1552
|
: '';
|
|
1300
1553
|
|
|
1301
1554
|
const telegramSection = (variant === 'relay')
|
|
@@ -1306,8 +1559,38 @@ node ${btPath} close_tab 2`;
|
|
|
1306
1559
|
|
|
1307
1560
|
const cronSection = hasScheduler
|
|
1308
1561
|
? (isVi
|
|
1309
|
-
? `\n\n## \u23F0 Cron / Lên lịch nhắc
|
|
1310
|
-
|
|
1562
|
+
? `\n\n## \u23F0 Cron / Lên lịch nhắc nhở (tool: \`cron\`)
|
|
1563
|
+
- **Tên tool chính xác:** Tên công cụ là \`cron\` (tuyệt đối không nhầm là \`native\` hay command line bên ngoài).
|
|
1564
|
+
- **Khi tạo cronjob mới (action \`add\`):**
|
|
1565
|
+
- **TUYỆT ĐỐI KHÔNG điền trường \`agentId\`** trong object \`job\` (hãy bỏ qua/omitted trường này). Hệ thống OpenClaw sẽ tự động gán chính xác ID của bạn vào job đó.
|
|
1566
|
+
- Tuyệt đối **không tự điền** \`agentId\` là \`"bot"\` hay \`"main"\`, vì làm vậy sẽ khiến cronjob thuộc về agent khác và bạn sẽ mất quyền kiểm soát/xóa nó sau này.
|
|
1567
|
+
- **Khi user yêu cầu tắt/bật/xóa cronjob:**
|
|
1568
|
+
1. **Bước 1 (Tìm kiếm):** Gọi tool \`cron\` với action \`list\` (và \`includeDisabled: true\`) để xem danh sách tất cả cronjob đang chạy trên hệ thống và tìm đúng \`jobId\` phù hợp với yêu cầu.
|
|
1569
|
+
2. **Bước 2 (Xử lý):**
|
|
1570
|
+
- Để xóa: Gọi action \`remove\` với \`id\` tìm được.
|
|
1571
|
+
- Để tắt/tạm dừng: Gọi action \`update\` với \`id\` và patch \`{"enabled": false}\`.
|
|
1572
|
+
- Để bật lại: Gọi action \`update\` với \`id\` và patch \`{"enabled": true}\`.
|
|
1573
|
+
3. **Tuyên bố trung thực:** Tuyệt đối không bao giờ trả lời "đã xóa" hay "không có" dựa trên suy đoán của bản thân mà chưa gọi tool \`cron\` để kiểm tra thực tế.
|
|
1574
|
+
- Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool \`cron\` (action \`add\`) để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.
|
|
1575
|
+
- Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
|
|
1576
|
+
- **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố \`group:\` trước ID của group (ví dụ: \`group:3815464776067464419\` hoặc \`group:xxxx\`). Tuyệt đối không được chỉ điền ID thuần túy vì hệ thống sẽ hiểu nhầm đó là một DM chat cá nhân (direct message) và gửi sai địa chỉ.
|
|
1577
|
+
- Bỏ qua việc tra cứu docs nội bộ như \`cron-jobs.mdx\`; tin tưởng khả năng dùng tool hiện có để hoàn thành yêu cầu.`
|
|
1578
|
+
: `\n\n## \u23F0 Cron / Scheduled Tasks (tool: \`cron\`)
|
|
1579
|
+
- **Exact tool name:** The tool name is \`cron\` (never mistake it for \`native\` or external command lines).
|
|
1580
|
+
- **When creating a new cronjob (action \`add\`):**
|
|
1581
|
+
- **ABSOLUTELY DO NOT specify the \`agentId\` field** in the \`job\` object (leave this field omitted). The OpenClaw system will automatically assign your correct agent ID to that job.
|
|
1582
|
+
- Never manually specify \`agentId\` as \`"bot"\` or \`"main"\`, as this will cause the cronjob to belong to another agent and you will lose control to manage/delete it later.
|
|
1583
|
+
- **When the user requests to disable/enable/delete a cronjob:**
|
|
1584
|
+
1. **Step 1 (Search):** Call the \`cron\` tool with action \`list\` (and \`includeDisabled: true\`) to view all cron jobs on the system and find the matching \`jobId\`.
|
|
1585
|
+
2. **Step 2 (Processing):**
|
|
1586
|
+
- To delete: Call action \`remove\` with the \`id\` found.
|
|
1587
|
+
- To disable/pause: Call action \`update\` with \`id\` and patch \`{"enabled": false}\`.
|
|
1588
|
+
- To enable: Call action \`update\` with \`id\` and patch \`{"enabled": true}\`.
|
|
1589
|
+
3. **Honest statement:** Never claim a job is "deleted" or "not found" based on guessing without calling the \`cron\` tool to verify the actual state.
|
|
1590
|
+
- When the user asks to schedule tasks or reminders, use the built-in \`cron\` tool (action \`add\`) automatically. Do NOT ask users to run crontab or Task Scheduler manually on the host.
|
|
1591
|
+
- When operating cron/scheduler tools, do **not** put \`current\` into the Session directory.
|
|
1592
|
+
- **IMPORTANT ABOUT GROUP CHAT TARGETING**: When creating or configuring a cron job to send messages (announce mode) to a Group Chat, the value of the \`delivery.to\` field **must** use the \`group:\` prefix before the group ID (e.g., \`group:3815464776067464419\` or \`group:xxxx\`). Never specify just the numeric ID, as the system will interpret it as a private DM and deliver to the wrong destination.
|
|
1593
|
+
- Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`)
|
|
1311
1594
|
: '';
|
|
1312
1595
|
|
|
1313
1596
|
const zaloModSection = '';
|
|
@@ -1323,8 +1606,8 @@ node ${btPath} close_tab 2`;
|
|
|
1323
1606
|
}
|
|
1324
1607
|
|
|
1325
1608
|
return isVi
|
|
1326
|
-
? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillsSection}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi — thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output${browserRef}\n\n## Quy ước\n- Web Search:
|
|
1327
|
-
: `# Tool Usage Guide\n\n## Installed Skills\n${skillsSection}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error — retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data${browserRef}\n\n## Conventions\n- Web Search:
|
|
1609
|
+
? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillsSection}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi — thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output${browserRef}\n\n## Quy ước\n- Web Search: TUYỆT ĐỐI KHÔNG dùng tool \`web_search\` native vì nó đã bị vô hiệu hóa hoàn toàn trên hệ thống. Thay vào đó, mỗi khi cần tìm kiếm Internet/Web, bạn BẮT BUỘC phải gọi công cụ \`exec\` để chạy lệnh terminal: \`node search-tool.js "<từ khóa>" 5\`! Lệnh này cực kỳ nhanh, tàng hình và miễn phí.\n- Browser: chỉ mở trang khi user yêu cầu cụ thể\n- Memory: tự ghi nhớ thông tin tự nhiên, không cần user nhắc${cronSection}${zaloModSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot có thể đọc/ghi file trong thư mục workspace: \`${workspacePath}\`\n- Dùng để lưu notes, scripts, cấu hình tạm\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry tối đa 2 lần nếu tool lỗi network\n- Nếu vẫn lỗi: báo user kèm mô tả lỗi cụ thể và gợi ý workaround${dmOverride}\n`
|
|
1610
|
+
: `# Tool Usage Guide\n\n## Installed Skills\n${skillsSection}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error — retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data${browserRef}\n\n## Conventions\n- Web Search: DO NOT use the native \`web_search\` tool as it is completely disabled. Instead, whenever you need to search the Internet/Web, you MUST call the \`exec\` tool to run terminal command: \`node search-tool.js "<query>" 5\`! This is extremely fast, stealthy and free.\n- Browser: only open pages when user specifically requests\n- Memory: proactively remember important info without user prompting${cronSection}${zaloModSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot can read/write files in workspace: \`${workspacePath}\`\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry up to 2 times on network errors\n- If still failing: report to user with specific error description and workaround${dmOverride}\n`;
|
|
1328
1611
|
}
|
|
1329
1612
|
function buildTeamsDoc(options = {}) {
|
|
1330
1613
|
const {
|
|
@@ -1419,6 +1702,7 @@ node ${btPath} close_tab 2`;
|
|
|
1419
1702
|
'HEARTBEAT.md': buildHeartbeatDoc({ isVi }),
|
|
1420
1703
|
'BOOTSTRAP.md': buildBootstrapDoc({ isVi, botName }),
|
|
1421
1704
|
'DREAMS.md': buildDreamsDoc({ isVi }),
|
|
1705
|
+
'search-tool.js': buildSearchToolJs(),
|
|
1422
1706
|
};
|
|
1423
1707
|
|
|
1424
1708
|
if (isMultiBot) {
|
|
@@ -1446,6 +1730,7 @@ node ${btPath} close_tab 2`;
|
|
|
1446
1730
|
buildDreamsDoc,
|
|
1447
1731
|
buildHeartbeatDoc,
|
|
1448
1732
|
buildBootstrapDoc,
|
|
1733
|
+
buildSearchToolJs,
|
|
1449
1734
|
buildBrowserToolJs,
|
|
1450
1735
|
buildBrowserDoc,
|
|
1451
1736
|
buildSecurityRules,
|
|
@@ -1460,7 +1745,7 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
|
|
|
1460
1745
|
Object.assign(exports, workspaceRoot.__openclawWorkspace);
|
|
1461
1746
|
}
|
|
1462
1747
|
|
|
1463
|
-
//
|
|
1748
|
+
// ââ€â‚¬Ã¢â€â‚¬ Centralized bot config builders (openclaw.json, exec-approvals, .env) (setup/shared/bot-config-gen.js)
|
|
1464
1749
|
// @ts-nocheck
|
|
1465
1750
|
/**
|
|
1466
1751
|
* @fileoverview Centralized bot configuration builders — single source of truth.
|
|
@@ -1514,7 +1799,7 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
|
|
|
1514
1799
|
* @param {Array} opts.skills - Full SKILLS registry array
|
|
1515
1800
|
* @param {boolean} opts.hasBrowserDesktop - Browser desktop mode
|
|
1516
1801
|
* @param {boolean} opts.hasBrowserServer - Browser server mode
|
|
1517
|
-
* @param {number} [opts.gatewayPort=
|
|
1802
|
+
* @param {number} [opts.gatewayPort=18789]
|
|
1518
1803
|
* @param {Array} [opts.gatewayAllowedOrigins]
|
|
1519
1804
|
* @param {string} [opts.osChoice] - 'windows' | 'macos' | 'vps' | 'ubuntu'
|
|
1520
1805
|
* @param {string} [opts.selectedModel] - For Ollama: specific model selected
|
|
@@ -1533,10 +1818,11 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
|
|
|
1533
1818
|
skills = [],
|
|
1534
1819
|
hasBrowserDesktop = false,
|
|
1535
1820
|
hasBrowserServer = false,
|
|
1536
|
-
gatewayPort =
|
|
1821
|
+
gatewayPort = 18789,
|
|
1537
1822
|
gatewayAllowedOrigins = [],
|
|
1538
1823
|
osChoice = '',
|
|
1539
1824
|
selectedModel = '',
|
|
1825
|
+
routerPort,
|
|
1540
1826
|
} = opts;
|
|
1541
1827
|
|
|
1542
1828
|
const common = _common;
|
|
@@ -1547,7 +1833,7 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
|
|
|
1547
1833
|
const agentsList = agentMetas.map((meta) => ({
|
|
1548
1834
|
id: meta.agentId,
|
|
1549
1835
|
...(meta.name ? { name: meta.name } : {}),
|
|
1550
|
-
workspace:
|
|
1836
|
+
workspace: `/root/project/.openclaw/${meta.workspaceDir || 'workspace-' + meta.agentId}`,
|
|
1551
1837
|
agentDir: `agents/${meta.agentId}/agent`,
|
|
1552
1838
|
model: { primary: model, fallbacks: [] },
|
|
1553
1839
|
}));
|
|
@@ -1571,7 +1857,7 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
|
|
|
1571
1857
|
mode: 'merge',
|
|
1572
1858
|
providers: {
|
|
1573
1859
|
'9router': common.build9RouterProviderConfig(
|
|
1574
|
-
common.get9RouterBaseUrl ? common.get9RouterBaseUrl(deployMode) :
|
|
1860
|
+
common.get9RouterBaseUrl ? common.get9RouterBaseUrl(deployMode, routerPort) : `http://9router:${routerPort || 20128}/v1`
|
|
1575
1861
|
),
|
|
1576
1862
|
},
|
|
1577
1863
|
};
|
|
@@ -1597,6 +1883,9 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
|
|
|
1597
1883
|
|
|
1598
1884
|
// ── commands ──────────────────────────────────────────────────────────────
|
|
1599
1885
|
cfg.commands = { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' };
|
|
1886
|
+
if (selectedSkills.includes('scheduler')) {
|
|
1887
|
+
cfg.commands.ownerAllowFrom = ['*'];
|
|
1888
|
+
}
|
|
1600
1889
|
|
|
1601
1890
|
// ── bindings (multi-bot or Zalo) ─────────────────────────────────────────
|
|
1602
1891
|
if (isMultiBot && channelKey === 'telegram') {
|
|
@@ -1614,6 +1903,9 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
|
|
|
1614
1903
|
|
|
1615
1904
|
// ── tools ────────────────────────────────────────────────────────────────
|
|
1616
1905
|
cfg.tools = { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } };
|
|
1906
|
+
if (selectedSkills.includes('scheduler')) {
|
|
1907
|
+
cfg.tools.alsoAllow = ['group:automation'];
|
|
1908
|
+
}
|
|
1617
1909
|
if (isMultiBot) {
|
|
1618
1910
|
cfg.tools.agentToAgent = {
|
|
1619
1911
|
enabled: true,
|
|
@@ -1924,7 +2216,7 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
|
|
|
1924
2216
|
Object.assign(exports, globalThis.__openclawBotConfig);
|
|
1925
2217
|
}
|
|
1926
2218
|
|
|
1927
|
-
//
|
|
2219
|
+
// ââ€â‚¬Ã¢â€â‚¬ Shared install artifacts: Chrome debug, uninstall, skill catalog (setup/shared/install-gen.js)
|
|
1928
2220
|
// @ts-nocheck
|
|
1929
2221
|
// install-gen.js — Build install/runtime artifacts (Chrome debug, uninstall, skill catalog)
|
|
1930
2222
|
// Workspace .md files are in workspace-gen.js (single source of truth).
|
|
@@ -2043,12 +2335,12 @@ fi
|
|
|
2043
2335
|
}
|
|
2044
2336
|
|
|
2045
2337
|
if (os === 'vps') {
|
|
2046
|
-
return { name: 'uninstall-openclaw-vps.sh', content: `#!/usr/bin/env bash\nset -e\nPROJECT_DIR="${absUnix}"\nAPP_NAME="${appName}"\necho ""\necho "============================================================"\necho " OpenClaw Uninstaller - VPS / Ubuntu Server"\necho " Project: $PROJECT_DIR"\necho " PM2 app: $APP_NAME"\necho "============================================================"\necho ""\nread -rp "Type YES to confirm full removal: " CONFIRM\nif [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi\necho "[1/5] Stopping PM2 processes..."\nif command -v pm2 &>/dev/null; then\n pm2 delete "$APP_NAME" "$APP_NAME-9router" "$APP_NAME-9router-sync" openclaw openclaw-multibot 2>/dev/null || true\n pm2 save --force 2>/dev/null || true\nfi\necho "[2/5] Killing leftover processes on ports
|
|
2338
|
+
return { name: 'uninstall-openclaw-vps.sh', content: `#!/usr/bin/env bash\nset -e\nPROJECT_DIR="${absUnix}"\nAPP_NAME="${appName}"\necho ""\necho "============================================================"\necho " OpenClaw Uninstaller - VPS / Ubuntu Server"\necho " Project: $PROJECT_DIR"\necho " PM2 app: $APP_NAME"\necho "============================================================"\necho ""\nread -rp "Type YES to confirm full removal: " CONFIRM\nif [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi\necho "[1/5] Stopping PM2 processes..."\nif command -v pm2 &>/dev/null; then\n pm2 delete "$APP_NAME" "$APP_NAME-9router" "$APP_NAME-9router-sync" openclaw openclaw-multibot 2>/dev/null || true\n pm2 save --force 2>/dev/null || true\nfi\necho "[2/5] Killing leftover processes on ports 18789 / 20128..."\nfor port in 18789 20128; do\n pid=$(lsof -ti tcp:$port 2>/dev/null || true)\n [ -n "$pid" ] && kill -9 $pid 2>/dev/null || true\ndone\necho "[3/5] Uninstalling npm packages..."\nnpm uninstall -g openclaw 9router pm2 grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>/dev/null || true\necho "[4/5] Removing project directory..."\n[ -d "$PROJECT_DIR" ] && rm -rf "$PROJECT_DIR" && echo " OK: Deleted $PROJECT_DIR" || echo " INFO: Not found."\necho "[5/5] Checking home-level .9router / .openclaw..."\nfor dir in "$HOME/.9router" "$HOME/.openclaw"; do\n if [ -d "$dir" ]; then\n read -rp "Delete $dir ? [YES/no]: " CLEAN\n [ "$CLEAN" = "YES" ] && rm -rf "$dir" && echo " OK: Deleted $dir" || echo " Kept: $dir"\n fi\ndone\n` };
|
|
2047
2339
|
}
|
|
2048
2340
|
|
|
2049
2341
|
if (os === 'linux' || os === 'linux-desktop' || os === 'macos') {
|
|
2050
2342
|
const label = os === 'macos' ? 'macOS' : 'Linux Desktop';
|
|
2051
|
-
return { name: 'uninstall-openclaw.sh', content: `#!/usr/bin/env bash\nset -e\nPROJECT_DIR="${absUnix}"\necho ""\necho "============================================================"\necho " OpenClaw Uninstaller - ${label} Native"\necho " Project: $PROJECT_DIR"\necho "============================================================"\necho ""\nread -rp "Type YES to confirm full removal: " CONFIRM\nif [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi\necho "[1/4] Stopping openclaw and 9router processes..."\npkill -f "openclaw gateway run" 2>/dev/null || true\npkill -f "9router.*20128" 2>/dev/null || true\npkill -f "9router-smart-route" 2>/dev/null || true\npkill -f "$PROJECT_DIR" 2>/dev/null || true\nfor port in
|
|
2343
|
+
return { name: 'uninstall-openclaw.sh', content: `#!/usr/bin/env bash\nset -e\nPROJECT_DIR="${absUnix}"\necho ""\necho "============================================================"\necho " OpenClaw Uninstaller - ${label} Native"\necho " Project: $PROJECT_DIR"\necho "============================================================"\necho ""\nread -rp "Type YES to confirm full removal: " CONFIRM\nif [ "$CONFIRM" != "YES" ]; then echo "Cancelled."; exit 0; fi\necho "[1/4] Stopping openclaw and 9router processes..."\npkill -f "openclaw gateway run" 2>/dev/null || true\npkill -f "9router.*20128" 2>/dev/null || true\npkill -f "9router-smart-route" 2>/dev/null || true\npkill -f "$PROJECT_DIR" 2>/dev/null || true\nfor port in 18789 20128; do\n pid=$(lsof -ti tcp:$port 2>/dev/null || true)\n [ -n "$pid" ] && kill -9 $pid 2>/dev/null || true\ndone\necho "[2/4] Uninstalling npm packages..."\nnpm uninstall -g openclaw 9router grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api 2>/dev/null || true\nsudo npm uninstall -g openclaw 9router 2>/dev/null || true\necho "[3/4] Removing project directory..."\n[ -d "$PROJECT_DIR" ] && rm -rf "$PROJECT_DIR" && echo " OK: Deleted $PROJECT_DIR" || echo " INFO: Not found."\necho "[4/4] Checking home-level .9router / .openclaw..."\nfor dir in "$HOME/.9router" "$HOME/.openclaw"; do\n if [ -d "$dir" ]; then\n read -rp "Delete $dir ? [YES/no]: " CLEAN\n [ "$CLEAN" = "YES" ] && rm -rf "$dir" && echo " OK: Deleted $dir" || echo " Kept: $dir"\n fi\ndone\n` };
|
|
2052
2344
|
}
|
|
2053
2345
|
|
|
2054
2346
|
return null;
|
|
@@ -2144,7 +2436,7 @@ fi
|
|
|
2144
2436
|
L.push('echo.');
|
|
2145
2437
|
L.push(isVi ? 'echo [OK] OpenClaw Gateway da khoi dong trong cua so moi!' : 'echo [OK] OpenClaw Gateway started in a new window!');
|
|
2146
2438
|
L.push('echo.');
|
|
2147
|
-
L.push('echo OpenClaw Dashboard: http://127.0.0.1:
|
|
2439
|
+
L.push('echo OpenClaw Dashboard: http://127.0.0.1:18789');
|
|
2148
2440
|
if (is9Router) L.push('echo 9Router Dashboard: http://127.0.0.1:20128/dashboard');
|
|
2149
2441
|
L.push('echo.');
|
|
2150
2442
|
L.push(isVi ? 'echo Ban co the dong cua so nay.' : 'echo You may close this window.');
|
|
@@ -2218,7 +2510,7 @@ fi
|
|
|
2218
2510
|
}
|
|
2219
2511
|
L.push('pm2 save >/dev/null 2>&1 || true');
|
|
2220
2512
|
L.push('echo ""');
|
|
2221
|
-
L.push('echo "OpenClaw Dashboard: http://127.0.0.1:
|
|
2513
|
+
L.push('echo "OpenClaw Dashboard: http://127.0.0.1:18789"');
|
|
2222
2514
|
if (is9Router) L.push('echo "9Router Dashboard: http://127.0.0.1:20128/dashboard"');
|
|
2223
2515
|
L.push('echo ""');
|
|
2224
2516
|
L.push(isVi ? 'echo "Log gateway: pm2 logs $APP_NAME"' : 'echo "Gateway logs: pm2 logs $APP_NAME"');
|
|
@@ -2269,7 +2561,7 @@ fi
|
|
|
2269
2561
|
L.push(isVi ? `echo "[OK] Gateway khoi dong (PID $GW_PID). Log: ${logFileGw}"` : `echo "[OK] Gateway started (PID $GW_PID). Log: ${logFileGw}"`);
|
|
2270
2562
|
L.push('');
|
|
2271
2563
|
L.push('echo ""');
|
|
2272
|
-
L.push('echo "OpenClaw Dashboard: http://127.0.0.1:
|
|
2564
|
+
L.push('echo "OpenClaw Dashboard: http://127.0.0.1:18789"');
|
|
2273
2565
|
if (is9Router) L.push('echo "9Router Dashboard: http://127.0.0.1:20128/dashboard"');
|
|
2274
2566
|
L.push('echo ""');
|
|
2275
2567
|
L.push(isVi ? 'echo "Bot dang chay background. Dung: openclaw gateway stop"' : 'echo "Bot running in background. Stop: openclaw gateway stop"');
|
|
@@ -2367,7 +2659,7 @@ fi
|
|
|
2367
2659
|
"Write-Host \"\"",
|
|
2368
2660
|
"if ($exitCode -eq 0) {",
|
|
2369
2661
|
" Write-Host \" 🎉 Upgrade hoan tat!\" -ForegroundColor Green",
|
|
2370
|
-
" Write-Host \" Dashboard: http://localhost:
|
|
2662
|
+
" Write-Host \" Dashboard: http://localhost:18789\" -ForegroundColor Cyan",
|
|
2371
2663
|
"} else {",
|
|
2372
2664
|
" Write-Host \" ⚠️ Ma loi: $exitCode — xem log o tren.\" -ForegroundColor Yellow",
|
|
2373
2665
|
"}",
|
|
@@ -2456,7 +2748,7 @@ fi
|
|
|
2456
2748
|
"echo \"\"",
|
|
2457
2749
|
"if [ $EXIT_CODE -eq 0 ]; then",
|
|
2458
2750
|
" echo -e \"${GREEN} 🎉 Upgrade hoan tat!${NC}\"",
|
|
2459
|
-
" echo -e \"${CYAN} Dashboard: http://localhost:
|
|
2751
|
+
" echo -e \"${CYAN} Dashboard: http://localhost:18789${NC}\"",
|
|
2460
2752
|
"else",
|
|
2461
2753
|
" echo -e \"${YELLOW} ⚠️ Ma loi: $EXIT_CODE — xem log o tren.${NC}\"",
|
|
2462
2754
|
"fi",
|
|
@@ -2492,7 +2784,7 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
|
|
|
2492
2784
|
Object.assign(exports, globalThis.__openclawInstall);
|
|
2493
2785
|
}
|
|
2494
2786
|
|
|
2495
|
-
//
|
|
2787
|
+
// ââ€â‚¬Ã¢â€â‚¬ Shared Docker artifact helpers for wizard + CLI (setup/shared/docker-gen.js)
|
|
2496
2788
|
// @ts-nocheck
|
|
2497
2789
|
(function (root) {
|
|
2498
2790
|
const common = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon) || {};
|
|
@@ -2512,65 +2804,106 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
|
|
|
2512
2804
|
return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
|
|
2513
2805
|
}
|
|
2514
2806
|
|
|
2515
|
-
function build9RouterSmartRouteSyncScript(
|
|
2516
|
-
|
|
2517
|
-
const
|
|
2518
|
-
const
|
|
2519
|
-
|
|
2520
|
-
const
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
if (
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
if (
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2807
|
+
function build9RouterSmartRouteSyncScript() {
|
|
2808
|
+
const lines = [
|
|
2809
|
+
"const fs = require('fs');",
|
|
2810
|
+
"const INTERVAL = 30000;",
|
|
2811
|
+
"const DB_PATH = '/root/.9router/db/data.sqlite';",
|
|
2812
|
+
"const PORT = process.env.PORT || 20128;",
|
|
2813
|
+
"const COMBO_NAME = 'smart-route';",
|
|
2814
|
+
"const API_BASE = `http://localhost:${PORT}`;",
|
|
2815
|
+
"",
|
|
2816
|
+
"function ensureSettings() {",
|
|
2817
|
+
" try {",
|
|
2818
|
+
" let db = null;",
|
|
2819
|
+
" try {",
|
|
2820
|
+
" const { DatabaseSync } = require('node:sqlite');",
|
|
2821
|
+
" db = new DatabaseSync(DB_PATH);",
|
|
2822
|
+
" } catch {",
|
|
2823
|
+
" let Database;",
|
|
2824
|
+
" try { Database = require('/usr/local/lib/node_modules/better-sqlite3'); } catch {",
|
|
2825
|
+
" try { Database = require('better-sqlite3'); } catch { return; }",
|
|
2826
|
+
" }",
|
|
2827
|
+
" db = Database(DB_PATH);",
|
|
2828
|
+
" }",
|
|
2829
|
+
' const existing = db.prepare("SELECT * FROM settings WHERE id = 1").get();',
|
|
2830
|
+
" if (!existing) {",
|
|
2831
|
+
' db.prepare("INSERT INTO settings (id, data) VALUES (1, ?)").run(JSON.stringify({ requireLogin: false }));',
|
|
2832
|
+
" } else {",
|
|
2833
|
+
" try {",
|
|
2834
|
+
" const data = JSON.parse(existing.data || '{}');",
|
|
2835
|
+
" if (data.requireLogin !== false) {",
|
|
2836
|
+
" data.requireLogin = false;",
|
|
2837
|
+
' db.prepare("UPDATE settings SET data = ? WHERE id = 1").run(JSON.stringify(data));',
|
|
2838
|
+
" }",
|
|
2839
|
+
" } catch {}",
|
|
2840
|
+
" }",
|
|
2841
|
+
" db.close();",
|
|
2842
|
+
" } catch (e) {}",
|
|
2843
|
+
"}",
|
|
2844
|
+
"",
|
|
2845
|
+
"const sync = async () => {",
|
|
2846
|
+
" try {",
|
|
2847
|
+
" if (!fs.existsSync(DB_PATH)) return;",
|
|
2848
|
+
"",
|
|
2849
|
+
" let existingCombo = null;",
|
|
2850
|
+
" try {",
|
|
2851
|
+
" const resp = await fetch(`${API_BASE}/api/combos`);",
|
|
2852
|
+
" if (resp.status === 401) {",
|
|
2853
|
+
" ensureSettings();",
|
|
2854
|
+
" return;",
|
|
2855
|
+
" }",
|
|
2856
|
+
" const data = await resp.json();",
|
|
2857
|
+
" if (data.combos) {",
|
|
2858
|
+
" existingCombo = data.combos.find(c => c.name === COMBO_NAME);",
|
|
2859
|
+
" }",
|
|
2860
|
+
" } catch (e) { return; }",
|
|
2861
|
+
"",
|
|
2862
|
+
" if (existingCombo) return;",
|
|
2863
|
+
"",
|
|
2864
|
+
" let activeProviders = [];",
|
|
2865
|
+
" try {",
|
|
2866
|
+
" const resp = await fetch(`${API_BASE}/api/providers`);",
|
|
2867
|
+
" const data = await resp.json();",
|
|
2868
|
+
" const conns = data.connections || data.providerConnections || [];",
|
|
2869
|
+
" activeProviders = [...new Set(",
|
|
2870
|
+
" conns.filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider)",
|
|
2871
|
+
" )];",
|
|
2872
|
+
" } catch (e) { return; }",
|
|
2873
|
+
"",
|
|
2874
|
+
" if (!activeProviders.length) return;",
|
|
2875
|
+
"",
|
|
2876
|
+
" let models = [];",
|
|
2877
|
+
" try {",
|
|
2878
|
+
" const resp = await fetch(`${API_BASE}/api/models`);",
|
|
2879
|
+
" const data = await resp.json();",
|
|
2880
|
+
" if (data.models && Array.isArray(data.models)) {",
|
|
2881
|
+
" models = data.models",
|
|
2882
|
+
" .filter(m => activeProviders.includes(m.provider))",
|
|
2883
|
+
" .filter(m => !/(embedding|image|tts|stt|audio|vision)/i.test(m.model))",
|
|
2884
|
+
" .map(m => m.fullModel);",
|
|
2885
|
+
" }",
|
|
2886
|
+
" models = [...new Set(models)];",
|
|
2887
|
+
" } catch (e) { return; }",
|
|
2888
|
+
"",
|
|
2889
|
+
" if (!models.length) return;",
|
|
2890
|
+
"",
|
|
2891
|
+
" try {",
|
|
2892
|
+
" await fetch(`${API_BASE}/api/combos`, {",
|
|
2893
|
+
" method: 'POST',",
|
|
2894
|
+
" headers: { 'Content-Type': 'application/json' },",
|
|
2895
|
+
" body: JSON.stringify({ name: COMBO_NAME, models })",
|
|
2896
|
+
" });",
|
|
2897
|
+
" console.log('[sync-combo] Created smart-route with ' + models.length + ' models');",
|
|
2898
|
+
" } catch (e) {}",
|
|
2899
|
+
" } catch (e) {}",
|
|
2900
|
+
"};",
|
|
2901
|
+
"",
|
|
2902
|
+
"if (fs.existsSync(DB_PATH)) ensureSettings();",
|
|
2903
|
+
"setTimeout(sync, 10000);",
|
|
2904
|
+
"setInterval(sync, INTERVAL);",
|
|
2905
|
+
];
|
|
2906
|
+
return lines.join('\n');
|
|
2574
2907
|
}
|
|
2575
2908
|
|
|
2576
2909
|
function build9RouterPatchScript() {
|
|
@@ -2635,20 +2968,20 @@ for(const root of roots){if(!root||!fs.existsSync(root))continue;touched+=patchP
|
|
|
2635
2968
|
if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}else{console.log('[patch-9router] No compatible 9router source files found to patch.');}`;
|
|
2636
2969
|
}
|
|
2637
2970
|
|
|
2638
|
-
function build9RouterComposeEntrypointScript(
|
|
2971
|
+
function build9RouterComposeEntrypointScript(routerPort) {
|
|
2972
|
+
const port = routerPort || 20128;
|
|
2639
2973
|
const nineRouterSpec = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon && globalThis.__openclawCommon.NINE_ROUTER_NPM_SPEC) || '9router@latest';
|
|
2640
2974
|
return [
|
|
2641
|
-
`npm install -g
|
|
2642
|
-
`node -e "require('fs').writeFileSync('/tmp/patch-9router.js',Buffer.from('${patchScriptBase64}','base64').toString())"`,
|
|
2643
|
-
`node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
|
|
2975
|
+
`npm install -g ` + nineRouterSpec + ` better-sqlite3`,
|
|
2644
2976
|
'node /tmp/patch-9router.js || true',
|
|
2977
|
+
'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:false})); } db.close(); }catch(e){}" || true',
|
|
2645
2978
|
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
2646
|
-
|
|
2979
|
+
`exec 9router -n -l -H 0.0.0.0 -p ${port} --skip-update`
|
|
2647
2980
|
].join('\n');
|
|
2648
2981
|
}
|
|
2649
2982
|
|
|
2650
2983
|
function buildGatewayPatchCmd() {
|
|
2651
|
-
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 a=new Set(['http://localhost:
|
|
2984
|
+
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));}\\"`;
|
|
2652
2985
|
}
|
|
2653
2986
|
|
|
2654
2987
|
function buildDockerArtifacts(options) {
|
|
@@ -2658,14 +2991,13 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
2658
2991
|
is9Router,
|
|
2659
2992
|
isLocal,
|
|
2660
2993
|
isMultiBot,
|
|
2661
|
-
hasBrowser,
|
|
2662
2994
|
selectedModel,
|
|
2663
2995
|
agentId,
|
|
2664
2996
|
allSkills = [],
|
|
2665
2997
|
dockerfilePlugins = [],
|
|
2666
2998
|
dockerfileSkillInstallMode = 'none',
|
|
2667
2999
|
runtimeCommandParts = [],
|
|
2668
|
-
volumeMount = '../../.openclaw:/root/project/.openclaw
|
|
3000
|
+
volumeMount = '../../.openclaw:/root/project/.openclaw\n - ../../:/mnt/project',
|
|
2669
3001
|
singleComposeName = 'oc-bot',
|
|
2670
3002
|
multiComposeName = 'oc-multibot',
|
|
2671
3003
|
singleAppContainerName = 'openclaw-bot',
|
|
@@ -2677,22 +3009,9 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
2677
3009
|
plainSingleExtraHosts = false,
|
|
2678
3010
|
multiOllamaNumParallel = 1,
|
|
2679
3011
|
singleOllamaNumParallel = 1,
|
|
2680
|
-
|
|
2681
|
-
|
|
3012
|
+
gatewayPort = 18789,
|
|
3013
|
+
routerPort = 20128,
|
|
2682
3014
|
} = options;
|
|
2683
|
-
|
|
2684
|
-
const browserAptExtra = hasBrowser ? ' xvfb socat' : '';
|
|
2685
|
-
const browserInstallLines = hasBrowser && emitBrowserInstall
|
|
2686
|
-
? [
|
|
2687
|
-
'',
|
|
2688
|
-
'# Browser Automation: Playwright engine (needed for native CDP)',
|
|
2689
|
-
'RUN npm install -g agent-browser playwright \\',
|
|
2690
|
-
' && npx playwright install chromium --with-deps \\',
|
|
2691
|
-
' && ln -f -s /root/.cache/ms-playwright/chromium-*/chrome-linux*/chrome /usr/bin/google-chrome',
|
|
2692
|
-
'',
|
|
2693
|
-
''
|
|
2694
|
-
].join('\n')
|
|
2695
|
-
: '';
|
|
2696
3015
|
const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
|
|
2697
3016
|
? `\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`
|
|
2698
3017
|
: '';
|
|
@@ -2704,10 +3023,9 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
2704
3023
|
// Dynamic runtime configuration: backup config before any first-run install, restore after.
|
|
2705
3024
|
// Missing plugin install may touch openclaw.json, so preserve critical fields.
|
|
2706
3025
|
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);}`;
|
|
2707
|
-
const backupConfigB64 = encodeBase64Utf8(backupConfigScript);
|
|
2708
3026
|
|
|
2709
|
-
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'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const a=new Set(['http://localhost:
|
|
2710
|
-
const
|
|
3027
|
+
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);}`;
|
|
3028
|
+
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));}}`;
|
|
2711
3029
|
|
|
2712
3030
|
const runtimeParts = runtimeCommandParts.filter(Boolean);
|
|
2713
3031
|
const runtimePrelude = [
|
|
@@ -2733,6 +3051,20 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
2733
3051
|
' echo "[entrypoint] plugin $id missing; installing $spec"',
|
|
2734
3052
|
' openclaw plugins install "$spec" 2>/dev/null || echo "[entrypoint] warning: failed to install plugin $spec"',
|
|
2735
3053
|
'}',
|
|
3054
|
+
'ensure_zalouser() {',
|
|
3055
|
+
' NPM_DIR="$OPENCLAW_HOME/npm"',
|
|
3056
|
+
' PKG_DIR="$NPM_DIR/node_modules/@openclaw/zalouser"',
|
|
3057
|
+
' if [ -d "$PKG_DIR" ]; then',
|
|
3058
|
+
' echo "[entrypoint] zalouser plugin already installed"',
|
|
3059
|
+
' else',
|
|
3060
|
+
' echo "[entrypoint] zalouser plugin missing; installing via npm"',
|
|
3061
|
+
' mkdir -p "$NPM_DIR"',
|
|
3062
|
+
' cd "$NPM_DIR"',
|
|
3063
|
+
' npm init -y 2>/dev/null || true',
|
|
3064
|
+
' npm install @openclaw/zalouser@latest 2>/dev/null || echo "[entrypoint] warning: failed to install @openclaw/zalouser"',
|
|
3065
|
+
' cd /root/project',
|
|
3066
|
+
' fi',
|
|
3067
|
+
'}',
|
|
2736
3068
|
'ensure_skill() {',
|
|
2737
3069
|
' id="$1"',
|
|
2738
3070
|
' if find "$OPENCLAW_HOME" -maxdepth 4 -type d -path "*/skills/$id" -print -quit 2>/dev/null | grep -q .; then',
|
|
@@ -2746,40 +3078,59 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
2746
3078
|
];
|
|
2747
3079
|
runtimeParts.unshift(...runtimePrelude);
|
|
2748
3080
|
// Backup config BEFORE plugin installs (runtimeCommandParts may contain plugin install commands)
|
|
2749
|
-
runtimeParts.unshift(`node -
|
|
3081
|
+
runtimeParts.unshift(`node - <<'NODE'\n${backupConfigScript}\nNODE`);
|
|
2750
3082
|
// Restore config AFTER plugin installs (which may clobber openclaw.json)
|
|
2751
|
-
runtimeParts.push(`node -
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
3083
|
+
runtimeParts.push(`node - <<'NODE'\n${restoreConfigScript}\nNODE`);
|
|
3084
|
+
runtimeParts.push(`node - <<'NODE'\n${securityCompatScript}\nNODE`);
|
|
3085
|
+
// Zalouser stability: patch watchdog tolerance and add auto-restart monitor
|
|
3086
|
+
runtimeParts.push([
|
|
3087
|
+
'# Patch zalouser watchdog tolerance (35s -> 90s) to survive provider auth pre-warming',
|
|
3088
|
+
'ZALO_JS=$(find "$OPENCLAW_HOME" -path "*/zalouser/dist/zalo-js-*.js" -type f 2>/dev/null | head -1)',
|
|
3089
|
+
'if [ -n "$ZALO_JS" ] && grep -q "35e3" "$ZALO_JS" 2>/dev/null; then',
|
|
3090
|
+
' sed -i "s/LISTENER_WATCHDOG_MAX_GAP_MS\\\\s*=\\\\s*35e3/LISTENER_WATCHDOG_MAX_GAP_MS = 90e3/" "$ZALO_JS"',
|
|
3091
|
+
' echo "[entrypoint] patched zalouser watchdog gap: 35s -> 90s"',
|
|
3092
|
+
'fi',
|
|
3093
|
+
].join('\n'));
|
|
3094
|
+
runtimeParts.push([
|
|
3095
|
+
'# Zalo channel auto-restart monitor (background)',
|
|
3096
|
+
'(',
|
|
3097
|
+
' sleep 180',
|
|
3098
|
+
' while true; do',
|
|
3099
|
+
' sleep 60',
|
|
3100
|
+
' STATUS=$(openclaw channels status 2>/dev/null | grep -i "Zalo Personal" || true)',
|
|
3101
|
+
' if echo "$STATUS" | grep -qi "stopped"; then',
|
|
3102
|
+
' echo "[zalo-monitor] Zalo channel stopped - restarting container in 5s"',
|
|
3103
|
+
' sleep 5',
|
|
3104
|
+
' kill 1 2>/dev/null || true',
|
|
3105
|
+
' fi',
|
|
3106
|
+
' done',
|
|
3107
|
+
') &',
|
|
3108
|
+
].join('\n'));
|
|
3109
|
+
runtimeParts.push('openclaw gateway run');
|
|
2758
3110
|
const runtimeScript = ['#!/bin/sh', 'set -e', ...runtimeParts].join('\n');
|
|
2759
|
-
const runtimeScriptB64 = encodeBase64Utf8(runtimeScript);
|
|
2760
3111
|
const dockerfile = `FROM node:22-slim
|
|
2761
3112
|
|
|
2762
|
-
RUN apt-get update && apt-get install -y git curl python3
|
|
2763
|
-
|
|
3113
|
+
RUN apt-get update && apt-get install -y git curl python3 && rm -rf /var/lib/apt/lists/*
|
|
3114
|
+
|
|
2764
3115
|
ARG OPENCLAW_VER="${openClawNpmSpec}"
|
|
2765
3116
|
ARG CACHE_BUST=""
|
|
2766
3117
|
RUN echo "CACHE_BUST=$CACHE_BUST" && npm install -g $OPENCLAW_VER ${openClawRuntimePackages}${skillLines}${pluginLines}
|
|
2767
3118
|
${patchLine}
|
|
2768
|
-
|
|
3119
|
+
|
|
3120
|
+
COPY entrypoint.sh /usr/local/bin/openclaw-entrypoint.sh
|
|
3121
|
+
RUN chmod +x /usr/local/bin/openclaw-entrypoint.sh
|
|
2769
3122
|
WORKDIR /root/project
|
|
2770
3123
|
|
|
2771
|
-
EXPOSE
|
|
3124
|
+
EXPOSE ${gatewayPort}
|
|
2772
3125
|
|
|
2773
3126
|
CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
|
|
2774
3127
|
|
|
2775
|
-
const syncScript = build9RouterSmartRouteSyncScript(
|
|
2776
|
-
const
|
|
2777
|
-
const
|
|
2778
|
-
const patchScriptBase64 = encodeBase64Utf8(patchScript);
|
|
2779
|
-
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64);
|
|
3128
|
+
const syncScript = build9RouterSmartRouteSyncScript();
|
|
3129
|
+
const patchScript = build9RouterPatchScript();
|
|
3130
|
+
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(routerPort);
|
|
2780
3131
|
const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
|
|
2781
3132
|
|
|
2782
|
-
const appEnvironmentBlock =
|
|
3133
|
+
const appEnvironmentBlock = ` environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/root/project/.openclaw\n - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1\n - OPENCLAW_GATEWAY_PORT=${gatewayPort}\n - OPENCLAW_PORT=${gatewayPort}\n tmpfs:\n - /root/project/.openclaw/plugin-runtime-deps\n`;
|
|
2783
3134
|
|
|
2784
3135
|
let compose;
|
|
2785
3136
|
if (isMultiBot) {
|
|
@@ -2788,7 +3139,7 @@ const patchScript = build9RouterPatchScript();
|
|
|
2788
3139
|
: isLocal
|
|
2789
3140
|
? ' depends_on:\n ollama:\n condition: service_healthy\n'
|
|
2790
3141
|
: '';
|
|
2791
|
-
const extraHosts =
|
|
3142
|
+
const extraHosts = `${extraHostsBlock}\n`;
|
|
2792
3143
|
if (is9Router) {
|
|
2793
3144
|
compose = `name: ${multiComposeName}
|
|
2794
3145
|
services:
|
|
@@ -2797,11 +3148,11 @@ services:
|
|
|
2797
3148
|
container_name: ${multiAppContainerName}
|
|
2798
3149
|
restart: always
|
|
2799
3150
|
env_file:
|
|
2800
|
-
-
|
|
3151
|
+
- ../../.env
|
|
2801
3152
|
${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
|
|
2802
3153
|
- ${volumeMount}
|
|
2803
3154
|
ports:
|
|
2804
|
-
- "
|
|
3155
|
+
- "${gatewayPort}:${gatewayPort}"
|
|
2805
3156
|
|
|
2806
3157
|
9router:
|
|
2807
3158
|
image: node:22-slim
|
|
@@ -2813,13 +3164,15 @@ ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
|
|
|
2813
3164
|
- |
|
|
2814
3165
|
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
2815
3166
|
environment:
|
|
2816
|
-
- PORT
|
|
3167
|
+
- PORT=${routerPort}
|
|
2817
3168
|
- HOSTNAME=0.0.0.0
|
|
2818
3169
|
- CI=true
|
|
2819
3170
|
volumes:
|
|
2820
3171
|
- 9router-data:/root/.9router
|
|
3172
|
+
- ./sync.js:/tmp/sync.js:ro
|
|
3173
|
+
- ./patch-9router.js:/tmp/patch-9router.js:ro
|
|
2821
3174
|
ports:
|
|
2822
|
-
- "
|
|
3175
|
+
- "${routerPort}:${routerPort}"
|
|
2823
3176
|
|
|
2824
3177
|
volumes:
|
|
2825
3178
|
9router-data:`;
|
|
@@ -2832,11 +3185,11 @@ services:
|
|
|
2832
3185
|
container_name: ${multiAppContainerName}
|
|
2833
3186
|
restart: always
|
|
2834
3187
|
env_file:
|
|
2835
|
-
-
|
|
3188
|
+
- ../../.env
|
|
2836
3189
|
${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
|
|
2837
3190
|
- ${volumeMount}
|
|
2838
3191
|
ports:
|
|
2839
|
-
- "
|
|
3192
|
+
- "${gatewayPort}:${gatewayPort}"
|
|
2840
3193
|
|
|
2841
3194
|
ollama:
|
|
2842
3195
|
image: ollama/ollama:latest
|
|
@@ -2872,11 +3225,11 @@ services:
|
|
|
2872
3225
|
container_name: ${multiAppContainerName}
|
|
2873
3226
|
restart: always
|
|
2874
3227
|
env_file:
|
|
2875
|
-
-
|
|
3228
|
+
- ../../.env
|
|
2876
3229
|
${appEnvironmentBlock}${extraHosts} volumes:
|
|
2877
3230
|
- ${volumeMount}
|
|
2878
3231
|
ports:
|
|
2879
|
-
- "
|
|
3232
|
+
- "${gatewayPort}:${gatewayPort}"`;
|
|
2880
3233
|
}
|
|
2881
3234
|
} else if (is9Router) {
|
|
2882
3235
|
compose = `name: ${singleComposeName}
|
|
@@ -2886,13 +3239,14 @@ services:
|
|
|
2886
3239
|
container_name: ${singleAppContainerName}
|
|
2887
3240
|
restart: always
|
|
2888
3241
|
env_file:
|
|
2889
|
-
-
|
|
3242
|
+
- ../../.env
|
|
2890
3243
|
depends_on:
|
|
2891
3244
|
- 9router
|
|
2892
|
-
${appEnvironmentBlock}${
|
|
3245
|
+
${appEnvironmentBlock}${extraHostsBlock}\n volumes:
|
|
2893
3246
|
- ${volumeMount}
|
|
3247
|
+
- openclaw-plugins:/root/project/.openclaw/npm
|
|
2894
3248
|
ports:
|
|
2895
|
-
- "
|
|
3249
|
+
- "${gatewayPort}:${gatewayPort}"
|
|
2896
3250
|
|
|
2897
3251
|
9router:
|
|
2898
3252
|
image: node:22-slim
|
|
@@ -2904,16 +3258,19 @@ ${appEnvironmentBlock}${hasBrowser ? `${extraHostsBlock}\n` : ''} volumes:
|
|
|
2904
3258
|
- |
|
|
2905
3259
|
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
2906
3260
|
environment:
|
|
2907
|
-
- PORT
|
|
3261
|
+
- PORT=${routerPort}
|
|
2908
3262
|
- HOSTNAME=0.0.0.0
|
|
2909
3263
|
- CI=true
|
|
2910
3264
|
volumes:
|
|
2911
3265
|
- 9router-data:/root/.9router
|
|
3266
|
+
- ./sync.js:/tmp/sync.js:ro
|
|
3267
|
+
- ./patch-9router.js:/tmp/patch-9router.js:ro
|
|
2912
3268
|
ports:
|
|
2913
|
-
- "
|
|
3269
|
+
- "${routerPort}:${routerPort}"
|
|
2914
3270
|
|
|
2915
3271
|
volumes:
|
|
2916
|
-
9router-data
|
|
3272
|
+
9router-data:
|
|
3273
|
+
openclaw-plugins:`;
|
|
2917
3274
|
} else if (isLocal) {
|
|
2918
3275
|
const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
|
|
2919
3276
|
compose = `name: ${singleComposeName}
|
|
@@ -2922,12 +3279,12 @@ services:
|
|
|
2922
3279
|
build: .
|
|
2923
3280
|
container_name: ${singleAppContainerName}
|
|
2924
3281
|
restart: always
|
|
2925
|
-
env_file:
|
|
3282
|
+
env_file: ../../.env
|
|
2926
3283
|
${appEnvironmentBlock} depends_on:
|
|
2927
3284
|
ollama:
|
|
2928
3285
|
condition: service_healthy
|
|
2929
|
-
${
|
|
2930
|
-
- "
|
|
3286
|
+
${extraHostsBlock}\n ports:
|
|
3287
|
+
- "${gatewayPort}:${gatewayPort}"
|
|
2931
3288
|
volumes:
|
|
2932
3289
|
- ${volumeMount}
|
|
2933
3290
|
|
|
@@ -2965,17 +3322,19 @@ services:
|
|
|
2965
3322
|
container_name: ${singleAppContainerName}
|
|
2966
3323
|
restart: always
|
|
2967
3324
|
env_file:
|
|
2968
|
-
-
|
|
3325
|
+
- ../../.env
|
|
2969
3326
|
${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''} volumes:
|
|
2970
3327
|
- ${volumeMount}
|
|
2971
3328
|
ports:
|
|
2972
|
-
- "
|
|
3329
|
+
- "${gatewayPort}:${gatewayPort}"`;
|
|
2973
3330
|
}
|
|
2974
3331
|
|
|
2975
3332
|
return {
|
|
2976
3333
|
dockerfile,
|
|
2977
3334
|
compose,
|
|
3335
|
+
entrypointScript: runtimeScript,
|
|
2978
3336
|
syncScript,
|
|
3337
|
+
patchScript,
|
|
2979
3338
|
docker9RouterEntrypointScript,
|
|
2980
3339
|
gatewayPatchCmd: buildGatewayPatchCmd(),
|
|
2981
3340
|
};
|
|
@@ -2996,7 +3355,9 @@ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globa
|
|
|
2996
3355
|
Object.assign(exports, globalThis.__openclawDockerGen);
|
|
2997
3356
|
}
|
|
2998
3357
|
|
|
2999
|
-
|
|
3358
|
+
|
|
3359
|
+
|
|
3360
|
+
// ââ€â‚¬Ã¢â€â‚¬ buildNativeScriptCtx and shared native runtime helpers (setup/generators/native-helpers-gen.js)
|
|
3000
3361
|
// @ts-nocheck
|
|
3001
3362
|
/* eslint-disable no-undef, no-unused-vars */
|
|
3002
3363
|
/**
|
|
@@ -3017,10 +3378,9 @@ function buildNativeScriptCtx(options) {
|
|
|
3017
3378
|
const ch = CHANNELS[state.channel];
|
|
3018
3379
|
const is9Router = !!(provider && provider.isProxy);
|
|
3019
3380
|
const isOllama = !!(provider && provider.isLocal);
|
|
3020
|
-
const hasBrowser = state.config.skills.includes('browser');
|
|
3021
3381
|
const nativeSkillConfigs = state.config.skills
|
|
3022
3382
|
.map((sid) => SKILLS.find((s) => s.id === sid))
|
|
3023
|
-
.filter((skill) => skill && skill.id !== 'scheduler' && skill.slug
|
|
3383
|
+
.filter((skill) => skill && skill.id !== 'scheduler' && skill.slug);
|
|
3024
3384
|
const selectedModel = (state.config.model || 'ollama/gemma4:e2b').replace('ollama/', '');
|
|
3025
3385
|
const isMultiBot = state.botCount > 1 && state.channel === 'telegram';
|
|
3026
3386
|
const projectDir = state.config.projectPath || '.';
|
|
@@ -3040,7 +3400,6 @@ function buildNativeScriptCtx(options) {
|
|
|
3040
3400
|
Object.assign(globalThis, {
|
|
3041
3401
|
isVi,
|
|
3042
3402
|
provider,
|
|
3043
|
-
hasBrowser,
|
|
3044
3403
|
is9Router,
|
|
3045
3404
|
selectedModel,
|
|
3046
3405
|
isMultiBot,
|
|
@@ -3048,15 +3407,101 @@ function buildNativeScriptCtx(options) {
|
|
|
3048
3407
|
});
|
|
3049
3408
|
|
|
3050
3409
|
function native9RouterSyncScriptContent() {
|
|
3051
|
-
return `const fs=require('fs');
|
|
3052
|
-
const path=require('path');
|
|
3053
|
-
const INTERVAL=30000;
|
|
3054
|
-
const
|
|
3055
|
-
const
|
|
3056
|
-
const
|
|
3057
|
-
const
|
|
3058
|
-
|
|
3059
|
-
|
|
3410
|
+
return `const fs = require('fs');
|
|
3411
|
+
const path = require('path');
|
|
3412
|
+
const INTERVAL = 30000;
|
|
3413
|
+
const DB_PATH = path.join(process.env.DATA_DIR || '.9router', 'db', 'data.sqlite');
|
|
3414
|
+
const PORT = process.env.PORT || 20128;
|
|
3415
|
+
const COMBO_NAME = 'smart-route';
|
|
3416
|
+
const API_BASE = \\\`http://localhost:\\\${PORT}\\\`;
|
|
3417
|
+
|
|
3418
|
+
function ensureSettings() {
|
|
3419
|
+
try {
|
|
3420
|
+
let Database;
|
|
3421
|
+
try {
|
|
3422
|
+
const cp = require('child_process');
|
|
3423
|
+
const npmRoot = cp.execSync('npm root -g').toString().trim();
|
|
3424
|
+
Database = require(path.join(npmRoot, '9router', 'node_modules', 'better-sqlite3'));
|
|
3425
|
+
} catch {
|
|
3426
|
+
try { Database = require('better-sqlite3'); } catch { return; }
|
|
3427
|
+
}
|
|
3428
|
+
const db = Database(DB_PATH);
|
|
3429
|
+
const existing = db.prepare("SELECT * FROM settings WHERE id = 1").get();
|
|
3430
|
+
if (!existing) {
|
|
3431
|
+
db.prepare("INSERT INTO settings (id, data) VALUES (1, ?)").run(JSON.stringify({ requireLogin: false }));
|
|
3432
|
+
} else {
|
|
3433
|
+
try {
|
|
3434
|
+
const data = JSON.parse(existing.data || '{}');
|
|
3435
|
+
if (data.requireLogin !== false) {
|
|
3436
|
+
data.requireLogin = false;
|
|
3437
|
+
db.prepare("UPDATE settings SET data = ? WHERE id = 1").run(JSON.stringify(data));
|
|
3438
|
+
}
|
|
3439
|
+
} catch {}
|
|
3440
|
+
}
|
|
3441
|
+
db.close();
|
|
3442
|
+
} catch (e) {}
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
const sync = async () => {
|
|
3446
|
+
try {
|
|
3447
|
+
if (!fs.existsSync(DB_PATH)) return;
|
|
3448
|
+
|
|
3449
|
+
let existingCombo = null;
|
|
3450
|
+
try {
|
|
3451
|
+
const resp = await fetch(\\\`\\\${API_BASE}/api/combos\\\`);
|
|
3452
|
+
if (resp.status === 401) {
|
|
3453
|
+
ensureSettings();
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
const data = await resp.json();
|
|
3457
|
+
if (data.combos) {
|
|
3458
|
+
existingCombo = data.combos.find(c => c.name === COMBO_NAME);
|
|
3459
|
+
}
|
|
3460
|
+
} catch (e) { return; }
|
|
3461
|
+
|
|
3462
|
+
if (existingCombo) return;
|
|
3463
|
+
|
|
3464
|
+
let activeProviders = [];
|
|
3465
|
+
try {
|
|
3466
|
+
const resp = await fetch(\\\`\\\${API_BASE}/api/providers\\\`);
|
|
3467
|
+
const data = await resp.json();
|
|
3468
|
+
const conns = data.connections || data.providerConnections || [];
|
|
3469
|
+
activeProviders = [...new Set(
|
|
3470
|
+
conns.filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider)
|
|
3471
|
+
)];
|
|
3472
|
+
} catch (e) { return; }
|
|
3473
|
+
|
|
3474
|
+
if (!activeProviders.length) return;
|
|
3475
|
+
|
|
3476
|
+
let models = [];
|
|
3477
|
+
try {
|
|
3478
|
+
const resp = await fetch(\\\`\\\${API_BASE}/api/models\\\`);
|
|
3479
|
+
const data = await resp.json();
|
|
3480
|
+
if (data.models && Array.isArray(data.models)) {
|
|
3481
|
+
models = data.models
|
|
3482
|
+
.filter(m => activeProviders.includes(m.provider))
|
|
3483
|
+
.filter(m => !/(embedding|image|tts|stt|audio|vision)/i.test(m.model))
|
|
3484
|
+
.map(m => m.fullModel);
|
|
3485
|
+
}
|
|
3486
|
+
models = [...new Set(models)];
|
|
3487
|
+
} catch (e) { return; }
|
|
3488
|
+
|
|
3489
|
+
if (!models.length) return;
|
|
3490
|
+
|
|
3491
|
+
try {
|
|
3492
|
+
await fetch(\\\`\\\${API_BASE}/api/combos\\\`, {
|
|
3493
|
+
method: 'POST',
|
|
3494
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3495
|
+
body: JSON.stringify({ name: COMBO_NAME, models })
|
|
3496
|
+
});
|
|
3497
|
+
console.log('[sync-combo] Created smart-route with ' + models.length + ' models');
|
|
3498
|
+
} catch (e) {}
|
|
3499
|
+
} catch (e) {}
|
|
3500
|
+
};
|
|
3501
|
+
|
|
3502
|
+
if (fs.existsSync(DB_PATH)) ensureSettings();
|
|
3503
|
+
setTimeout(sync, 10000);
|
|
3504
|
+
setInterval(sync, INTERVAL);`;
|
|
3060
3505
|
}
|
|
3061
3506
|
|
|
3062
3507
|
function native9RouterServerEntryLookup() {
|
|
@@ -3202,12 +3647,16 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3202
3647
|
list: multiBotAgentMetas.map((meta) => ({
|
|
3203
3648
|
id: meta.agentId,
|
|
3204
3649
|
name: meta.name,
|
|
3205
|
-
workspace: '.openclaw/' + meta.workspaceDir,
|
|
3206
|
-
agentDir: `agents/${meta.agentId}/agent`,
|
|
3207
3650
|
model: { primary: state.config.model, fallbacks: [] },
|
|
3208
3651
|
})),
|
|
3209
3652
|
},
|
|
3210
|
-
commands: {
|
|
3653
|
+
commands: {
|
|
3654
|
+
native: 'auto',
|
|
3655
|
+
nativeSkills: 'auto',
|
|
3656
|
+
restart: true,
|
|
3657
|
+
ownerDisplay: 'raw',
|
|
3658
|
+
...(state.config.skills.includes('scheduler') ? { ownerAllowFrom: ['*'] } : {}),
|
|
3659
|
+
},
|
|
3211
3660
|
bindings: multiBotAgentMetas.map((meta) => ({
|
|
3212
3661
|
agentId: meta.agentId,
|
|
3213
3662
|
match: { channel: 'telegram', accountId: meta.accountId },
|
|
@@ -3234,6 +3683,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3234
3683
|
},
|
|
3235
3684
|
tools: {
|
|
3236
3685
|
profile: 'full',
|
|
3686
|
+
...(state.config.skills.includes('scheduler') ? { alsoAllow: ['group:automation'] } : {}),
|
|
3237
3687
|
exec: { host: 'gateway', security: 'full', ask: 'off' },
|
|
3238
3688
|
agentToAgent: {
|
|
3239
3689
|
enabled: true,
|
|
@@ -3254,12 +3704,11 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3254
3704
|
}
|
|
3255
3705
|
} : {}),
|
|
3256
3706
|
gateway: {
|
|
3257
|
-
port:
|
|
3707
|
+
port: 18789,
|
|
3258
3708
|
mode: 'local',
|
|
3259
|
-
bind: state.nativeOs === 'vps' ? '
|
|
3260
|
-
...(state.nativeOs === 'vps' ? { customBindHost: '0.0.0.0' } : {}),
|
|
3709
|
+
bind: state.nativeOs === 'vps' ? 'lan' : 'loopback',
|
|
3261
3710
|
controlUi: {
|
|
3262
|
-
allowedOrigins: getGatewayAllowedOrigins(
|
|
3711
|
+
allowedOrigins: getGatewayAllowedOrigins(18789),
|
|
3263
3712
|
},
|
|
3264
3713
|
auth: { mode: 'token', token: crypto.randomUUID().replace(/-/g, '') },
|
|
3265
3714
|
},
|
|
@@ -3304,7 +3753,6 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3304
3753
|
provider,
|
|
3305
3754
|
is9Router,
|
|
3306
3755
|
isOllama,
|
|
3307
|
-
hasBrowser,
|
|
3308
3756
|
selectedModel,
|
|
3309
3757
|
isMultiBot,
|
|
3310
3758
|
projectDir,
|
|
@@ -3324,7 +3772,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3324
3772
|
};
|
|
3325
3773
|
}
|
|
3326
3774
|
|
|
3327
|
-
//
|
|
3775
|
+
// ââ€â‚¬Ã¢â€â‚¬ botEnvContent, botConfigContent, botWorkspaceFiles, botFiles, helpers (setup/generators/config-gen.js)
|
|
3328
3776
|
// @ts-nocheck
|
|
3329
3777
|
/* eslint-disable no-undef, no-unused-vars */
|
|
3330
3778
|
/**
|
|
@@ -3340,7 +3788,6 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3340
3788
|
* @global {boolean} isVi - Vietnamese language mode
|
|
3341
3789
|
* @global {object} provider - Current primary provider config
|
|
3342
3790
|
* @global {boolean} isMultiBot - Multi-bot mode flag
|
|
3343
|
-
* @global {boolean} hasBrowser - Browser plugin selected
|
|
3344
3791
|
* @global {boolean} is9Router - 9Router proxy mode
|
|
3345
3792
|
* @global {string} projectDir - Output project directory path
|
|
3346
3793
|
* @global {Function} getGatewayAllowedOrigins
|
|
@@ -3381,12 +3828,12 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3381
3828
|
const bot = state.bots[botIndex] || {};
|
|
3382
3829
|
const botName = bot.name || `Bot ${botIndex + 1}`;
|
|
3383
3830
|
const agentId = botName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
3384
|
-
const basePort =
|
|
3831
|
+
const basePort = 18789 + botIndex;
|
|
3385
3832
|
const groupId = state.groupId || '';
|
|
3386
3833
|
|
|
3387
3834
|
// Force use global provider if proxy mode is chosen globally, else use bot specific provider
|
|
3388
3835
|
const botProvider = (provider && provider.isProxy) ? provider : (PROVIDERS[bot.provider] || provider);
|
|
3389
|
-
const actualModel = botProvider.isProxy ?
|
|
3836
|
+
const actualModel = botProvider.isProxy ? 'smart-route' : (bot.model || state.config.model);
|
|
3390
3837
|
const bcfg = globalThis.__openclawBotConfig;
|
|
3391
3838
|
|
|
3392
3839
|
const cfg = bcfg.buildOpenclawJson({
|
|
@@ -3405,8 +3852,8 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3405
3852
|
groupId,
|
|
3406
3853
|
selectedSkills: state.config.skills,
|
|
3407
3854
|
skills: SKILLS,
|
|
3408
|
-
hasBrowserDesktop:
|
|
3409
|
-
hasBrowserServer:
|
|
3855
|
+
hasBrowserDesktop: false,
|
|
3856
|
+
hasBrowserServer: false,
|
|
3410
3857
|
gatewayPort: basePort,
|
|
3411
3858
|
gatewayAllowedOrigins: getGatewayAllowedOrigins(basePort),
|
|
3412
3859
|
osChoice: state.nativeOs || '',
|
|
@@ -3476,7 +3923,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3476
3923
|
agentWorkspaceDir,
|
|
3477
3924
|
persona: bot.persona || '',
|
|
3478
3925
|
userInfo: state.config.userInfo || '',
|
|
3479
|
-
hasBrowser,
|
|
3926
|
+
hasBrowser: false,
|
|
3480
3927
|
soulVariant: 'wizard',
|
|
3481
3928
|
memoryVariant: 'wizard',
|
|
3482
3929
|
hasZaloMod: state.channel === 'zalo-personal',
|
|
@@ -3554,7 +4001,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3554
4001
|
}));
|
|
3555
4002
|
}
|
|
3556
4003
|
|
|
3557
|
-
//
|
|
4004
|
+
// ââ€â‚¬Ã¢â€â‚¬ generateZaloLoginBat, generateZaloLoginSh (unified) (setup/generators/zalo-login-gen.js)
|
|
3558
4005
|
// @ts-nocheck
|
|
3559
4006
|
/* eslint-disable no-undef, no-unused-vars */
|
|
3560
4007
|
/**
|
|
@@ -3591,15 +4038,13 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3591
4038
|
* @param {string} opts.projectDirVar - BAT var for project dir e.g. '%PROJECT_DIR%'
|
|
3592
4039
|
* @param {string} opts.label - Unique BAT label suffix (avoid duplicate labels)
|
|
3593
4040
|
* e.g. 'win', 'multi', 'combo'
|
|
3594
|
-
* @param {boolean} [opts.useInstance] -
|
|
4041
|
+
* @param {boolean} [opts.useInstance] - Reserved for QR file suffix only.
|
|
3595
4042
|
* @returns {string[]} Lines to push into the bat script
|
|
3596
4043
|
*/
|
|
3597
4044
|
function generateZaloLoginBat(opts) {
|
|
3598
4045
|
const { homeVar, projectDirVar, label = 'default', useInstance = false } = opts;
|
|
3599
4046
|
const credPath = `${homeVar}\\credentials\\zalouser\\credentials.json`;
|
|
3600
|
-
const loginCmd =
|
|
3601
|
-
? 'openclaw channels login --channel zalouser --instance default --verbose'
|
|
3602
|
-
: 'openclaw channels login --channel zalouser --verbose';
|
|
4047
|
+
const loginCmd = 'openclaw channels login --channel zalouser --verbose';
|
|
3603
4048
|
const contLabel = `:zalo_continue_${label}`;
|
|
3604
4049
|
const retryLabel = `:retry_zalo_${label}`;
|
|
3605
4050
|
|
|
@@ -3639,15 +4084,13 @@ function generateZaloLoginBat(opts) {
|
|
|
3639
4084
|
* @param {object} opts
|
|
3640
4085
|
* @param {string} opts.homeVar - Shell var for OPENCLAW_HOME e.g. '$OPENCLAW_HOME'
|
|
3641
4086
|
* @param {string} opts.projectDirVar - Shell var for project dir e.g. '$PROJECT_DIR'
|
|
3642
|
-
* @param {boolean} [opts.useInstance] -
|
|
4087
|
+
* @param {boolean} [opts.useInstance] - Reserved for QR file suffix only.
|
|
3643
4088
|
* @returns {string[]} Lines to push into the sh script
|
|
3644
4089
|
*/
|
|
3645
4090
|
function generateZaloLoginSh(opts) {
|
|
3646
4091
|
const { homeVar, projectDirVar, useInstance = false } = opts;
|
|
3647
4092
|
const credPath = `${homeVar}/credentials/zalouser/credentials.json`;
|
|
3648
|
-
const loginCmd =
|
|
3649
|
-
? 'openclaw channels login --channel zalouser --instance default --verbose'
|
|
3650
|
-
: 'openclaw channels login --channel zalouser --verbose';
|
|
4093
|
+
const loginCmd = 'openclaw channels login --channel zalouser --verbose';
|
|
3651
4094
|
|
|
3652
4095
|
return [
|
|
3653
4096
|
`# ── Zalo Personal Login (idempotent) ─────────────────────────────────`,
|
|
@@ -3671,7 +4114,7 @@ function generateZaloLoginSh(opts) {
|
|
|
3671
4114
|
];
|
|
3672
4115
|
}
|
|
3673
4116
|
|
|
3674
|
-
//
|
|
4117
|
+
// ââ€â‚¬Ã¢â€â‚¬ generateStartScript wizard wrapper (delegates to install-gen) (setup/generators/gateway-start-gen.js)
|
|
3675
4118
|
// @ts-nocheck
|
|
3676
4119
|
/* eslint-disable no-undef, no-unused-vars */
|
|
3677
4120
|
/**
|
|
@@ -3718,7 +4161,7 @@ function generateStartScript() {
|
|
|
3718
4161
|
return null;
|
|
3719
4162
|
}
|
|
3720
4163
|
|
|
3721
|
-
//
|
|
4164
|
+
// ââ€â‚¬Ã¢â€â‚¬ generateUninstallScript, setup script download helpers (setup/generators/download-gen.js)
|
|
3722
4165
|
// @ts-nocheck
|
|
3723
4166
|
/* eslint-disable no-undef, no-unused-vars */
|
|
3724
4167
|
/**
|
|
@@ -3975,7 +4418,7 @@ window.__downloadGen = {
|
|
|
3975
4418
|
updateDockerDlLabel,
|
|
3976
4419
|
};
|
|
3977
4420
|
|
|
3978
|
-
//
|
|
4421
|
+
// ââ€â‚¬Ã¢â€â‚¬ Windows .bat  if (state.nativeOs === "win") block (setup/os/win-bat.js)
|
|
3979
4422
|
// @ts-nocheck
|
|
3980
4423
|
/* eslint-disable no-undef, no-unused-vars */
|
|
3981
4424
|
/**
|
|
@@ -3985,7 +4428,7 @@ window.__downloadGen = {
|
|
|
3985
4428
|
*/
|
|
3986
4429
|
function generateWinBat(ctx) {
|
|
3987
4430
|
const {
|
|
3988
|
-
ch, isVi, provider, is9Router, isOllama,
|
|
4431
|
+
ch, isVi, provider, is9Router, isOllama, selectedModel, isMultiBot, projectDir, todayStamp, allPlugins, pluginCmd, nativeSkillInstallCmds, nativeSkillConfigs, providerLines, sharedNativeFileMap, sharedNativeEnvContent, sharedNativeExecApprovalsContent, sharedNativeConfigContent, native9RouterSyncScriptContent, native9RouterServerEntryLookup, windowsHiddenNodeLaunch, generateUninstallScript,
|
|
3989
4432
|
} = ctx;
|
|
3990
4433
|
// state, PROVIDERS, SKILLS, PLUGINS, CHANNELS are IIFE-level globals
|
|
3991
4434
|
let scriptContent;
|
|
@@ -4027,8 +4470,8 @@ function generateWinBat(ctx) {
|
|
|
4027
4470
|
|
|
4028
4471
|
function appendDashboardInfo(arr) {
|
|
4029
4472
|
arr.push('echo.');
|
|
4030
|
-
arr.push('echo OpenClaw Dashboard: http://127.0.0.1:
|
|
4031
|
-
arr.push('echo Other reachable URLs: http://localhost:
|
|
4473
|
+
arr.push('echo OpenClaw Dashboard: http://127.0.0.1:18789');
|
|
4474
|
+
arr.push('echo Other reachable URLs: http://localhost:18789');
|
|
4032
4475
|
arr.push('echo If the dashboard asks for a Gateway Token, run: openclaw dashboard');
|
|
4033
4476
|
if (is9Router) {
|
|
4034
4477
|
arr.push('echo.');
|
|
@@ -4038,11 +4481,6 @@ function generateWinBat(ctx) {
|
|
|
4038
4481
|
}
|
|
4039
4482
|
|
|
4040
4483
|
providerLines(lines, 'bat');
|
|
4041
|
-
if (hasBrowser) {
|
|
4042
|
-
lines.push('echo Cai Browser Automation runtime...');
|
|
4043
|
-
lines.push('call npm install -g agent-browser playwright || goto :fail');
|
|
4044
|
-
lines.push('call npx playwright install chromium || goto :fail');
|
|
4045
|
-
}
|
|
4046
4484
|
if (nativeSkillInstallCmds.length > 0) {
|
|
4047
4485
|
lines.push('echo Cai skills...');
|
|
4048
4486
|
lines.push(...nativeSkillInstallCmds);
|
|
@@ -4120,7 +4558,7 @@ function generateWinBat(ctx) {
|
|
|
4120
4558
|
return { scriptName, scriptContent };
|
|
4121
4559
|
}
|
|
4122
4560
|
|
|
4123
|
-
//
|
|
4561
|
+
// ââ€â‚¬Ã¢â€â‚¬ macOS .sh  if (state.nativeOs === "macos") block (setup/os/macos-sh.js)
|
|
4124
4562
|
// @ts-nocheck
|
|
4125
4563
|
/* eslint-disable no-undef, no-unused-vars */
|
|
4126
4564
|
/**
|
|
@@ -4130,7 +4568,7 @@ function generateWinBat(ctx) {
|
|
|
4130
4568
|
*/
|
|
4131
4569
|
function generateMacOsSh(ctx) {
|
|
4132
4570
|
const {
|
|
4133
|
-
ch, isVi, provider, is9Router, isOllama,
|
|
4571
|
+
ch, isVi, provider, is9Router, isOllama, selectedModel, isMultiBot, projectDir, todayStamp, allPlugins, pluginCmd, nativeSkillInstallCmds, nativeSkillConfigs, providerLines, sharedNativeFileMap, sharedNativeEnvContent, sharedNativeExecApprovalsContent, sharedNativeConfigContent, native9RouterSyncScriptContent, native9RouterServerEntryLookup, windowsHiddenNodeLaunch, generateUninstallScript,
|
|
4134
4572
|
} = ctx;
|
|
4135
4573
|
// state, PROVIDERS, SKILLS, PLUGINS, CHANNELS are IIFE-level globals
|
|
4136
4574
|
let scriptContent;
|
|
@@ -4238,7 +4676,7 @@ function generateMacOsSh(ctx) {
|
|
|
4238
4676
|
return { scriptName, scriptContent };
|
|
4239
4677
|
}
|
|
4240
4678
|
|
|
4241
|
-
//
|
|
4679
|
+
// ââ€â‚¬Ã¢â€â‚¬ VPS/PM2 .sh  if (state.nativeOs === "vps") block (setup/os/vps-sh.js)
|
|
4242
4680
|
// @ts-nocheck
|
|
4243
4681
|
/* eslint-disable no-undef, no-unused-vars */
|
|
4244
4682
|
/**
|
|
@@ -4248,7 +4686,7 @@ function generateMacOsSh(ctx) {
|
|
|
4248
4686
|
*/
|
|
4249
4687
|
function generateVpsSh(ctx) {
|
|
4250
4688
|
const {
|
|
4251
|
-
ch, isVi, provider, is9Router, isOllama,
|
|
4689
|
+
ch, isVi, provider, is9Router, isOllama, selectedModel, isMultiBot, projectDir, todayStamp, allPlugins, pluginCmd, nativeSkillInstallCmds, nativeSkillConfigs, providerLines, sharedNativeFileMap, sharedNativeEnvContent, sharedNativeExecApprovalsContent, sharedNativeConfigContent, native9RouterSyncScriptContent, native9RouterServerEntryLookup, windowsHiddenNodeLaunch, generateUninstallScript,
|
|
4252
4690
|
} = ctx;
|
|
4253
4691
|
// state, PROVIDERS, SKILLS, PLUGINS, CHANNELS are IIFE-level globals
|
|
4254
4692
|
let scriptContent;
|
|
@@ -4352,7 +4790,7 @@ GWEOF`);
|
|
|
4352
4790
|
}
|
|
4353
4791
|
|
|
4354
4792
|
vps.push('echo ""');
|
|
4355
|
-
vps.push('echo "Dashboard: http://127.0.0.1:
|
|
4793
|
+
vps.push('echo "Dashboard: http://127.0.0.1:18789"');
|
|
4356
4794
|
if (is9Router) vps.push('echo "9Router: http://127.0.0.1:20128/dashboard"');
|
|
4357
4795
|
vps.push('echo ""');
|
|
4358
4796
|
vps.push(`echo "Restart: bash start-bot.sh"`);
|
|
@@ -4363,7 +4801,7 @@ GWEOF`);
|
|
|
4363
4801
|
return { scriptName, scriptContent };
|
|
4364
4802
|
}
|
|
4365
4803
|
|
|
4366
|
-
//
|
|
4804
|
+
// ââ€â‚¬Ã¢â€â‚¬ Linux Desktop .sh  if (state.nativeOs === "linux-desktop") block (setup/os/linux-sh.js)
|
|
4367
4805
|
// @ts-nocheck
|
|
4368
4806
|
/* eslint-disable no-undef, no-unused-vars */
|
|
4369
4807
|
/**
|
|
@@ -4373,7 +4811,7 @@ GWEOF`);
|
|
|
4373
4811
|
*/
|
|
4374
4812
|
function generateLinuxSh(ctx) {
|
|
4375
4813
|
const {
|
|
4376
|
-
ch, isVi, provider, is9Router, isOllama,
|
|
4814
|
+
ch, isVi, provider, is9Router, isOllama, selectedModel, isMultiBot, projectDir, todayStamp, allPlugins, pluginCmd, nativeSkillInstallCmds, nativeSkillConfigs, providerLines, sharedNativeFileMap, sharedNativeEnvContent, sharedNativeExecApprovalsContent, sharedNativeConfigContent, native9RouterSyncScriptContent, native9RouterServerEntryLookup, windowsHiddenNodeLaunch, generateUninstallScript,
|
|
4377
4815
|
} = ctx;
|
|
4378
4816
|
// state, PROVIDERS, SKILLS, PLUGINS, CHANNELS are IIFE-level globals
|
|
4379
4817
|
let scriptContent;
|
|
@@ -4426,7 +4864,7 @@ function generateLinuxSh(ctx) {
|
|
|
4426
4864
|
return { scriptName, scriptContent };
|
|
4427
4865
|
}
|
|
4428
4866
|
|
|
4429
|
-
//
|
|
4867
|
+
// ââ€â‚¬Ã¢â€â‚¬ UI init, language/channel/deploy controllers, form rendering (setup/ui/controller.js)
|
|
4430
4868
|
// @ts-nocheck
|
|
4431
4869
|
/* eslint-disable no-undef, no-unused-vars */
|
|
4432
4870
|
/**
|
|
@@ -5075,7 +5513,7 @@ function generateLinuxSh(ctx) {
|
|
|
5075
5513
|
envContent.textContent = lines.join('\n');
|
|
5076
5514
|
}
|
|
5077
5515
|
|
|
5078
|
-
//
|
|
5516
|
+
// ââ€â‚¬Ã¢â€â‚¬ Multi-bot state + UI (setup/ui/multi-bot.js) ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
5079
5517
|
// @ts-nocheck
|
|
5080
5518
|
/* eslint-disable no-undef, no-unused-vars */
|
|
5081
5519
|
/**
|
|
@@ -5424,7 +5862,7 @@ function generateLinuxSh(ctx) {
|
|
|
5424
5862
|
|
|
5425
5863
|
// ========== Step 1: Deploy Mode + OS ==========
|
|
5426
5864
|
|
|
5427
|
-
//
|
|
5865
|
+
// ââ€â‚¬Ã¢â€â‚¬ Step navigation, validation (setup/ui/steps.js) ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬
|
|
5428
5866
|
// @ts-nocheck
|
|
5429
5867
|
/* eslint-disable no-undef, no-unused-vars */
|
|
5430
5868
|
/**
|
|
@@ -5584,7 +6022,7 @@ function generateLinuxSh(ctx) {
|
|
|
5584
6022
|
|
|
5585
6023
|
// ========== Step 2: Bot Config ==========
|
|
5586
6024
|
|
|
5587
|
-
//
|
|
6025
|
+
// ââ€â‚¬Ã¢â€â‚¬ generateOutput + generateNativeScript + clipboard (setup/ui/output.js)
|
|
5588
6026
|
// @ts-nocheck
|
|
5589
6027
|
/* eslint-disable no-undef, no-unused-vars */
|
|
5590
6028
|
/**
|
|
@@ -5628,68 +6066,6 @@ function generateLinuxSh(ctx) {
|
|
|
5628
6066
|
const routerNotice = document.getElementById('9router-notice');
|
|
5629
6067
|
if (routerNotice) routerNotice.style.display = is9Router ? '' : 'none';
|
|
5630
6068
|
|
|
5631
|
-
// Show/hide Browser Automation notice + generate scripts
|
|
5632
|
-
const browserNotice = document.getElementById('browser-notice');
|
|
5633
|
-
const hasBrowserSkill = state.config.skills.includes('browser');
|
|
5634
|
-
if (browserNotice) browserNotice.style.display = hasBrowserSkill ? '' : 'none';
|
|
5635
|
-
|
|
5636
|
-
if (hasBrowserSkill) {
|
|
5637
|
-
// Chrome Debug .bat script
|
|
5638
|
-
const chromeBat = `@echo off
|
|
5639
|
-
echo ============================================
|
|
5640
|
-
echo OpenClaw - Chrome Debug Mode
|
|
5641
|
-
echo ============================================
|
|
5642
|
-
echo.
|
|
5643
|
-
echo Dang tat Chrome cu (neu co)...
|
|
5644
|
-
taskkill /F /IM chrome.exe >nul 2>&1
|
|
5645
|
-
timeout /t 3 /nobreak >nul
|
|
5646
|
-
echo Dang mo Chrome voi Debug Mode...
|
|
5647
|
-
start "" "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" ^
|
|
5648
|
-
--remote-debugging-port=9222 ^
|
|
5649
|
-
--remote-allow-origins=* ^
|
|
5650
|
-
--user-data-dir="%TEMP%\\chrome-debug"
|
|
5651
|
-
timeout /t 4 /nobreak >nul
|
|
5652
|
-
powershell -Command "try { Invoke-WebRequest -Uri 'http://localhost:9222/json/version' -UseBasicParsing -TimeoutSec 5 | Out-Null; Write-Host 'OK! Chrome Debug Mode dang chay tren port 9222.' -ForegroundColor Green } catch { Write-Host 'LOI: Port 9222 chua mo. Thu lai.' -ForegroundColor Red }"
|
|
5653
|
-
echo.
|
|
5654
|
-
pause`;
|
|
5655
|
-
setOutput('out-chrome-bat', chromeBat);
|
|
5656
|
-
|
|
5657
|
-
// Task Scheduler PowerShell script
|
|
5658
|
-
const taskPs1 = `# ============================================
|
|
5659
|
-
# OpenClaw - Auto-start Chrome Debug khi logon
|
|
5660
|
-
# Chay script nay 1 lan voi Run as Administrator
|
|
5661
|
-
# ============================================
|
|
5662
|
-
|
|
5663
|
-
# Duong dan toi file .bat
|
|
5664
|
-
$batPath = "$env:USERPROFILE\\start-chrome-debug.bat"
|
|
5665
|
-
|
|
5666
|
-
# Kiem tra file .bat ton tai
|
|
5667
|
-
if (-not (Test-Path $batPath)) {
|
|
5668
|
-
Write-Host "LOI: Khong tim thay $batPath" -ForegroundColor Red
|
|
5669
|
-
Write-Host "Hay luu file start-chrome-debug.bat vao $env:USERPROFILE truoc." -ForegroundColor Yellow
|
|
5670
|
-
exit 1
|
|
5671
|
-
}
|
|
5672
|
-
|
|
5673
|
-
# Tao Scheduled Task
|
|
5674
|
-
$action = New-ScheduledTaskAction -Execute $batPath
|
|
5675
|
-
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
5676
|
-
$trigger.Delay = "PT10S" # Delay 10 giay sau khi logon
|
|
5677
|
-
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
|
|
5678
|
-
|
|
5679
|
-
Register-ScheduledTask \\
|
|
5680
|
-
-TaskName "OpenClaw-ChromeDebug" \\
|
|
5681
|
-
-Description "Tu dong bat Chrome Debug Mode cho OpenClaw Browser Automation" \\
|
|
5682
|
-
-Action $action \\
|
|
5683
|
-
-Trigger $trigger \\
|
|
5684
|
-
-Settings $settings \\
|
|
5685
|
-
-Force
|
|
5686
|
-
|
|
5687
|
-
Write-Host ""
|
|
5688
|
-
Write-Host "DONE! Task 'OpenClaw-ChromeDebug' da duoc tao." -ForegroundColor Green
|
|
5689
|
-
Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (delay 10s)." -ForegroundColor Cyan`;
|
|
5690
|
-
setOutput('out-task-ps1', taskPs1);
|
|
5691
|
-
}
|
|
5692
|
-
|
|
5693
6069
|
// Show/hide docker vs native output based on deployMode
|
|
5694
6070
|
const dockerOut = document.getElementById('docker-output');
|
|
5695
6071
|
const nativeOut = document.getElementById('native-output');
|
|
@@ -5718,8 +6094,6 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
5718
6094
|
: 'Script is generated from your choices. Download and run — everything else is handled automatically.';
|
|
5719
6095
|
|
|
5720
6096
|
const agentId = state.config.botName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-$/, '') || 'chat';
|
|
5721
|
-
|
|
5722
|
-
const hasBrowser = state.config.skills.includes('browser');
|
|
5723
6097
|
// isMultiBot => unified into isMultiBot above
|
|
5724
6098
|
const multiBotAgentMetas = isMultiBot
|
|
5725
6099
|
? state.bots.slice(0, state.botCount).map((bot, idx) => {
|
|
@@ -5751,15 +6125,26 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
5751
6125
|
},
|
|
5752
6126
|
list: [{
|
|
5753
6127
|
id: agentId,
|
|
5754
|
-
|
|
6128
|
+
name: botName,
|
|
6129
|
+
workspace: `/root/project/.openclaw/workspace-${agentId}`,
|
|
5755
6130
|
agentDir: `agents/${agentId}/agent`,
|
|
5756
6131
|
model: { primary: state.config.model, fallbacks: [] },
|
|
5757
6132
|
}],
|
|
5758
6133
|
},
|
|
5759
|
-
commands: {
|
|
6134
|
+
commands: {
|
|
6135
|
+
native: 'auto',
|
|
6136
|
+
nativeSkills: 'auto',
|
|
6137
|
+
restart: true,
|
|
6138
|
+
ownerDisplay: 'raw',
|
|
6139
|
+
...(state.config.skills.includes('scheduler') ? { ownerAllowFrom: ['*'] } : {}),
|
|
6140
|
+
},
|
|
5760
6141
|
channels: ch.channelConfig,
|
|
5761
|
-
tools: {
|
|
5762
|
-
|
|
6142
|
+
tools: {
|
|
6143
|
+
profile: 'full',
|
|
6144
|
+
...(state.config.skills.includes('scheduler') ? { alsoAllow: ['group:automation'] } : {}),
|
|
6145
|
+
exec: { host: 'gateway', security: 'full', ask: 'off' },
|
|
6146
|
+
},
|
|
6147
|
+
gateway: common.buildGatewayConfig(18789, state.deployMode, getGatewayAllowedOrigins(18789), state.nativeOs || ''),
|
|
5763
6148
|
};
|
|
5764
6149
|
|
|
5765
6150
|
// 9Router: add proxy endpoint config under models.providers
|
|
@@ -5797,19 +6182,6 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
5797
6182
|
};
|
|
5798
6183
|
}
|
|
5799
6184
|
|
|
5800
|
-
// Browser Automation: inject browser config
|
|
5801
|
-
if (hasBrowser) {
|
|
5802
|
-
clawConfig.browser = {
|
|
5803
|
-
enabled: true,
|
|
5804
|
-
defaultProfile: 'host-chrome',
|
|
5805
|
-
profiles: {
|
|
5806
|
-
'host-chrome': {
|
|
5807
|
-
cdpUrl: 'http://127.0.0.1:9222',
|
|
5808
|
-
color: '#4285F4',
|
|
5809
|
-
},
|
|
5810
|
-
},
|
|
5811
|
-
};
|
|
5812
|
-
}
|
|
5813
6185
|
|
|
5814
6186
|
// Skills: register all selected skills in openclaw.json → skills.entries
|
|
5815
6187
|
// This makes OpenClaw actually load and enable them at runtime
|
|
@@ -5818,8 +6190,6 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
5818
6190
|
state.config.skills.forEach((sid) => {
|
|
5819
6191
|
const skill = SKILLS.find((s) => s.id === sid);
|
|
5820
6192
|
if (!skill) return;
|
|
5821
|
-
// Native browser tools are loaded automatically via the root 'browser' config
|
|
5822
|
-
if (skill.slug === 'browser-automation') return;
|
|
5823
6193
|
// scheduler is now native cron (not a skill), skip registering in skills.entries
|
|
5824
6194
|
if (skill.id === 'scheduler' || !skill.slug) return;
|
|
5825
6195
|
|
|
@@ -5849,7 +6219,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
5849
6219
|
clawConfig.agents.list = multiBotAgentMetas.map((meta) => ({
|
|
5850
6220
|
id: meta.agentId,
|
|
5851
6221
|
name: meta.name,
|
|
5852
|
-
workspace:
|
|
6222
|
+
workspace: `/root/project/.openclaw/${meta.workspaceDir}`,
|
|
5853
6223
|
agentDir: `agents/${meta.agentId}/agent`,
|
|
5854
6224
|
model: { primary: state.config.model, fallbacks: [] },
|
|
5855
6225
|
}));
|
|
@@ -5880,6 +6250,7 @@ Write-Host "Chrome se tu dong bat Debug Mode moi khi ban dang nhap Windows (dela
|
|
|
5880
6250
|
};
|
|
5881
6251
|
clawConfig.tools = {
|
|
5882
6252
|
...(clawConfig.tools || {}),
|
|
6253
|
+
...(state.config.skills.includes('scheduler') ? { alsoAllow: ['group:automation'] } : {}),
|
|
5883
6254
|
agentToAgent: {
|
|
5884
6255
|
enabled: true,
|
|
5885
6256
|
allow: multiBotAgentMetas.map((meta) => meta.agentId),
|
|
@@ -5946,7 +6317,7 @@ model:
|
|
|
5946
6317
|
const allSkills = [];
|
|
5947
6318
|
state.config.skills.forEach((sid) => {
|
|
5948
6319
|
const skill = SKILLS.find((s) => s.id === sid);
|
|
5949
|
-
if (skill && skill.slug
|
|
6320
|
+
if (skill && skill.slug) {
|
|
5950
6321
|
allSkills.push(skill.slug);
|
|
5951
6322
|
}
|
|
5952
6323
|
});
|
|
@@ -5966,7 +6337,6 @@ model:
|
|
|
5966
6337
|
is9Router,
|
|
5967
6338
|
isLocal,
|
|
5968
6339
|
isMultiBot: state.botCount > 1 && (state.channel === 'telegram'),
|
|
5969
|
-
hasBrowser,
|
|
5970
6340
|
selectedModel: state.config.model || 'ollama/gemma4:e2b',
|
|
5971
6341
|
agentId: 'bot',
|
|
5972
6342
|
allSkills,
|
|
@@ -5974,6 +6344,9 @@ model:
|
|
|
5974
6344
|
dockerfileSkillInstallMode: 'build',
|
|
5975
6345
|
runtimeCommandParts: [
|
|
5976
6346
|
pluginInstallCmd,
|
|
6347
|
+
// zalouser: use npm install (not openclaw plugins install) to avoid openclaw.json writes
|
|
6348
|
+
// ClawHub build gives error:not configured; npm version works correctly
|
|
6349
|
+
state.channel === 'zalo-personal' ? 'ensure_zalouser' : '',
|
|
5977
6350
|
'while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done >/dev/null 2>&1 &'
|
|
5978
6351
|
].filter(Boolean),
|
|
5979
6352
|
plainSingleExtraHosts: true,
|
|
@@ -5983,6 +6356,7 @@ model:
|
|
|
5983
6356
|
});
|
|
5984
6357
|
const dockerfile = dockerArtifacts.dockerfile;
|
|
5985
6358
|
const compose = dockerArtifacts.compose;
|
|
6359
|
+
const entrypointScript = dockerArtifacts.entrypointScript;
|
|
5986
6360
|
// isMultiBot => unified into isMultiBot above
|
|
5987
6361
|
setOutput('out-dockerfile', dockerfile);
|
|
5988
6362
|
setOutput('out-compose', compose);
|
|
@@ -6122,12 +6496,6 @@ _This file is yours to evolve. If someone asks to change it, confirm with the us
|
|
|
6122
6496
|
`;
|
|
6123
6497
|
|
|
6124
6498
|
// ── AGENTS.md — Hướng dẫn vận hành ("operating manual")
|
|
6125
|
-
const browserAgentSection = hasBrowser ? `
|
|
6126
|
-
## Sử dụng Trình Duyệt (Browser Automation)
|
|
6127
|
-
- BẠN SỞ HỮU GIAO DIỆN TRÌNH DUYỆT CHROME THẬT CỦA USER thông qua script \`browser-tool.js\`. ĐỌC NGAY FILE \`BROWSER.md\` để biết cách dùng.
|
|
6128
|
-
- BẮT BUỘC dùng \`bash\` để gõ \`node ~/browser-tool.js ...\` khi có yêu cầu liên quan đến web thay vì dùng web_search!
|
|
6129
|
-
- KHÔNG BAO GIỜ từ chối mở trình duyệt với lý do "không có giao diện" hay "máy chủ không có browser".
|
|
6130
|
-
` : '';
|
|
6131
6499
|
|
|
6132
6500
|
const agentsMd = lang === 'vi'
|
|
6133
6501
|
? `# Hướng dẫn vận hành
|
|
@@ -6155,7 +6523,6 @@ Bạn hỗ trợ người dùng trong mọi tác vụ hàng ngày thông qua tin
|
|
|
6155
6523
|
- Luôn xác nhận kết quả tool trước khi trả lời user
|
|
6156
6524
|
- Nếu tool lỗi → thông báo rõ ràng, đề xuất cách khác
|
|
6157
6525
|
|
|
6158
|
-
${browserAgentSection}
|
|
6159
6526
|
${state.config.securityRules}
|
|
6160
6527
|
`
|
|
6161
6528
|
|
|
@@ -6239,143 +6606,6 @@ _Update this file as you learn more about the user. Ask before changing._
|
|
|
6239
6606
|
// ── MEMORY.md — via scaffold builder
|
|
6240
6607
|
const memoryMd = _scaffold.buildMemoryDoc({ isVi, variant: 'wizard' });
|
|
6241
6608
|
|
|
6242
|
-
// Browser tool files (generated into workspace when hasBrowser)
|
|
6243
|
-
const browserToolJs = `/**
|
|
6244
|
-
* browser-tool.js - Connect to real Windows Chrome via CDP
|
|
6245
|
-
* Flow: Docker -> socat (port 9222) -> host.docker.internal:9222 -> user's Chrome
|
|
6246
|
-
*/
|
|
6247
|
-
const { chromium } = require('/usr/local/lib/node_modules/openclaw/node_modules/playwright-core');
|
|
6248
|
-
const action = process.argv[2];
|
|
6249
|
-
const param1 = process.argv[3];
|
|
6250
|
-
const param2 = process.argv[4];
|
|
6251
|
-
const CDP_URL = 'http://127.0.0.1:9222';
|
|
6252
|
-
(async () => {
|
|
6253
|
-
let browser;
|
|
6254
|
-
try {
|
|
6255
|
-
browser = await chromium.connectOverCDP(CDP_URL, { timeout: 5000 });
|
|
6256
|
-
const ctx = browser.contexts()[0];
|
|
6257
|
-
const pages = ctx.pages();
|
|
6258
|
-
let page = pages.length > 0 ? pages[0] : await ctx.newPage();
|
|
6259
|
-
if (action === 'open') {
|
|
6260
|
-
console.log('[Browser] Mo trang: ' + param1);
|
|
6261
|
-
await page.goto(param1, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
6262
|
-
await page.waitForTimeout(1500);
|
|
6263
|
-
console.log('[Browser] Da mo: ' + (await page.title()) + ' | ' + page.url());
|
|
6264
|
-
} else if (action === 'get_text') {
|
|
6265
|
-
const text = await page.evaluate(() => {
|
|
6266
|
-
document.querySelectorAll('script,style,noscript,svg').forEach(e => e.remove());
|
|
6267
|
-
return document.body.innerText.trim();
|
|
6268
|
-
});
|
|
6269
|
-
console.log(text.substring(0, 4000));
|
|
6270
|
-
} else if (action === 'click') {
|
|
6271
|
-
await page.locator(param1).first().click({ timeout: 5000 });
|
|
6272
|
-
await page.waitForTimeout(600);
|
|
6273
|
-
console.log('[Browser] Da click: ' + param1);
|
|
6274
|
-
} else if (action === 'fill') {
|
|
6275
|
-
await page.locator(param1).first().fill(param2, { timeout: 5000 });
|
|
6276
|
-
console.log('[Browser] Da dien "' + param2 + '" vao: ' + param1);
|
|
6277
|
-
} else if (action === 'press') {
|
|
6278
|
-
await page.keyboard.press(param1);
|
|
6279
|
-
await page.waitForTimeout(1000);
|
|
6280
|
-
console.log('[Browser] Da nhan phim: ' + param1);
|
|
6281
|
-
} else if (action === 'status') {
|
|
6282
|
-
console.log('[Browser] Ket noi Chrome that! Tab: ' + (await page.title()) + ' | ' + page.url());
|
|
6283
|
-
} else {
|
|
6284
|
-
console.log('Lenh: open <url> | get_text | click <sel> | fill <sel> <text> | press <key> | status');
|
|
6285
|
-
}
|
|
6286
|
-
} catch(e) {
|
|
6287
|
-
if (e.message.includes('ECONNREFUSED') || e.message.includes('Timeout')) {
|
|
6288
|
-
console.error('[Browser] Ban chua bat Chrome Debug! Hay click dup vao start-chrome-debug.bat roi thu lai.');
|
|
6289
|
-
} else {
|
|
6290
|
-
console.error('[Browser] Loi:', e.message);
|
|
6291
|
-
}
|
|
6292
|
-
} finally {
|
|
6293
|
-
if (browser) await browser.close();
|
|
6294
|
-
}
|
|
6295
|
-
`;
|
|
6296
|
-
|
|
6297
|
-
const browserMd = `# Dieu Khien Trinh Duyet (Browser Automation)
|
|
6298
|
-
|
|
6299
|
-
Bot dieu khien Chrome THAT tren man hinh Windows cua ban. Moi thao tac hien thi truc tiep!
|
|
6300
|
-
|
|
6301
|
-
## Lenh su dung (chay qua bash)
|
|
6302
|
-
|
|
6303
|
-
\\\`\\\`\\\`bash
|
|
6304
|
-
node ~/browser-tool.js status
|
|
6305
|
-
node ~/browser-tool.js open "https://google.com"
|
|
6306
|
-
node ~/browser-tool.js get_text
|
|
6307
|
-
node ~/browser-tool.js fill "input[name='q']" "tu khoa"
|
|
6308
|
-
node ~/browser-tool.js press "Enter"
|
|
6309
|
-
node ~/browser-tool.js click "#button"
|
|
6310
|
-
\\\`\\\`\\\`
|
|
6311
|
-
|
|
6312
|
-
## QUY TAC BAT BUOC
|
|
6313
|
-
- KHONG BAO GIO tu choi mo trinh duyet.
|
|
6314
|
-
- Neu loi ECONNREFUSED: bao user bat lai start-chrome-debug.bat.
|
|
6315
|
-
- KHONG dung web_search khi user yeu cau browser.
|
|
6316
|
-
`;
|
|
6317
|
-
|
|
6318
|
-
const chromeBatContent = `@echo off
|
|
6319
|
-
echo ====== OpenClaw - Chrome Debug Mode ======
|
|
6320
|
-
echo.
|
|
6321
|
-
echo Dang tat Chrome cu (neu co)...
|
|
6322
|
-
taskkill /F /IM chrome.exe >nul 2>&1
|
|
6323
|
-
timeout /t 3 /nobreak >nul
|
|
6324
|
-
echo Dang mo Chrome voi Debug Mode...
|
|
6325
|
-
start "" "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" ^
|
|
6326
|
-
--remote-debugging-port=9222 ^
|
|
6327
|
-
--remote-allow-origins=* ^
|
|
6328
|
-
--user-data-dir="%TEMP%\\chrome-debug"
|
|
6329
|
-
timeout /t 4 /nobreak >nul
|
|
6330
|
-
powershell -Command "try { Invoke-WebRequest -Uri 'http://localhost:9222/json/version' -UseBasicParsing -TimeoutSec 5 | Out-Null; Write-Host 'OK! Chrome Debug Mode dang chay.' -ForegroundColor Green } catch { Write-Host 'LOI: Port 9222 chua mo.' -ForegroundColor Red }"
|
|
6331
|
-
echo.
|
|
6332
|
-
pause
|
|
6333
|
-
`;
|
|
6334
|
-
|
|
6335
|
-
const chromeShContent = `#!/usr/bin/env bash
|
|
6336
|
-
# ====== OpenClaw - Chrome Debug Mode (Mac/Linux) ======
|
|
6337
|
-
set -e
|
|
6338
|
-
echo "====== OpenClaw - Chrome Debug Mode ======"
|
|
6339
|
-
echo ""
|
|
6340
|
-
|
|
6341
|
-
# Detect Chrome path
|
|
6342
|
-
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
6343
|
-
CHROME_BIN="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
6344
|
-
[ ! -f "$CHROME_BIN" ] && CHROME_BIN="/Applications/Chromium.app/Contents/MacOS/Chromium"
|
|
6345
|
-
[ ! -f "$CHROME_BIN" ] && CHROME_BIN="/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"
|
|
6346
|
-
else
|
|
6347
|
-
CHROME_BIN="$(command -v google-chrome || command -v google-chrome-stable || command -v chromium-browser || command -v chromium || echo '')"
|
|
6348
|
-
fi
|
|
6349
|
-
[ -n "$CHROME_DEBUG_BIN" ] && CHROME_BIN="$CHROME_DEBUG_BIN"
|
|
6350
|
-
|
|
6351
|
-
if [ -z "$CHROME_BIN" ] || { [ ! -f "$CHROME_BIN" ] && [ ! -x "$CHROME_BIN" ]; }; then
|
|
6352
|
-
echo -e "\\033[31mERROR: Chrome/Chromium not found.\\033[0m"
|
|
6353
|
-
echo "Install Chrome or: export CHROME_DEBUG_BIN=/path/to/chrome"
|
|
6354
|
-
exit 1
|
|
6355
|
-
fi
|
|
6356
|
-
|
|
6357
|
-
echo "Using: $CHROME_BIN"
|
|
6358
|
-
echo "Killing existing Chrome debug instances..."
|
|
6359
|
-
pkill -f -- "--remote-debugging-port=9222" 2>/dev/null || true
|
|
6360
|
-
sleep 2
|
|
6361
|
-
|
|
6362
|
-
TMP_DIR="\${TMPDIR:-/tmp}/chrome-debug-openclaw"
|
|
6363
|
-
mkdir -p "$TMP_DIR"
|
|
6364
|
-
|
|
6365
|
-
echo "Starting Chrome in Debug Mode (port 9222)..."
|
|
6366
|
-
"$CHROME_BIN" \\
|
|
6367
|
-
--remote-debugging-port=9222 \\
|
|
6368
|
-
--remote-allow-origins=* \\
|
|
6369
|
-
--user-data-dir="$TMP_DIR" &
|
|
6370
|
-
|
|
6371
|
-
sleep 4
|
|
6372
|
-
if curl -s http://localhost:9222/json/version > /dev/null 2>&1; then
|
|
6373
|
-
echo -e "\\033[32mOK! Chrome Debug Mode is running on port 9222.\\033[0m"
|
|
6374
|
-
else
|
|
6375
|
-
echo -e "\\033[31mERROR: Port 9222 not responding.\\033[0m"
|
|
6376
|
-
exit 1
|
|
6377
|
-
fi
|
|
6378
|
-
`;
|
|
6379
6609
|
|
|
6380
6610
|
const envText = (document.getElementById('env-content')?.textContent || '').trim();
|
|
6381
6611
|
const rootEnvContent = envText ? `${envText}\n` : '';
|
|
@@ -6396,6 +6626,7 @@ fi
|
|
|
6396
6626
|
if (!isNativeMode) {
|
|
6397
6627
|
sharedFiles['docker/openclaw/Dockerfile'] = dockerfile;
|
|
6398
6628
|
sharedFiles['docker/openclaw/docker-compose.yml'] = compose;
|
|
6629
|
+
sharedFiles['docker/openclaw/entrypoint.sh'] = entrypointScript;
|
|
6399
6630
|
sharedFiles['docker/openclaw/.env'] = rootEnvContent;
|
|
6400
6631
|
}
|
|
6401
6632
|
sharedFiles[globalThis.__openclawCommon.TELEGRAM_SETUP_GUIDE_FILENAME] = buildTelegramPostInstallChecklist();
|
|
@@ -6429,10 +6660,6 @@ fi
|
|
|
6429
6660
|
});
|
|
6430
6661
|
sharedFiles[`.openclaw/${meta.workspaceDir}/TEAMS.md`] = _scaffold.buildTeamsDoc({ isVi });
|
|
6431
6662
|
sharedFiles[`.openclaw/${meta.workspaceDir}/MEMORY.md`] = memoryMd;
|
|
6432
|
-
if (hasBrowser) {
|
|
6433
|
-
sharedFiles[`.openclaw/${meta.workspaceDir}/browser-tool.js`] = browserToolJs;
|
|
6434
|
-
sharedFiles[`.openclaw/${meta.workspaceDir}/BROWSER.md`] = browserMd;
|
|
6435
|
-
}
|
|
6436
6663
|
}
|
|
6437
6664
|
state._generatedFiles = sharedFiles;
|
|
6438
6665
|
} else {
|
|
@@ -6452,10 +6679,6 @@ fi
|
|
|
6452
6679
|
}),
|
|
6453
6680
|
[`.openclaw/workspace-${agentId}/MEMORY.md`]: memoryMd,
|
|
6454
6681
|
'.gitignore': isNativeMode ? '.env\nnode_modules/' : '.env\ndocker/openclaw/.env\nnode_modules/',
|
|
6455
|
-
...(hasBrowser ? {
|
|
6456
|
-
[`.openclaw/workspace-${agentId}/browser-tool.js`]: browserToolJs,
|
|
6457
|
-
[`.openclaw/workspace-${agentId}/BROWSER.md`]: browserMd,
|
|
6458
|
-
} : {}),
|
|
6459
6682
|
};
|
|
6460
6683
|
if (rootEnvContent) {
|
|
6461
6684
|
singleFiles['.env'] = rootEnvContent;
|
|
@@ -6467,6 +6690,7 @@ fi
|
|
|
6467
6690
|
if (!isNativeMode) {
|
|
6468
6691
|
singleFiles['docker/openclaw/Dockerfile'] = dockerfile;
|
|
6469
6692
|
singleFiles['docker/openclaw/docker-compose.yml'] = compose;
|
|
6693
|
+
singleFiles['docker/openclaw/entrypoint.sh'] = entrypointScript;
|
|
6470
6694
|
singleFiles['docker/openclaw/.env'] = rootEnvContent;
|
|
6471
6695
|
}
|
|
6472
6696
|
state._generatedFiles = singleFiles;
|
|
@@ -6476,8 +6700,6 @@ fi
|
|
|
6476
6700
|
// chrome-debug, start-bot, uninstall added ONCE here, not per-bot-mode block
|
|
6477
6701
|
if (isNativeMode) {
|
|
6478
6702
|
const _files = state._generatedFiles;
|
|
6479
|
-
_files['start-chrome-debug.bat'] = chromeBatContent;
|
|
6480
|
-
_files['start-chrome-debug.sh'] = chromeShContent;
|
|
6481
6703
|
_files['start-bot.bat'] = generateStartBotBat({
|
|
6482
6704
|
projectDir: state.config.projectPath || '.',
|
|
6483
6705
|
openclawHome: (state.config.projectPath || '.') + '\\.openclaw',
|
|
@@ -6635,38 +6857,46 @@ fi
|
|
|
6635
6857
|
// ========== Zalo Personal Login Guide (post-setup) ==========
|
|
6636
6858
|
function generateZaloOnboardGuide() {
|
|
6637
6859
|
const lang = document.getElementById('cfg-language')?.value || 'vi';
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6860
|
+
const isVi = lang === 'vi';
|
|
6861
|
+
const containerName = state.botCount > 1 ? 'openclaw-multibot' : 'openclaw-bot';
|
|
6862
|
+
|
|
6863
|
+
setOutput('out-zalo-onboard-cmd', `# ${isVi ? 'Bước 1: Dọn dẹp session cũ' : 'Step 1: Clean up old session'}
|
|
6864
|
+
docker exec ${containerName} rm -f /root/project/.openclaw/credentials/zalouser/credentials.json
|
|
6642
6865
|
|
|
6643
|
-
|
|
6866
|
+
# ${isVi ? 'Bước 2: Kích hoạt màn hình login QR (Quét mã trên terminal)' : 'Step 2: Start login QR (Scan on terminal)'}
|
|
6867
|
+
docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose
|
|
6868
|
+
|
|
6869
|
+
# ${isVi ? 'Bước 3: Khởi động lại container sau khi login thành công' : 'Step 3: Restart container after successful login'}
|
|
6870
|
+
docker restart ${containerName}`);
|
|
6871
|
+
|
|
6872
|
+
if (isVi) {
|
|
6644
6873
|
setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
|
|
6645
|
-
│ Chạy lệnh bên trái để
|
|
6874
|
+
│ Chạy các lệnh bên trái để đăng nhập Zalo Personal │
|
|
6875
|
+
│ theo quy trình chuẩn 4 bước. │
|
|
6646
6876
|
├─────────────────────────────────────────────────────┤
|
|
6647
|
-
│ 1.
|
|
6648
|
-
│
|
|
6649
|
-
│
|
|
6650
|
-
│
|
|
6651
|
-
│ docker cp
|
|
6877
|
+
│ 1. Lệnh 1 xoá file credentials.json cũ để tránh │
|
|
6878
|
+
│ lỗi xung đột "Already linked". │
|
|
6879
|
+
│ 2. Lệnh 2 mở màn hình login. Quét mã QR hiện trên │
|
|
6880
|
+
│ terminal hoặc lấy file từ container: │
|
|
6881
|
+
│ docker cp ${containerName}:/tmp/openclaw/ │
|
|
6652
6882
|
│ openclaw-zalouser-qr-default.png . │
|
|
6653
|
-
│
|
|
6654
|
-
│
|
|
6655
|
-
│
|
|
6883
|
+
│ 3. Sau khi quét xong và terminal báo thành công, │
|
|
6884
|
+
│ nhấn Ctrl+C để thoát. │
|
|
6885
|
+
│ 4. Chạy Lệnh 3 để restart container giúp nhận tin. │
|
|
6656
6886
|
└─────────────────────────────────────────────────────┘`);
|
|
6657
6887
|
} else {
|
|
6658
6888
|
setOutput('out-zalo-onboard-guide', `┌─────────────────────────────────────────────────────┐
|
|
6659
|
-
│ Run the
|
|
6889
|
+
│ Run the commands on the left to login Zalo Personal│
|
|
6890
|
+
│ following the standard 4-step workflow. │
|
|
6660
6891
|
├─────────────────────────────────────────────────────┤
|
|
6661
|
-
│ 1.
|
|
6662
|
-
│
|
|
6663
|
-
│
|
|
6664
|
-
│
|
|
6665
|
-
│ docker cp
|
|
6892
|
+
│ 1. Command 1 deletes the old credentials.json file │
|
|
6893
|
+
│ to avoid "Already linked" conflicts. │
|
|
6894
|
+
│ 2. Command 2 opens the login interface. Scan the │
|
|
6895
|
+
│ QR on your terminal or copy the image file: │
|
|
6896
|
+
│ docker cp ${containerName}:/tmp/openclaw/ │
|
|
6666
6897
|
│ openclaw-zalouser-qr-default.png . │
|
|
6667
|
-
│
|
|
6668
|
-
│
|
|
6669
|
-
│ 7. Run channels status --probe; it should run. │
|
|
6898
|
+
│ 3. Once scanned and successful, press Ctrl+C. │
|
|
6899
|
+
│ 4. Run Command 3 to restart the container. │
|
|
6670
6900
|
└─────────────────────────────────────────────────────┘`);
|
|
6671
6901
|
}
|
|
6672
6902
|
}
|