create-openclaw-bot 5.6.13 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,442 +1,443 @@
1
- // @ts-nocheck
2
- (function (root) {
3
- const common = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon) || {};
4
- const SUPPORTED_CODEX_MODELS = common.SUPPORTED_CODEX_MODELS || ['cx/gpt-5.4', 'cx/gpt-5.3-codex', 'cx/gpt-5.2', 'cx/gpt-5.4-mini'];
5
- const SMART_ROUTE_PROVIDER_MODELS = common.SMART_ROUTE_PROVIDER_MODELS || { codex: SUPPORTED_CODEX_MODELS };
6
- const SMART_ROUTE_PROVIDER_ORDER = common.SMART_ROUTE_PROVIDER_ORDER || ['codex'];
7
-
8
- function encodeBase64Utf8(value) {
9
- if (typeof Buffer !== 'undefined') {
10
- return Buffer.from(String(value), 'utf8').toString('base64');
11
- }
12
- return btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
13
- }
14
-
15
- function indentBlock(text, spaces) {
16
- const prefix = ' '.repeat(spaces);
17
- return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
18
- }
19
-
20
- function build9RouterSmartRouteSyncScript(dbPath) {
21
- return `const fs=require('fs');const INTERVAL=30000;const p='${dbPath}';
22
- const PM=${JSON.stringify(SMART_ROUTE_PROVIDER_MODELS)};
23
- const PREF=${JSON.stringify(SMART_ROUTE_PROVIDER_ORDER)};
24
- console.log('[sync-combo] 9Router sync loop started...');
25
- const sync = async () => {
26
- try {
27
- let db = {};
28
- try { db = JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e){}
29
- if (!db.combos) db.combos = [];
30
- const removeSmartRoute = () => {
31
- const next = db.combos.filter(x => x.id !== 'smart-route');
32
- if (next.length !== db.combos.length) {
33
- db.combos = next;
34
- fs.writeFileSync(p, JSON.stringify(db, null, 2));
35
- console.log('[sync-combo] Removed smart-route (no active providers)');
36
- }
37
- };
38
- const res = await fetch('http://localhost:20128/api/providers');
39
- if (!res.ok) { console.log('[sync-combo] API not ready, retrying...'); return; }
40
- const d = await res.json();
41
- const rawConnections = Array.isArray(d.connections) ? d.connections : Array.isArray(d.providerConnections) ? d.providerConnections : [];
42
- const a = [...new Set(rawConnections.filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider))];
43
- if (!a.length) { removeSmartRoute(); return; }
44
- a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
45
- const m = a.flatMap(pv => PM[pv] || []);
46
- if (!m.length) { removeSmartRoute(); return; }
47
- const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
48
- const i = db.combos.findIndex(x => x.id === 'smart-route');
49
- if (i >= 0) {
50
- if (JSON.stringify(db.combos[i].models) !== JSON.stringify(c.models)) {
51
- db.combos[i] = c;
52
- fs.writeFileSync(p, JSON.stringify(db, null, 2));
53
- console.log('[sync-combo] Updated smart-route: ' + c.models.length + ' models');
54
- }
55
- } else {
56
- db.combos.push(c);
57
- fs.writeFileSync(p, JSON.stringify(db, null, 2));
58
- console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models');
59
- }
60
- } catch (e) {}
61
- };
62
- setTimeout(sync, 5000);
63
- setInterval(sync, INTERVAL);`;
64
- }
65
-
66
- function build9RouterPatchScript() {
67
- return `const fs=require('fs');const path=require('path');const cp=require('child_process');
68
- const MODELS=${JSON.stringify(SUPPORTED_CODEX_MODELS.map((model) => model.replace('cx/', '')))};
69
- const MODEL_NAMES={"gpt-5.4":"GPT 5.4","gpt-5.4-mini":"GPT 5.4 Mini","gpt-5.3-codex":"GPT 5.3 Codex","gpt-5.2":"GPT 5.2"};
70
- const SELF_TEST_BLOCK=[
71
- 'codex: {',
72
- ' url: "https://chatgpt.com/backend-api/codex/responses",',
73
- ' method: "POST",',
74
- ' authHeader: "Authorization",',
75
- ' authPrefix: "Bearer ",',
76
- ' extraHeaders: { "Content-Type": "application/json", "originator": "codex-cli", "User-Agent": "codex-cli/1.0.18 (macOS; arm64)" },',
77
- ' body: JSON.stringify({',
78
- ' model: "gpt-5.2",',
79
- ' instructions: "You are a coding assistant.",',
80
- ' input: [{ role: "user", content: [{ type: "input_text", text: "Reply with exactly: ok" }] }],',
81
- ' stream: true,',
82
- ' store: false,',
83
- ' }),',
84
- ' acceptStatuses: [200, 400],',
85
- ' refreshable: true,',
86
- ' },'
87
- ].join('\\n');
88
- const roots=new Set();
89
- function add(p){if(p)roots.add(p);}
90
- try{const npmRoot=cp.execSync('npm root -g',{stdio:['ignore','pipe','ignore'],encoding:'utf8'}).trim();if(npmRoot)add(path.join(npmRoot,'9router'));}catch{}
91
- add(path.join(process.env.APPDATA||'','npm','node_modules','9router'));
92
- add('/usr/local/lib/node_modules/9router');
93
- add('/usr/lib/node_modules/9router');
94
- add(path.join(process.cwd(),'node_modules','9router'));
95
- function patchFile(filePath, transform){if(!fs.existsSync(filePath))return false;const before=fs.readFileSync(filePath,'utf8');const after=transform(before);if(!after||after===before)return false;fs.writeFileSync(filePath,after);return true;}
96
- function patchText(text,replacers){let next=text;for(const replacer of replacers){next=replacer(next);}return next===text?null:next;}
97
- function patchProviderModels(root){return patchFile(path.join(root,'open-sse','config','providerModels.js'),(text)=>text.replace(/cx:\\s*\\[[\\s\\S]*?\\],/,()=>{const lines=MODELS.map((id)=>' { id: "'+id+'", name: "'+(MODEL_NAMES[id]||id)+'" },');return 'cx: [ // OpenAI Codex\\n'+lines.join('\\n')+'\\n ],';}));}
98
- function patchCodexLikeFile(filePath){return patchFile(filePath,(text)=>{if(text.includes('max_output_tokens'))return text;return patchText(text,[
99
- (value)=>value.replace(/delete (\\w+)\\.max_tokens,delete \\1\\.user/g,'delete $1.max_tokens,delete $1.max_output_tokens,delete $1.user'),
100
- (value)=>value.replace(/delete (\\w+)\\.max_tokens;(\\s*)delete \\1\\.user/g,'delete $1.max_tokens;$2delete $1.max_output_tokens;$2delete $1.user'),
101
- (value)=>value.replace(' delete body.max_tokens;\\n',' delete body.max_tokens;\\n delete body.max_output_tokens;\\n')
102
- ]);});}
103
- function patchCodexExecutor(root){let touched=0;touched+=patchCodexLikeFile(path.join(root,'open-sse','executors','codex.js'))?1:0;const chunksDir=path.join(root,'app','.next','server','chunks');if(fs.existsSync(chunksDir)){for(const entry of fs.readdirSync(chunksDir)){if(!entry.endsWith('.js'))continue;touched+=patchCodexLikeFile(path.join(chunksDir,entry))?1:0;}}return touched;}
104
- function patchResponsesNullGuard(root){let touched=0;const chunksDir=path.join(root,'app','.next','server','chunks');if(!fs.existsSync(chunksDir))return touched;for(const entry of fs.readdirSync(chunksDir)){if(!entry.endsWith('.js'))continue;touched+=patchFile(path.join(chunksDir,entry),(text)=>patchText(text,[
105
- (value)=>value.replace('let b=a.content.find(a=>"output_text"===a.type);','let b=a.content.find(a=>a&&"output_text"===a.type);'),
106
- (value)=>value.replace('let c=a.content.find(a=>"string"==typeof a.text);','let c=a.content.find(a=>a&&"string"==typeof a.text);'),
107
- (value)=>value.replace('let b=a.filter(a=>a?.type==="message");','let b=a.filter(a=>a&&a?.type==="message");'),
108
- (value)=>value.replace('for(let a of j){let b=a.type||(a.role?"message":null);','for(let a of j){let b=a&&(a.type||(a.role?"message":null));'),
109
- (value)=>value.replace('for(let a of b.messages||[]){if("system"===a.role){','for(let a of b.messages||[])if(a){if("system"===a.role){'),
110
- (value)=>value.replace('let b=Array.isArray(a.content)?a.content.map(a=>"input_text"===a.type||"output_text"===a.type?{type:"text",text:a.text}:"input_image"===a.type?{type:"image_url",image_url:{url:a.image_url||a.file_id||"",detail:a.detail||"auto"}}:a):a.content;','let b=Array.isArray(a.content)?a.content.map(a=>a&&("input_text"===a.type||"output_text"===a.type)?{type:"text",text:a.text}:a&&"input_image"===a.type?{type:"image_url",image_url:{url:a.image_url||a.file_id||"",detail:a.detail||"auto"}}:a).filter(Boolean):a.content;'),
111
- (value)=>value.replace('c="string"==typeof a.content?[{type:b,text:a.content}]:Array.isArray(a.content)?a.content.map(a=>{if("text"===a.type)return{type:b,text:a.text};if("image_url"===a.type)return{type:"input_image",image_url:"string"==typeof a.image_url?a.image_url:a.image_url?.url,detail:a.image_url?.detail||"auto"};if("input_image"===a.type)return a;let c=a.text||a.content||JSON.stringify(a);return{type:b,text:"string"==typeof c?c:JSON.stringify(c)}}):[];','c="string"==typeof a.content?[{type:b,text:a.content}]:Array.isArray(a.content)?a.content.map(a=>{if(!a)return null;if("text"===a.type)return{type:b,text:a.text};if("image_url"===a.type)return{type:"input_image",image_url:"string"==typeof a.image_url?a.image_url:a.image_url?.url,detail:a.image_url?.detail||"auto"};if("input_image"===a.type)return a;let c=a.text||a.content||JSON.stringify(a);return{type:b,text:"string"==typeof c?c:JSON.stringify(c)}}).filter(Boolean):[];'),
112
- (value)=>value.replace('b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>{if(a.function)return a;let b=a.name;return b&&"string"==typeof b&&""!==b.trim()?{type:"function",function:{name:b,description:String(a.description||""),parameters:i(a.parameters),strict:a.strict}}:null}).filter(Boolean))','b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>{if(!a)return null;if(a.function)return a;let b=a.name;return b&&"string"==typeof b&&""!==b.trim()?{type:"function",function:{name:b,description:String(a.description||""),parameters:i(a.parameters),strict:a.strict}}:null}).filter(Boolean))'),
113
- (value)=>value.replace('b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>"function"===a.type?{type:"function",name:a.function.name,description:String(a.function.description||""),parameters:i(a.function.parameters),strict:a.function.strict}:a)),','b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>a&&"function"===a.type?{type:"function",name:a.function.name,description:String(a.function.description||""),parameters:i(a.function.parameters),strict:a.function.strict}:a).filter(Boolean)),'),
114
- (value)=>value.replace('filter(a=>"function_call"===a.type)','filter(a=>a&&"function_call"===a.type)'),
115
- (value)=>value.replace(/filter\\(a=>"text"===a\\.type\\)/g,'filter(a=>a&&"text"===a.type)'),
116
- (value)=>value.replace(/find\\(a=>"message_stop"===a\\.type\\)/g,'find(a=>a&&"message_stop"===a.type)'),
117
- (value)=>value.replace(/find\\(a=>"content_block_delta"===a\\.type\\)/g,'find(a=>a&&"content_block_delta"===a.type)'),
118
- (value)=>value.replace(/find\\(a=>"message_delta"===a\\.type\\)/g,'find(a=>a&&"message_delta"===a.type)'),
119
- (value)=>value.replace(/find\\(a=>"message_start"===a\\.type\\)/g,'find(a=>a&&"message_start"===a.type)'),
120
- (value)=>value.replace(/for\\(let e of a\\.content\\)(?!if\\(e\\))/g,'for(let e of a.content)if(e)')
121
- ] ))?1:0;}return touched;}
122
- function patchSelfTest(root){return patchFile(path.join(root,'src','app','api','providers','[id]','test','testUtils.js'),(text)=>{if(text.includes('model: "gpt-5.2"')&&text.includes('store: false')&&text.includes('acceptStatuses: [200, 400]'))return text;return text.replace(/codex:\\s*\\{[\\s\\S]*?refreshable:\\s*true,\\s*\\},/,SELF_TEST_BLOCK);});}
123
- let touched=0;
124
- for(const root of roots){if(!root||!fs.existsSync(root))continue;touched+=patchProviderModels(root)?1:0;touched+=patchCodexExecutor(root)?1:0;touched+=patchResponsesNullGuard(root)?1:0;touched+=patchSelfTest(root)?1:0;}
125
- if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}else{console.log('[patch-9router] No compatible 9router source files found to patch.');}`;
126
- }
127
-
128
- function build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64) {
129
- const nineRouterSpec = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon && globalThis.__openclawCommon.NINE_ROUTER_NPM_SPEC) || '9router@latest';
130
- return [
131
- `npm install -g ${nineRouterSpec}`,
132
- `node -e "require('fs').writeFileSync('/tmp/patch-9router.js',Buffer.from('${patchScriptBase64}','base64').toString())"`,
133
- `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
134
- 'node /tmp/patch-9router.js || true',
135
- 'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
136
- 'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
137
- ].join('\n');
138
- }
139
-
140
- function buildGatewayPatchCmd() {
141
- 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));}\\"`;
142
- }
143
-
144
- function buildDockerArtifacts(options) {
145
- const {
146
- openClawNpmSpec,
147
- openClawRuntimePackages,
148
- is9Router,
149
- isLocal,
150
- isMultiBot,
151
- hasBrowser,
152
- selectedModel,
153
- agentId,
154
- allSkills = [],
155
- dockerfileSkillInstallMode = 'none',
156
- runtimeCommandParts = [],
157
- volumeMount = '../..:/root/project',
158
- singleComposeName = 'oc-bot',
159
- multiComposeName = 'oc-multibot',
160
- singleAppContainerName = 'openclaw-bot',
161
- multiAppContainerName = 'openclaw-multibot',
162
- singleRouterContainerName = '9router',
163
- multiRouterContainerName = '9router-multibot',
164
- singleOllamaContainerName = 'ollama',
165
- multiOllamaContainerName = 'ollama-multibot',
166
- plainSingleExtraHosts = false,
167
- multiOllamaNumParallel = 1,
168
- singleOllamaNumParallel = 1,
169
- emitBrowserInstall = true,
170
- } = options;
171
-
172
- const browserAptExtra = hasBrowser ? ' xvfb socat' : '';
173
- const browserInstallLines = hasBrowser && emitBrowserInstall
174
- ? [
175
- '',
176
- '# Browser Automation: Playwright engine (needed for native CDP)',
177
- 'RUN npm install -g agent-browser playwright \\',
178
- ' && npx playwright install chromium --with-deps \\',
179
- ' && ln -f -s /root/.cache/ms-playwright/chromium-*/chrome-linux*/chrome /usr/bin/google-chrome',
180
- '',
181
- ''
182
- ].join('\n')
183
- : '';
184
- const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
185
- ? `\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`
186
- : '';
187
- 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);}"`;
188
-
189
- // Dynamic runtime configuration injection for container internal IPs
190
- 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));}`;
191
- const setupInternalIpB64 = encodeBase64Utf8(setupInternalIpScript);
192
-
193
- const runtimeParts = runtimeCommandParts.filter(Boolean);
194
- runtimeParts.unshift('export OPENCLAW_HOME="$PWD/.openclaw"');
195
- runtimeParts.unshift('export OPENCLAW_STATE_DIR="$PWD/.openclaw"');
196
- runtimeParts.unshift(`node -e 'eval(Buffer.from("${setupInternalIpB64}","base64").toString())'`);
197
- if (hasBrowser) {
198
- runtimeParts.push('socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &');
199
- runtimeParts.push('Xvfb :99 -screen 0 1280x720x24 > /dev/null 2>&1 & DISPLAY=:99 openclaw gateway run');
200
- } else {
201
- runtimeParts.push('openclaw gateway run');
202
- }
203
- const runtimeScript = ['#!/bin/sh', 'set -e', ...runtimeParts].join('\n');
204
- const runtimeScriptB64 = encodeBase64Utf8(runtimeScript);
205
- const dockerfile = `FROM node:22-slim
206
-
207
- RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /var/lib/apt/lists/*
208
- ${browserInstallLines}
209
- ARG OPENCLAW_VER="${openClawNpmSpec}"
210
- ARG CACHE_BUST=""
211
- RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}
212
- ${patchLine}
213
- 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
214
- WORKDIR /root/project
215
-
216
- EXPOSE 18791
217
-
218
- CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
219
-
220
- const syncScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
221
- const syncScriptBase64 = encodeBase64Utf8(syncScript);
222
- const patchScript = build9RouterPatchScript();
223
- const patchScriptBase64 = encodeBase64Utf8(patchScript);
224
- const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64);
225
- const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
226
-
227
- const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/root/project/.openclaw\n';
228
-
229
- let compose;
230
- if (isMultiBot) {
231
- const dependsOn = is9Router
232
- ? ' depends_on:\n - 9router\n'
233
- : isLocal
234
- ? ' depends_on:\n ollama:\n condition: service_healthy\n'
235
- : '';
236
- const extraHosts = hasBrowser ? `${extraHostsBlock}\n` : '';
237
- if (is9Router) {
238
- compose = `name: ${multiComposeName}
239
- services:
240
- ai-bot:
241
- build: .
242
- container_name: ${multiAppContainerName}
243
- restart: always
244
- env_file:
245
- - .env
246
- ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
247
- - ${volumeMount}
248
- ports:
249
- - "18791:18791"
250
-
251
- 9router:
252
- image: node:22-slim
253
- container_name: ${multiRouterContainerName}
254
- restart: always
255
- entrypoint:
256
- - /bin/sh
257
- - -c
258
- - |
259
- ${indentBlock(docker9RouterEntrypointScript, 8)}
260
- environment:
261
- - PORT=20128
262
- - HOSTNAME=0.0.0.0
263
- - CI=true
264
- volumes:
265
- - 9router-data:/root/.9router
266
- ports:
267
- - "20128:20128"
268
-
269
- volumes:
270
- 9router-data:`;
271
- } else if (isLocal) {
272
- const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
273
- compose = `name: ${multiComposeName}
274
- services:
275
- ai-bot:
276
- build: .
277
- container_name: ${multiAppContainerName}
278
- restart: always
279
- env_file:
280
- - .env
281
- ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
282
- - ${volumeMount}
283
- ports:
284
- - "18791:18791"
285
-
286
- ollama:
287
- image: ollama/ollama:latest
288
- container_name: ${multiOllamaContainerName}
289
- restart: always
290
- environment:
291
- - OLLAMA_KEEP_ALIVE=24h
292
- - OLLAMA_NUM_PARALLEL=${multiOllamaNumParallel}
293
- volumes:
294
- - ollama-data:/root/.ollama
295
- entrypoint:
296
- - /bin/sh
297
- - -c
298
- - |
299
- ollama serve &
300
- until ollama list > /dev/null 2>&1; do sleep 1; done
301
- ollama pull ${ollamaModelTag}
302
- wait
303
- healthcheck:
304
- test: ["CMD-SHELL", "ollama list > /dev/null 2>&1"]
305
- interval: 10s
306
- timeout: 5s
307
- retries: 10
308
- start_period: 30s
309
-
310
- volumes:
311
- ollama-data:`;
312
- } else {
313
- compose = `name: ${multiComposeName}
314
- services:
315
- ai-bot:
316
- build: .
317
- container_name: ${multiAppContainerName}
318
- restart: always
319
- env_file:
320
- - .env
321
- ${appEnvironmentBlock}${extraHosts} volumes:
322
- - ${volumeMount}
323
- ports:
324
- - "18791:18791"`;
325
- }
326
- } else if (is9Router) {
327
- compose = `name: ${singleComposeName}
328
- services:
329
- ai-bot:
330
- build: .
331
- container_name: ${singleAppContainerName}
332
- restart: always
333
- env_file:
334
- - .env
335
- depends_on:
336
- - 9router
337
- ${appEnvironmentBlock}${hasBrowser ? `${extraHostsBlock}\n` : ''} volumes:
338
- - ${volumeMount}
339
- ports:
340
- - "18791:18791"
341
-
342
- 9router:
343
- image: node:22-slim
344
- container_name: ${singleRouterContainerName}
345
- restart: always
346
- entrypoint:
347
- - /bin/sh
348
- - -c
349
- - |
350
- ${indentBlock(docker9RouterEntrypointScript, 8)}
351
- environment:
352
- - PORT=20128
353
- - HOSTNAME=0.0.0.0
354
- - CI=true
355
- volumes:
356
- - 9router-data:/root/.9router
357
- ports:
358
- - "20128:20128"
359
-
360
- volumes:
361
- 9router-data:`;
362
- } else if (isLocal) {
363
- const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
364
- compose = `name: ${singleComposeName}
365
- services:
366
- ai-bot:
367
- build: .
368
- container_name: ${singleAppContainerName}
369
- restart: always
370
- env_file: .env
371
- ${appEnvironmentBlock} depends_on:
372
- ollama:
373
- condition: service_healthy
374
- ${hasBrowser ? `${extraHostsBlock}\n` : ''} ports:
375
- - "18791:18791"
376
- volumes:
377
- - ${volumeMount}
378
-
379
- ollama:
380
- image: ollama/ollama:latest
381
- container_name: ${singleOllamaContainerName}
382
- restart: always
383
- environment:
384
- - OLLAMA_KEEP_ALIVE=24h
385
- - OLLAMA_NUM_PARALLEL=${singleOllamaNumParallel}
386
- volumes:
387
- - ollama-data:/root/.ollama
388
- entrypoint:
389
- - /bin/sh
390
- - -c
391
- - |
392
- ollama serve &
393
- until ollama list > /dev/null 2>&1; do sleep 1; done
394
- ollama pull ${ollamaModelTag}
395
- wait
396
- healthcheck:
397
- test: ["CMD-SHELL", "ollama list > /dev/null 2>&1"]
398
- interval: 10s
399
- timeout: 5s
400
- retries: 10
401
- start_period: 30s
402
-
403
- volumes:
404
- ollama-data:`;
405
- } else {
406
- compose = `name: ${singleComposeName}
407
- services:
408
- ai-bot:
409
- build: .
410
- container_name: ${singleAppContainerName}
411
- restart: always
412
- env_file:
413
- - .env
414
- ${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''} volumes:
415
- - ${volumeMount}
416
- ports:
417
- - "18791:18791"`;
418
- }
419
-
420
- return {
421
- dockerfile,
422
- compose,
423
- syncScript,
424
- docker9RouterEntrypointScript,
425
- gatewayPatchCmd: buildGatewayPatchCmd(),
426
- };
427
- }
428
-
429
- root.__openclawDockerGen = {
430
- encodeBase64Utf8,
431
- indentBlock,
432
- build9RouterSmartRouteSyncScript,
433
- build9RouterPatchScript,
434
- build9RouterComposeEntrypointScript,
435
- buildGatewayPatchCmd,
436
- buildDockerArtifacts,
437
- };
438
-
439
- })(typeof globalThis !== 'undefined' ? globalThis : {});
440
- if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globalThis.__openclawDockerGen) {
441
- Object.assign(exports, globalThis.__openclawDockerGen);
442
- }
1
+ // @ts-nocheck
2
+ (function (root) {
3
+ const common = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon) || {};
4
+ const SUPPORTED_CODEX_MODELS = common.SUPPORTED_CODEX_MODELS || ['cx/gpt-5.4', 'cx/gpt-5.3-codex', 'cx/gpt-5.2', 'cx/gpt-5.4-mini'];
5
+ const SMART_ROUTE_PROVIDER_MODELS = common.SMART_ROUTE_PROVIDER_MODELS || { codex: SUPPORTED_CODEX_MODELS };
6
+ const SMART_ROUTE_PROVIDER_ORDER = common.SMART_ROUTE_PROVIDER_ORDER || ['codex'];
7
+
8
+ function encodeBase64Utf8(value) {
9
+ if (typeof Buffer !== 'undefined') {
10
+ return Buffer.from(String(value), 'utf8').toString('base64');
11
+ }
12
+ return btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
13
+ }
14
+
15
+ function indentBlock(text, spaces) {
16
+ const prefix = ' '.repeat(spaces);
17
+ return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
18
+ }
19
+
20
+ function build9RouterSmartRouteSyncScript(dbPath) {
21
+ return `const fs=require('fs');const INTERVAL=30000;const p='${dbPath}';
22
+ const PM=${JSON.stringify(SMART_ROUTE_PROVIDER_MODELS)};
23
+ const PREF=${JSON.stringify(SMART_ROUTE_PROVIDER_ORDER)};
24
+ console.log('[sync-combo] 9Router sync loop started...');
25
+ const sync = async () => {
26
+ try {
27
+ let db = {};
28
+ try { db = JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e){}
29
+ if (!db.combos) db.combos = [];
30
+ const removeSmartRoute = () => {
31
+ const next = db.combos.filter(x => x.id !== 'smart-route');
32
+ if (next.length !== db.combos.length) {
33
+ db.combos = next;
34
+ fs.writeFileSync(p, JSON.stringify(db, null, 2));
35
+ console.log('[sync-combo] Removed smart-route (no active providers)');
36
+ }
37
+ };
38
+ const res = await fetch('http://localhost:20128/api/providers');
39
+ if (!res.ok) { console.log('[sync-combo] API not ready, retrying...'); return; }
40
+ const d = await res.json();
41
+ const rawConnections = Array.isArray(d.connections) ? d.connections : Array.isArray(d.providerConnections) ? d.providerConnections : [];
42
+ const a = [...new Set(rawConnections.filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider))];
43
+ if (!a.length) { removeSmartRoute(); return; }
44
+ a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
45
+ const m = a.flatMap(pv => PM[pv] || []);
46
+ if (!m.length) { removeSmartRoute(); return; }
47
+ const c = { id: 'smart-route', name: 'smart-route', alias: 'smart-route', models: m };
48
+ const i = db.combos.findIndex(x => x.id === 'smart-route');
49
+ if (i >= 0) {
50
+ if (JSON.stringify(db.combos[i].models) !== JSON.stringify(c.models)) {
51
+ db.combos[i] = c;
52
+ fs.writeFileSync(p, JSON.stringify(db, null, 2));
53
+ console.log('[sync-combo] Updated smart-route: ' + c.models.length + ' models');
54
+ }
55
+ } else {
56
+ db.combos.push(c);
57
+ fs.writeFileSync(p, JSON.stringify(db, null, 2));
58
+ console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models');
59
+ }
60
+ } catch (e) {}
61
+ };
62
+ setTimeout(sync, 5000);
63
+ setInterval(sync, INTERVAL);`;
64
+ }
65
+
66
+ function build9RouterPatchScript() {
67
+ return `const fs=require('fs');const path=require('path');const cp=require('child_process');
68
+ const MODELS=${JSON.stringify(SUPPORTED_CODEX_MODELS.map((model) => model.replace('cx/', '')))};
69
+ const MODEL_NAMES={"gpt-5.4":"GPT 5.4","gpt-5.4-mini":"GPT 5.4 Mini","gpt-5.3-codex":"GPT 5.3 Codex","gpt-5.2":"GPT 5.2"};
70
+ const SELF_TEST_BLOCK=[
71
+ 'codex: {',
72
+ ' url: "https://chatgpt.com/backend-api/codex/responses",',
73
+ ' method: "POST",',
74
+ ' authHeader: "Authorization",',
75
+ ' authPrefix: "Bearer ",',
76
+ ' extraHeaders: { "Content-Type": "application/json", "originator": "codex-cli", "User-Agent": "codex-cli/1.0.18 (macOS; arm64)" },',
77
+ ' body: JSON.stringify({',
78
+ ' model: "gpt-5.2",',
79
+ ' instructions: "You are a coding assistant.",',
80
+ ' input: [{ role: "user", content: [{ type: "input_text", text: "Reply with exactly: ok" }] }],',
81
+ ' stream: true,',
82
+ ' store: false,',
83
+ ' }),',
84
+ ' acceptStatuses: [200, 400],',
85
+ ' refreshable: true,',
86
+ ' },'
87
+ ].join('\\n');
88
+ const roots=new Set();
89
+ function add(p){if(p)roots.add(p);}
90
+ try{const npmRoot=cp.execSync('npm root -g',{stdio:['ignore','pipe','ignore'],encoding:'utf8'}).trim();if(npmRoot)add(path.join(npmRoot,'9router'));}catch{}
91
+ add(path.join(process.env.APPDATA||'','npm','node_modules','9router'));
92
+ add('/usr/local/lib/node_modules/9router');
93
+ add('/usr/lib/node_modules/9router');
94
+ add(path.join(process.cwd(),'node_modules','9router'));
95
+ function patchFile(filePath, transform){if(!fs.existsSync(filePath))return false;const before=fs.readFileSync(filePath,'utf8');const after=transform(before);if(!after||after===before)return false;fs.writeFileSync(filePath,after);return true;}
96
+ function patchText(text,replacers){let next=text;for(const replacer of replacers){next=replacer(next);}return next===text?null:next;}
97
+ function patchProviderModels(root){return patchFile(path.join(root,'open-sse','config','providerModels.js'),(text)=>text.replace(/cx:\\s*\\[[\\s\\S]*?\\],/,()=>{const lines=MODELS.map((id)=>' { id: "'+id+'", name: "'+(MODEL_NAMES[id]||id)+'" },');return 'cx: [ // OpenAI Codex\\n'+lines.join('\\n')+'\\n ],';}));}
98
+ function patchCodexLikeFile(filePath){return patchFile(filePath,(text)=>{if(text.includes('max_output_tokens'))return text;return patchText(text,[
99
+ (value)=>value.replace(/delete (\\w+)\\.max_tokens,delete \\1\\.user/g,'delete $1.max_tokens,delete $1.max_output_tokens,delete $1.user'),
100
+ (value)=>value.replace(/delete (\\w+)\\.max_tokens;(\\s*)delete \\1\\.user/g,'delete $1.max_tokens;$2delete $1.max_output_tokens;$2delete $1.user'),
101
+ (value)=>value.replace(' delete body.max_tokens;\\n',' delete body.max_tokens;\\n delete body.max_output_tokens;\\n')
102
+ ]);});}
103
+ function patchCodexExecutor(root){let touched=0;touched+=patchCodexLikeFile(path.join(root,'open-sse','executors','codex.js'))?1:0;const chunksDir=path.join(root,'app','.next','server','chunks');if(fs.existsSync(chunksDir)){for(const entry of fs.readdirSync(chunksDir)){if(!entry.endsWith('.js'))continue;touched+=patchCodexLikeFile(path.join(chunksDir,entry))?1:0;}}return touched;}
104
+ function patchResponsesNullGuard(root){let touched=0;const chunksDir=path.join(root,'app','.next','server','chunks');if(!fs.existsSync(chunksDir))return touched;for(const entry of fs.readdirSync(chunksDir)){if(!entry.endsWith('.js'))continue;touched+=patchFile(path.join(chunksDir,entry),(text)=>patchText(text,[
105
+ (value)=>value.replace('let b=a.content.find(a=>"output_text"===a.type);','let b=a.content.find(a=>a&&"output_text"===a.type);'),
106
+ (value)=>value.replace('let c=a.content.find(a=>"string"==typeof a.text);','let c=a.content.find(a=>a&&"string"==typeof a.text);'),
107
+ (value)=>value.replace('let b=a.filter(a=>a?.type==="message");','let b=a.filter(a=>a&&a?.type==="message");'),
108
+ (value)=>value.replace('for(let a of j){let b=a.type||(a.role?"message":null);','for(let a of j){let b=a&&(a.type||(a.role?"message":null));'),
109
+ (value)=>value.replace('for(let a of b.messages||[]){if("system"===a.role){','for(let a of b.messages||[])if(a){if("system"===a.role){'),
110
+ (value)=>value.replace('let b=Array.isArray(a.content)?a.content.map(a=>"input_text"===a.type||"output_text"===a.type?{type:"text",text:a.text}:"input_image"===a.type?{type:"image_url",image_url:{url:a.image_url||a.file_id||"",detail:a.detail||"auto"}}:a):a.content;','let b=Array.isArray(a.content)?a.content.map(a=>a&&("input_text"===a.type||"output_text"===a.type)?{type:"text",text:a.text}:a&&"input_image"===a.type?{type:"image_url",image_url:{url:a.image_url||a.file_id||"",detail:a.detail||"auto"}}:a).filter(Boolean):a.content;'),
111
+ (value)=>value.replace('c="string"==typeof a.content?[{type:b,text:a.content}]:Array.isArray(a.content)?a.content.map(a=>{if("text"===a.type)return{type:b,text:a.text};if("image_url"===a.type)return{type:"input_image",image_url:"string"==typeof a.image_url?a.image_url:a.image_url?.url,detail:a.image_url?.detail||"auto"};if("input_image"===a.type)return a;let c=a.text||a.content||JSON.stringify(a);return{type:b,text:"string"==typeof c?c:JSON.stringify(c)}}):[];','c="string"==typeof a.content?[{type:b,text:a.content}]:Array.isArray(a.content)?a.content.map(a=>{if(!a)return null;if("text"===a.type)return{type:b,text:a.text};if("image_url"===a.type)return{type:"input_image",image_url:"string"==typeof a.image_url?a.image_url:a.image_url?.url,detail:a.image_url?.detail||"auto"};if("input_image"===a.type)return a;let c=a.text||a.content||JSON.stringify(a);return{type:b,text:"string"==typeof c?c:JSON.stringify(c)}}).filter(Boolean):[];'),
112
+ (value)=>value.replace('b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>{if(a.function)return a;let b=a.name;return b&&"string"==typeof b&&""!==b.trim()?{type:"function",function:{name:b,description:String(a.description||""),parameters:i(a.parameters),strict:a.strict}}:null}).filter(Boolean))','b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>{if(!a)return null;if(a.function)return a;let b=a.name;return b&&"string"==typeof b&&""!==b.trim()?{type:"function",function:{name:b,description:String(a.description||""),parameters:i(a.parameters),strict:a.strict}}:null}).filter(Boolean))'),
113
+ (value)=>value.replace('b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>"function"===a.type?{type:"function",name:a.function.name,description:String(a.function.description||""),parameters:i(a.function.parameters),strict:a.function.strict}:a)),','b.tools&&Array.isArray(b.tools)&&(e.tools=b.tools.map(a=>a&&"function"===a.type?{type:"function",name:a.function.name,description:String(a.function.description||""),parameters:i(a.function.parameters),strict:a.function.strict}:a).filter(Boolean)),'),
114
+ (value)=>value.replace('filter(a=>"function_call"===a.type)','filter(a=>a&&"function_call"===a.type)'),
115
+ (value)=>value.replace(/filter\\(a=>"text"===a\\.type\\)/g,'filter(a=>a&&"text"===a.type)'),
116
+ (value)=>value.replace(/find\\(a=>"message_stop"===a\\.type\\)/g,'find(a=>a&&"message_stop"===a.type)'),
117
+ (value)=>value.replace(/find\\(a=>"content_block_delta"===a\\.type\\)/g,'find(a=>a&&"content_block_delta"===a.type)'),
118
+ (value)=>value.replace(/find\\(a=>"message_delta"===a\\.type\\)/g,'find(a=>a&&"message_delta"===a.type)'),
119
+ (value)=>value.replace(/find\\(a=>"message_start"===a\\.type\\)/g,'find(a=>a&&"message_start"===a.type)'),
120
+ (value)=>value.replace(/for\\(let e of a\\.content\\)(?!if\\(e\\))/g,'for(let e of a.content)if(e)')
121
+ ] ))?1:0;}return touched;}
122
+ function patchSelfTest(root){return patchFile(path.join(root,'src','app','api','providers','[id]','test','testUtils.js'),(text)=>{if(text.includes('model: "gpt-5.2"')&&text.includes('store: false')&&text.includes('acceptStatuses: [200, 400]'))return text;return text.replace(/codex:\\s*\\{[\\s\\S]*?refreshable:\\s*true,\\s*\\},/,SELF_TEST_BLOCK);});}
123
+ let touched=0;
124
+ for(const root of roots){if(!root||!fs.existsSync(root))continue;touched+=patchProviderModels(root)?1:0;touched+=patchCodexExecutor(root)?1:0;touched+=patchResponsesNullGuard(root)?1:0;touched+=patchSelfTest(root)?1:0;}
125
+ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}else{console.log('[patch-9router] No compatible 9router source files found to patch.');}`;
126
+ }
127
+
128
+ function build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64) {
129
+ const nineRouterSpec = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon && globalThis.__openclawCommon.NINE_ROUTER_NPM_SPEC) || '9router@latest';
130
+ return [
131
+ `npm install -g ${nineRouterSpec}`,
132
+ `node -e "require('fs').writeFileSync('/tmp/patch-9router.js',Buffer.from('${patchScriptBase64}','base64').toString())"`,
133
+ `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
134
+ 'node /tmp/patch-9router.js || true',
135
+ 'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
136
+ 'exec 9router -n -l -H 0.0.0.0 -p 20128 --skip-update'
137
+ ].join('\n');
138
+ }
139
+
140
+ function buildGatewayPatchCmd() {
141
+ 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));}\\"`;
142
+ }
143
+
144
+ function buildDockerArtifacts(options) {
145
+ const {
146
+ openClawNpmSpec,
147
+ openClawRuntimePackages,
148
+ is9Router,
149
+ isLocal,
150
+ isMultiBot,
151
+ hasBrowser,
152
+ selectedModel,
153
+ agentId,
154
+ allSkills = [],
155
+ dockerfileSkillInstallMode = 'none',
156
+ runtimeCommandParts = [],
157
+ volumeMount = '../..:/root/project',
158
+ singleComposeName = 'oc-bot',
159
+ multiComposeName = 'oc-multibot',
160
+ singleAppContainerName = 'openclaw-bot',
161
+ multiAppContainerName = 'openclaw-multibot',
162
+ singleRouterContainerName = '9router',
163
+ multiRouterContainerName = '9router-multibot',
164
+ singleOllamaContainerName = 'ollama',
165
+ multiOllamaContainerName = 'ollama-multibot',
166
+ plainSingleExtraHosts = false,
167
+ multiOllamaNumParallel = 1,
168
+ singleOllamaNumParallel = 1,
169
+ emitBrowserInstall = true,
170
+
171
+ } = options;
172
+
173
+ const browserAptExtra = hasBrowser ? ' xvfb socat' : '';
174
+ const browserInstallLines = hasBrowser && emitBrowserInstall
175
+ ? [
176
+ '',
177
+ '# Browser Automation: Playwright engine (needed for native CDP)',
178
+ 'RUN npm install -g agent-browser playwright \\',
179
+ ' && npx playwright install chromium --with-deps \\',
180
+ ' && ln -f -s /root/.cache/ms-playwright/chromium-*/chrome-linux*/chrome /usr/bin/google-chrome',
181
+ '',
182
+ ''
183
+ ].join('\n')
184
+ : '';
185
+ const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
186
+ ? `\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`
187
+ : '';
188
+ 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);}"`;
189
+
190
+ // Dynamic runtime configuration injection for container internal IPs
191
+ 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));}`;
192
+ const setupInternalIpB64 = encodeBase64Utf8(setupInternalIpScript);
193
+
194
+ const runtimeParts = runtimeCommandParts.filter(Boolean);
195
+ runtimeParts.unshift('export OPENCLAW_HOME="$PWD/.openclaw"');
196
+ runtimeParts.unshift('export OPENCLAW_STATE_DIR="$PWD/.openclaw"');
197
+ runtimeParts.unshift(`node -e 'eval(Buffer.from("${setupInternalIpB64}","base64").toString())'`);
198
+ if (hasBrowser) {
199
+ runtimeParts.push('socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &');
200
+ runtimeParts.push('Xvfb :99 -screen 0 1280x720x24 > /dev/null 2>&1 & DISPLAY=:99 openclaw gateway run');
201
+ } else {
202
+ runtimeParts.push('openclaw gateway run');
203
+ }
204
+ const runtimeScript = ['#!/bin/sh', 'set -e', ...runtimeParts].join('\n');
205
+ const runtimeScriptB64 = encodeBase64Utf8(runtimeScript);
206
+ const dockerfile = `FROM node:22-slim
207
+
208
+ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /var/lib/apt/lists/*
209
+ ${browserInstallLines}
210
+ ARG OPENCLAW_VER="${openClawNpmSpec}"
211
+ ARG CACHE_BUST=""
212
+ RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}
213
+ ${patchLine}
214
+ 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
215
+ WORKDIR /root/project
216
+
217
+ EXPOSE 18791
218
+
219
+ CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
220
+
221
+ const syncScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
222
+ const syncScriptBase64 = encodeBase64Utf8(syncScript);
223
+ const patchScript = build9RouterPatchScript();
224
+ const patchScriptBase64 = encodeBase64Utf8(patchScript);
225
+ const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64);
226
+ const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
227
+
228
+ const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/root/project/.openclaw\n';
229
+
230
+ let compose;
231
+ if (isMultiBot) {
232
+ const dependsOn = is9Router
233
+ ? ' depends_on:\n - 9router\n'
234
+ : isLocal
235
+ ? ' depends_on:\n ollama:\n condition: service_healthy\n'
236
+ : '';
237
+ const extraHosts = hasBrowser ? `${extraHostsBlock}\n` : '';
238
+ if (is9Router) {
239
+ compose = `name: ${multiComposeName}
240
+ services:
241
+ ai-bot:
242
+ build: .
243
+ container_name: ${multiAppContainerName}
244
+ restart: always
245
+ env_file:
246
+ - .env
247
+ ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
248
+ - ${volumeMount}
249
+ ports:
250
+ - "18791:18791"
251
+
252
+ 9router:
253
+ image: node:22-slim
254
+ container_name: ${multiRouterContainerName}
255
+ restart: always
256
+ entrypoint:
257
+ - /bin/sh
258
+ - -c
259
+ - |
260
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
261
+ environment:
262
+ - PORT=20128
263
+ - HOSTNAME=0.0.0.0
264
+ - CI=true
265
+ volumes:
266
+ - 9router-data:/root/.9router
267
+ ports:
268
+ - "20128:20128"
269
+
270
+ volumes:
271
+ 9router-data:`;
272
+ } else if (isLocal) {
273
+ const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
274
+ compose = `name: ${multiComposeName}
275
+ services:
276
+ ai-bot:
277
+ build: .
278
+ container_name: ${multiAppContainerName}
279
+ restart: always
280
+ env_file:
281
+ - .env
282
+ ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
283
+ - ${volumeMount}
284
+ ports:
285
+ - "18791:18791"
286
+
287
+ ollama:
288
+ image: ollama/ollama:latest
289
+ container_name: ${multiOllamaContainerName}
290
+ restart: always
291
+ environment:
292
+ - OLLAMA_KEEP_ALIVE=24h
293
+ - OLLAMA_NUM_PARALLEL=${multiOllamaNumParallel}
294
+ volumes:
295
+ - ollama-data:/root/.ollama
296
+ entrypoint:
297
+ - /bin/sh
298
+ - -c
299
+ - |
300
+ ollama serve &
301
+ until ollama list > /dev/null 2>&1; do sleep 1; done
302
+ ollama pull ${ollamaModelTag}
303
+ wait
304
+ healthcheck:
305
+ test: ["CMD-SHELL", "ollama list > /dev/null 2>&1"]
306
+ interval: 10s
307
+ timeout: 5s
308
+ retries: 10
309
+ start_period: 30s
310
+
311
+ volumes:
312
+ ollama-data:`;
313
+ } else {
314
+ compose = `name: ${multiComposeName}
315
+ services:
316
+ ai-bot:
317
+ build: .
318
+ container_name: ${multiAppContainerName}
319
+ restart: always
320
+ env_file:
321
+ - .env
322
+ ${appEnvironmentBlock}${extraHosts} volumes:
323
+ - ${volumeMount}
324
+ ports:
325
+ - "18791:18791"`;
326
+ }
327
+ } else if (is9Router) {
328
+ compose = `name: ${singleComposeName}
329
+ services:
330
+ ai-bot:
331
+ build: .
332
+ container_name: ${singleAppContainerName}
333
+ restart: always
334
+ env_file:
335
+ - .env
336
+ depends_on:
337
+ - 9router
338
+ ${appEnvironmentBlock}${hasBrowser ? `${extraHostsBlock}\n` : ''} volumes:
339
+ - ${volumeMount}
340
+ ports:
341
+ - "18791:18791"
342
+
343
+ 9router:
344
+ image: node:22-slim
345
+ container_name: ${singleRouterContainerName}
346
+ restart: always
347
+ entrypoint:
348
+ - /bin/sh
349
+ - -c
350
+ - |
351
+ ${indentBlock(docker9RouterEntrypointScript, 8)}
352
+ environment:
353
+ - PORT=20128
354
+ - HOSTNAME=0.0.0.0
355
+ - CI=true
356
+ volumes:
357
+ - 9router-data:/root/.9router
358
+ ports:
359
+ - "20128:20128"
360
+
361
+ volumes:
362
+ 9router-data:`;
363
+ } else if (isLocal) {
364
+ const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
365
+ compose = `name: ${singleComposeName}
366
+ services:
367
+ ai-bot:
368
+ build: .
369
+ container_name: ${singleAppContainerName}
370
+ restart: always
371
+ env_file: .env
372
+ ${appEnvironmentBlock} depends_on:
373
+ ollama:
374
+ condition: service_healthy
375
+ ${hasBrowser ? `${extraHostsBlock}\n` : ''} ports:
376
+ - "18791:18791"
377
+ volumes:
378
+ - ${volumeMount}
379
+
380
+ ollama:
381
+ image: ollama/ollama:latest
382
+ container_name: ${singleOllamaContainerName}
383
+ restart: always
384
+ environment:
385
+ - OLLAMA_KEEP_ALIVE=24h
386
+ - OLLAMA_NUM_PARALLEL=${singleOllamaNumParallel}
387
+ volumes:
388
+ - ollama-data:/root/.ollama
389
+ entrypoint:
390
+ - /bin/sh
391
+ - -c
392
+ - |
393
+ ollama serve &
394
+ until ollama list > /dev/null 2>&1; do sleep 1; done
395
+ ollama pull ${ollamaModelTag}
396
+ wait
397
+ healthcheck:
398
+ test: ["CMD-SHELL", "ollama list > /dev/null 2>&1"]
399
+ interval: 10s
400
+ timeout: 5s
401
+ retries: 10
402
+ start_period: 30s
403
+
404
+ volumes:
405
+ ollama-data:`;
406
+ } else {
407
+ compose = `name: ${singleComposeName}
408
+ services:
409
+ ai-bot:
410
+ build: .
411
+ container_name: ${singleAppContainerName}
412
+ restart: always
413
+ env_file:
414
+ - .env
415
+ ${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''} volumes:
416
+ - ${volumeMount}
417
+ ports:
418
+ - "18791:18791"`;
419
+ }
420
+
421
+ return {
422
+ dockerfile,
423
+ compose,
424
+ syncScript,
425
+ docker9RouterEntrypointScript,
426
+ gatewayPatchCmd: buildGatewayPatchCmd(),
427
+ };
428
+ }
429
+
430
+ root.__openclawDockerGen = {
431
+ encodeBase64Utf8,
432
+ indentBlock,
433
+ build9RouterSmartRouteSyncScript,
434
+ build9RouterPatchScript,
435
+ build9RouterComposeEntrypointScript,
436
+ buildGatewayPatchCmd,
437
+ buildDockerArtifacts,
438
+ };
439
+
440
+ })(typeof globalThis !== 'undefined' ? globalThis : {});
441
+ if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globalThis.__openclawDockerGen) {
442
+ Object.assign(exports, globalThis.__openclawDockerGen);
443
+ }