create-openclaw-bot 5.6.1 → 5.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- // @ts-nocheck
1
+ // @ts-nocheck
2
2
  /* eslint-disable no-undef, no-unused-vars */
3
3
  /**
4
4
  * @fileoverview Part of the OpenClaw Setup Wizard IIFE bundle.
@@ -46,14 +46,14 @@
46
46
  botName: '',
47
47
  description: '',
48
48
  emoji: '🤖',
49
- provider: 'google',
50
- model: 'google/gemini-2.5-flash',
49
+ provider: '9router',
50
+ model: '9router/smart-route',
51
51
  language: 'vi',
52
52
  systemPrompt: '',
53
53
  userInfo: '',
54
54
  securityRules: '',
55
55
  plugins: [],
56
- skills: [],
56
+ skills: ['memory'],
57
57
  // Persisted credential inputs (Bug 1+2 fix)
58
58
  botToken: '',
59
59
  apiKey: '',
@@ -2,14 +2,17 @@
2
2
  (function (root) {
3
3
  const OPENCLAW_NPM_SPEC = 'openclaw@2026.4.14';
4
4
  const OPENCLAW_RUNTIME_PACKAGES = 'grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api';
5
+ const NINE_ROUTER_NPM_SPEC = '9router@latest';
5
6
  const TELEGRAM_RELAY_PLUGIN_SPEC = 'openclaw-telegram-multibot-relay';
7
+ const TELEGRAM_RELAY_PLUGIN_ID = 'telegram-multibot-relay';
8
+ const TELEGRAM_SETUP_GUIDE_FILENAME = 'TELEGRAM-GROUP-SETUP.md';
6
9
 
7
10
  function buildRelayPluginInstallCommand(prefix = 'openclaw') {
8
- return `${prefix} plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC} 2>/dev/null || true`;
11
+ return `if [ ! -d "$OPENCLAW_STATE_DIR/extensions/${TELEGRAM_RELAY_PLUGIN_ID}" ]; then ${prefix} plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC} 2>/dev/null || true; fi`;
9
12
  }
10
13
 
11
14
  function buildRelayPluginInstallCommandWin(prefix = 'openclaw') {
12
- return `${prefix} plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC} || exit /b 0`;
15
+ return `if not exist ".openclaw\\extensions\\${TELEGRAM_RELAY_PLUGIN_ID}\\" ${prefix} plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC} || exit /b 0`;
13
16
  }
14
17
 
15
18
  function buildTelegramPostInstallChecklist(options = {}) {
@@ -29,7 +32,7 @@
29
32
  }).join('\n');
30
33
 
31
34
  if (isVi) {
32
- return `# Telegram Post-Install Checklist
35
+ return `# Telegram Group Setup Guide
33
36
 
34
37
  Bot da duoc cai dat. Thuc hien cac buoc sau de hoat dong trong group.
35
38
 
@@ -91,7 +94,7 @@ Neu trong qua trinh setup bao loi cai plugin, sau khi bot dang chay hay chay:
91
94
  `;
92
95
  }
93
96
 
94
- return `# Telegram Post-Install Checklist
97
+ return `# Telegram Group Setup Guide
95
98
 
96
99
  Bots are installed. Complete the steps below to activate them in a group.
97
100
 
@@ -209,7 +212,10 @@ If setup reported a plugin install error, run this after the bot is running:
209
212
  root.__openclawCommon = {
210
213
  OPENCLAW_NPM_SPEC,
211
214
  OPENCLAW_RUNTIME_PACKAGES,
215
+ NINE_ROUTER_NPM_SPEC,
212
216
  TELEGRAM_RELAY_PLUGIN_SPEC,
217
+ TELEGRAM_RELAY_PLUGIN_ID,
218
+ TELEGRAM_SETUP_GUIDE_FILENAME,
213
219
  buildRelayPluginInstallCommand,
214
220
  buildRelayPluginInstallCommandWin,
215
221
  buildTelegramPostInstallChecklist,
@@ -1,5 +1,34 @@
1
1
  // @ts-nocheck
2
2
  (function (root) {
3
+ const SMART_ROUTE_PROVIDER_MODELS = {
4
+ codex: ['cx/gpt-5.4', 'cx/gpt-5.3-codex', 'cx/gpt-5.3-codex-high', 'cx/gpt-5.2-codex', 'cx/gpt-5.2', 'cx/gpt-5.1-codex-max', 'cx/gpt-5.1-codex', 'cx/gpt-5.1', 'cx/gpt-5-codex'],
5
+ 'claude-code': ['cc/claude-opus-4-6', 'cc/claude-sonnet-4-6', 'cc/claude-opus-4-5-20251101', 'cc/claude-sonnet-4-5-20250929', 'cc/claude-haiku-4-5-20251001'],
6
+ github: ['gh/gpt-5.4', 'gh/gpt-5.3-codex', 'gh/gpt-5.2-codex', 'gh/gpt-5.2', 'gh/gpt-5.1-codex-max', 'gh/gpt-5.1-codex', 'gh/gpt-5.1', 'gh/gpt-5', 'gh/gpt-4.1', 'gh/gpt-4o', 'gh/claude-opus-4.6', 'gh/claude-sonnet-4.6', 'gh/claude-sonnet-4.5', 'gh/claude-opus-4.5', 'gh/claude-haiku-4.5', 'gh/gemini-3-pro-preview', 'gh/gemini-3-flash-preview', 'gh/gemini-2.5-pro'],
7
+ cursor: ['cu/default', 'cu/claude-4.6-opus-max', 'cu/claude-4.5-opus-high-thinking', 'cu/claude-4.5-sonnet-thinking', 'cu/claude-4.5-sonnet', 'cu/gpt-5.3-codex', 'cu/gpt-5.2-codex', 'cu/gemini-3-flash-preview'],
8
+ kilo: ['kc/anthropic/claude-sonnet-4-20250514', 'kc/anthropic/claude-opus-4-20250514', 'kc/google/gemini-2.5-pro', 'kc/google/gemini-2.5-flash', 'kc/openai/gpt-4.1', 'kc/deepseek/deepseek-chat'],
9
+ cline: ['cl/anthropic/claude-sonnet-4.6', 'cl/anthropic/claude-opus-4.6', 'cl/openai/gpt-5.3-codex', 'cl/openai/gpt-5.4', 'cl/google/gemini-3.1-pro-preview'],
10
+ 'gemini-cli': ['gc/gemini-3-flash-preview', 'gc/gemini-3-pro-preview'],
11
+ iflow: ['if/qwen3-coder-plus', 'if/kimi-k2', 'if/kimi-k2-thinking', 'if/glm-4.7', 'if/deepseek-r1', 'if/deepseek-v3.2', 'if/deepseek-v3', 'if/qwen3-max', 'if/qwen3-235b', 'if/iflow-rome-30ba3b'],
12
+ qwen: ['qw/qwen3-coder-plus', 'qw/qwen3-coder-flash', 'qw/vision-model', 'qw/coder-model'],
13
+ kiro: ['kr/claude-sonnet-4.5', 'kr/claude-haiku-4.5', 'kr/deepseek-3.2', 'kr/deepseek-3.1', 'kr/qwen3-coder-next'],
14
+ ollama: ['ollama/gemma4:e2b', 'ollama/gemma4:e4b', 'ollama/gemma4:26b', 'ollama/gemma4:31b', 'ollama/qwen3.5', 'ollama/kimi-k2.5', 'ollama/glm-5', 'ollama/glm-4.7-flash', 'ollama/minimax-m2.5', 'ollama/gpt-oss:120b'],
15
+ 'kimi-coding': ['kmc/kimi-k2.5', 'kmc/kimi-k2.5-thinking', 'kmc/kimi-latest'],
16
+ glm: ['glm/glm-5.1', 'glm/glm-5', 'glm/glm-4.7'],
17
+ 'glm-cn': ['glm/glm-5.1', 'glm/glm-5', 'glm/glm-4.7'],
18
+ minimax: ['minimax/MiniMax-M2.7', 'minimax/MiniMax-M2.5', 'minimax/MiniMax-M2.1'],
19
+ kimi: ['kimi/kimi-k2.5', 'kimi/kimi-k2.5-thinking', 'kimi/kimi-latest'],
20
+ deepseek: ['deepseek/deepseek-chat', 'deepseek/deepseek-reasoner'],
21
+ xai: ['xai/grok-4', 'xai/grok-4-fast-reasoning', 'xai/grok-code-fast-1'],
22
+ mistral: ['mistral/mistral-large-latest', 'mistral/codestral-latest'],
23
+ groq: ['groq/llama-3.3-70b-versatile', 'groq/openai/gpt-oss-120b'],
24
+ cerebras: ['cerebras/gpt-oss-120b'],
25
+ alicode: ['alicode/qwen3.5-plus', 'alicode/qwen3-coder-plus'],
26
+ openai: ['openai/gpt-4o', 'openai/gpt-4.1'],
27
+ anthropic: ['anthropic/claude-sonnet-4', 'anthropic/claude-haiku-3.5'],
28
+ gemini: ['gemini/gemini-2.5-flash', 'gemini/gemini-2.5-pro'],
29
+ };
30
+ const SMART_ROUTE_PROVIDER_ORDER = ['openai', 'anthropic', 'claude-code', 'codex', 'cursor', 'github', 'cline', 'kimi', 'minimax', 'deepseek', 'glm', 'alicode', 'xai', 'mistral', 'kilo', 'kiro', 'iflow', 'qwen', 'gemini-cli', 'ollama'];
31
+
3
32
  function encodeBase64Utf8(value) {
4
33
  if (typeof Buffer !== 'undefined') {
5
34
  return Buffer.from(String(value), 'utf8').toString('base64');
@@ -14,7 +43,8 @@
14
43
 
15
44
  function build9RouterSmartRouteSyncScript(dbPath) {
16
45
  return `const fs=require('fs');const INTERVAL=30000;const p='${dbPath}';
17
- const PM={codex:['cx/gpt-5.4','cx/gpt-5.3-codex','cx/gpt-5.3-codex-high','cx/gpt-5.2-codex','cx/gpt-5.2','cx/gpt-5.1-codex-max','cx/gpt-5.1-codex','cx/gpt-5.1','cx/gpt-5-codex'],'claude-code':['cc/claude-opus-4-6','cc/claude-sonnet-4-6','cc/claude-opus-4-5-20251101','cc/claude-sonnet-4-5-20250929','cc/claude-haiku-4-5-20251001'],github:['gh/gpt-5.4','gh/gpt-5.3-codex','gh/gpt-5.2-codex','gh/gpt-5.2','gh/gpt-5.1-codex-max','gh/gpt-5.1-codex','gh/gpt-5.1','gh/gpt-5','gh/gpt-4.1','gh/gpt-4o','gh/claude-opus-4.6','gh/claude-sonnet-4.6','gh/claude-sonnet-4.5','gh/claude-opus-4.5','gh/claude-haiku-4.5','gh/gemini-3-pro-preview','gh/gemini-3-flash-preview','gh/gemini-2.5-pro'],cursor:['cu/default','cu/claude-4.6-opus-max','cu/claude-4.5-opus-high-thinking','cu/claude-4.5-sonnet-thinking','cu/claude-4.5-sonnet','cu/gpt-5.3-codex','cu/gpt-5.2-codex','cu/gemini-3-flash-preview'],'kilo:['kc/anthropic/claude-sonnet-4-20250514','kc/anthropic/claude-opus-4-20250514','kc/google/gemini-2.5-pro','kc/google/gemini-2.5-flash','kc/openai/gpt-4.1','kc/deepseek/deepseek-chat'],'cline':['cl/anthropic/claude-sonnet-4.6','cl/anthropic/claude-opus-4.6','cl/openai/gpt-5.3-codex','cl/openai/gpt-5.4','cl/google/gemini-3.1-pro-preview'],'gemini-cli':['gc/gemini-3-flash-preview','gc/gemini-3-pro-preview'],'iflow':['if/qwen3-coder-plus','if/kimi-k2','if/kimi-k2-thinking','if/glm-4.7','if/deepseek-r1','if/deepseek-v3.2','if/deepseek-v3','if/qwen3-max','if/qwen3-235b','if/iflow-rome-30ba3b'],'qwen':['qw/qwen3-coder-plus','qw/qwen3-coder-flash','qw/vision-model','qw/coder-model'],'kiro':['kr/claude-sonnet-4.5','kr/claude-haiku-4.5','kr/deepseek-3.2','kr/deepseek-3.1','kr/qwen3-coder-next'],'ollama':['ollama/gemma4:e2b','ollama/gemma4:e4b','ollama/gemma4:26b','ollama/gemma4:31b','ollama/qwen3.5','ollama/kimi-k2.5','ollama/glm-5','ollama/glm-4.7-flash','ollama/minimax-m2.5','ollama/gpt-oss:120b'],'kimi-coding':['kmc/kimi-k2.5','kmc/kimi-k2.5-thinking','kmc/kimi-latest'],'glm':['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],'glm-cn':['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],'minimax':['minimax/MiniMax-M2.7','minimax/MiniMax-M2.5','minimax/MiniMax-M2.1'],'kimi':['kimi/kimi-k2.5','kimi/kimi-k2.5-thinking','kimi/kimi-latest'],'deepseek':['deepseek/deepseek-chat','deepseek/deepseek-reasoner'],'xai':['xai/grok-4','xai/grok-4-fast-reasoning','xai/grok-code-fast-1'],'mistral':['mistral/mistral-large-latest','mistral/codestral-latest'],'groq':['groq/llama-3.3-70b-versatile','groq/openai/gpt-oss-120b'],'cerebras':['cerebras/gpt-oss-120b'],'alicode':['alicode/qwen3.5-plus','alicode/qwen3-coder-plus'],'openai':['openai/gpt-4o','openai/gpt-4.1'],'anthropic':['anthropic/claude-sonnet-4','anthropic/claude-haiku-3.5'],'gemini':['gemini/gemini-2.5-flash','gemini/gemini-2.5-pro']};
46
+ const PM=${JSON.stringify(SMART_ROUTE_PROVIDER_MODELS)};
47
+ const PREF=${JSON.stringify(SMART_ROUTE_PROVIDER_ORDER)};
18
48
  console.log('[sync-combo] 9Router sync loop started...');
19
49
  const sync = async () => {
20
50
  try {
@@ -32,9 +62,9 @@ const sync = async () => {
32
62
  const res = await fetch('http://localhost:20128/api/providers');
33
63
  if (!res.ok) { console.log('[sync-combo] API not ready, retrying...'); return; }
34
64
  const d = await res.json();
35
- const a = (d.connections || []).filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider);
65
+ const rawConnections = Array.isArray(d.connections) ? d.connections : Array.isArray(d.providerConnections) ? d.providerConnections : [];
66
+ const a = [...new Set(rawConnections.filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider))];
36
67
  if (!a.length) { removeSmartRoute(); return; }
37
- const PREF = ['openai','anthropic','claude-code','codex','cursor','github','cline','kimi','minimax','deepseek','glm','alicode','xai','mistral','kilo','kiro','iflow','qwen','gemini-cli','ollama'];
38
68
  a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
39
69
  const m = a.flatMap(pv => PM[pv] || []);
40
70
  if (!m.length) { removeSmartRoute(); return; }
@@ -58,8 +88,9 @@ setInterval(sync, INTERVAL);`;
58
88
  }
59
89
 
60
90
  function build9RouterComposeEntrypointScript(syncScriptBase64) {
61
- return [
62
- 'npm install -g 9router',
91
+ const nineRouterSpec = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon && globalThis.__openclawCommon.NINE_ROUTER_NPM_SPEC) || '9router@latest';
92
+ return [
93
+ `npm install -g ${nineRouterSpec}`,
63
94
  `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
64
95
  'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
65
96
  'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
@@ -67,7 +98,7 @@ setInterval(sync, INTERVAL);`;
67
98
  }
68
99
 
69
100
  function buildGatewayPatchCmd() {
70
- return `node -e \\"const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);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 + ':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,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));}\\"`;
101
+ 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:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);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 + ':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,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));}\\"`;
71
102
  }
72
103
 
73
104
  function buildDockerArtifacts(options) {
@@ -83,7 +114,7 @@ setInterval(sync, INTERVAL);`;
83
114
  allSkills = [],
84
115
  dockerfileSkillInstallMode = 'none',
85
116
  runtimeCommandParts = [],
86
- volumeMount = '../../.openclaw:/root/.openclaw',
117
+ volumeMount = '../..:/root/project',
87
118
  singleComposeName = 'oc-bot',
88
119
  multiComposeName = 'oc-multibot',
89
120
  singleAppContainerName = 'openclaw-bot',
@@ -98,7 +129,7 @@ setInterval(sync, INTERVAL);`;
98
129
  emitBrowserInstall = true,
99
130
  } = options;
100
131
 
101
- const browserAptExtra = hasBrowser ? ' xvfb' : '';
132
+ const browserAptExtra = hasBrowser ? ' xvfb socat' : '';
102
133
  const browserInstallLines = hasBrowser && emitBrowserInstall
103
134
  ? [
104
135
  '',
@@ -116,34 +147,43 @@ setInterval(sync, INTERVAL);`;
116
147
  const patchLine = `RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const files=fs.readdirSync(dir).filter(n=>/\\.js$/.test(n));let patched=0;for(const file of files){const p=path.join(dir,file);let s='';try{s=fs.readFileSync(p,'utf8');}catch{continue;}if(s.includes(to)||!s.includes(from))continue;s=s.replace(from,to);fs.writeFileSync(p,s);patched++;}if(!patched){process.exit(0);}"`;
117
148
 
118
149
  // Dynamic runtime configuration injection for container internal IPs
119
- const setupInternalIpScript = `const fs=require('fs'),os=require('os'),p='/root/.openclaw/openclaw.json';if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);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 + ':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,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));}`;
150
+ const setupInternalIpScript = `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:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);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 + ':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,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));}`;
120
151
  const setupInternalIpB64 = encodeBase64Utf8(setupInternalIpScript);
121
152
 
122
153
  const runtimeParts = runtimeCommandParts.filter(Boolean);
123
- runtimeParts.unshift(`node -e "eval(Buffer.from('${setupInternalIpB64}','base64').toString())" &&`);
154
+ runtimeParts.unshift('export OPENCLAW_HOME="$PWD/.openclaw"');
155
+ runtimeParts.unshift('export OPENCLAW_STATE_DIR="$PWD/.openclaw"');
156
+ runtimeParts.unshift(`node -e 'eval(Buffer.from("${setupInternalIpB64}","base64").toString())'`);
124
157
  if (hasBrowser) {
125
- runtimeParts.push('(Xvfb :99 -screen 0 1280x720x24 > /dev/null 2>&1 &) && export DISPLAY=:99 && openclaw gateway run');
158
+ runtimeParts.push('socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &');
159
+ runtimeParts.push('Xvfb :99 -screen 0 1280x720x24 > /dev/null 2>&1 & DISPLAY=:99 openclaw gateway run');
126
160
  } else {
127
161
  runtimeParts.push('openclaw gateway run');
128
162
  }
163
+ const runtimeScript = ['#!/bin/sh', 'set -e', ...runtimeParts].join('\n');
164
+ const runtimeScriptB64 = encodeBase64Utf8(runtimeScript);
129
165
  const dockerfile = `FROM node:22-slim
130
166
 
131
167
  RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /var/lib/apt/lists/*
132
168
  ${browserInstallLines}
133
169
  ARG OPENCLAW_VER="${openClawNpmSpec}"
170
+ ARG CACHE_BUST=""
134
171
  RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}
135
172
  ${patchLine}
136
- WORKDIR /root/.openclaw
173
+ RUN node -e "require('fs').writeFileSync('/usr/local/bin/openclaw-entrypoint.sh', Buffer.from('${runtimeScriptB64}','base64').toString())" && chmod +x /usr/local/bin/openclaw-entrypoint.sh
174
+ WORKDIR /root/project
137
175
 
138
176
  EXPOSE 18791
139
177
 
140
- CMD sh -c "${runtimeParts.join(' ')}"`;
178
+ CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
141
179
 
142
180
  const syncScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
143
181
  const syncScriptBase64 = encodeBase64Utf8(syncScript);
144
182
  const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64);
145
183
  const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
146
184
 
185
+ const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/root/project/.openclaw\n';
186
+
147
187
  let compose;
148
188
  if (isMultiBot) {
149
189
  const dependsOn = is9Router
@@ -161,7 +201,7 @@ services:
161
201
  restart: always
162
202
  env_file:
163
203
  - .env
164
- ${dependsOn}${extraHosts} volumes:
204
+ ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
165
205
  - ${volumeMount}
166
206
  ports:
167
207
  - "18791:18791"
@@ -196,7 +236,7 @@ services:
196
236
  restart: always
197
237
  env_file:
198
238
  - .env
199
- ${dependsOn}${extraHosts} volumes:
239
+ ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
200
240
  - ${volumeMount}
201
241
  ports:
202
242
  - "18791:18791"
@@ -236,7 +276,7 @@ services:
236
276
  restart: always
237
277
  env_file:
238
278
  - .env
239
- ${extraHosts} volumes:
279
+ ${appEnvironmentBlock}${extraHosts} volumes:
240
280
  - ${volumeMount}
241
281
  ports:
242
282
  - "18791:18791"`;
@@ -252,7 +292,7 @@ services:
252
292
  - .env
253
293
  depends_on:
254
294
  - 9router
255
- ${hasBrowser ? `${extraHostsBlock}\n` : ''} volumes:
295
+ ${appEnvironmentBlock}${hasBrowser ? `${extraHostsBlock}\n` : ''} volumes:
256
296
  - ${volumeMount}
257
297
  ports:
258
298
  - "18791:18791"
@@ -286,7 +326,7 @@ services:
286
326
  container_name: ${singleAppContainerName}
287
327
  restart: always
288
328
  env_file: .env
289
- depends_on:
329
+ ${appEnvironmentBlock} depends_on:
290
330
  ollama:
291
331
  condition: service_healthy
292
332
  ${hasBrowser ? `${extraHostsBlock}\n` : ''} ports:
@@ -329,7 +369,7 @@ services:
329
369
  restart: always
330
370
  env_file:
331
371
  - .env
332
- ${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''} volumes:
372
+ ${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''} volumes:
333
373
  - ${volumeMount}
334
374
  ports:
335
375
  - "18791:18791"`;