@shadowob/cloud 1.1.6-dev.311

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.
Files changed (334) hide show
  1. package/README.md +509 -0
  2. package/dist/agent-browser-CERTMCDL.js +117 -0
  3. package/dist/agent-browser-CIRZRIY4.js +118 -0
  4. package/dist/agent-pack-LF3O5TR4.js +1236 -0
  5. package/dist/agent-pack-RQT27V7R.js +1235 -0
  6. package/dist/airtable-BG2Q75G2.js +82 -0
  7. package/dist/airtable-JCQXFM5D.js +83 -0
  8. package/dist/alipay-MZX2XCDB.js +52 -0
  9. package/dist/alipay-TZQI34RB.js +51 -0
  10. package/dist/amap-5RQB3VGC.js +45 -0
  11. package/dist/amap-KPCLZYYL.js +44 -0
  12. package/dist/atlassian-LGOEWYC7.js +54 -0
  13. package/dist/atlassian-TVS2A4IU.js +55 -0
  14. package/dist/baidu-appbuilder-6UMESXHW.js +41 -0
  15. package/dist/baidu-appbuilder-QRRL3ETM.js +42 -0
  16. package/dist/baidu-maps-HEPMVP5D.js +44 -0
  17. package/dist/baidu-maps-HXC4FBVP.js +45 -0
  18. package/dist/baidu-netdisk-G5Q6B5NH.js +45 -0
  19. package/dist/baidu-netdisk-RS2K5W2M.js +44 -0
  20. package/dist/baidu-smartprogram-EWTK5WKK.js +41 -0
  21. package/dist/baidu-smartprogram-JHD3XWF6.js +40 -0
  22. package/dist/browserbase-IUIYVYI7.js +67 -0
  23. package/dist/browserbase-JFO2PCIA.js +68 -0
  24. package/dist/canva-3YOFL7JS.js +62 -0
  25. package/dist/canva-FMYN65SM.js +61 -0
  26. package/dist/chunk-6P2K6QZR.js +529 -0
  27. package/dist/chunk-7VMRQ7MG.js +90 -0
  28. package/dist/chunk-AD3JTIU3.js +17 -0
  29. package/dist/chunk-BF6CV2Y4.js +64 -0
  30. package/dist/chunk-CTNUKOQE.js +439 -0
  31. package/dist/chunk-EEFMJYKB.js +97 -0
  32. package/dist/chunk-EJKFQ35I.js +739 -0
  33. package/dist/chunk-HUICDC56.js +62 -0
  34. package/dist/chunk-JUPAE5IA.js +527 -0
  35. package/dist/chunk-JY2HTT7Q.js +437 -0
  36. package/dist/chunk-KEPTCLUO.js +121 -0
  37. package/dist/chunk-KKK5H7YX.js +3622 -0
  38. package/dist/chunk-POSVEKIY.js +210 -0
  39. package/dist/chunk-QET4LT4J.js +5769 -0
  40. package/dist/chunk-QV4XWO3P.js +30 -0
  41. package/dist/chunk-R52J3PH2.js +120 -0
  42. package/dist/chunk-R5U7XKVJ.js +16 -0
  43. package/dist/chunk-RECNVWMT.js +212 -0
  44. package/dist/chunk-RTPBU5HF.js +92 -0
  45. package/dist/chunk-SUZ2ATT6.js +5774 -0
  46. package/dist/chunk-SVMXSIMG.js +98 -0
  47. package/dist/chunk-TV3CBM7R.js +28 -0
  48. package/dist/chunk-V2LU736V.js +3495 -0
  49. package/dist/chunk-ZUYL3W53.js +741 -0
  50. package/dist/claude-plugin-577TAQVS.js +1463 -0
  51. package/dist/claude-plugin-L3MXJJ6J.js +1464 -0
  52. package/dist/cli.js +7021 -0
  53. package/dist/cloudflare-HBBABPK6.js +114 -0
  54. package/dist/cloudflare-RDFPKMM5.js +115 -0
  55. package/dist/cnb-FLP3QX46.js +44 -0
  56. package/dist/cnb-YAVVEYFB.js +45 -0
  57. package/dist/console/index.html +12 -0
  58. package/dist/console/logo.png +0 -0
  59. package/dist/console/static/css/5079.f9e0918d.css +1 -0
  60. package/dist/console/static/css/index.7f91f806.css +1 -0
  61. package/dist/console/static/font/codicon.5b7d6fac.ttf +0 -0
  62. package/dist/console/static/js/5079.72a51ca2.js +699 -0
  63. package/dist/console/static/js/5079.72a51ca2.js.LICENSE.txt +35 -0
  64. package/dist/console/static/js/7426.f8d483ea.js +1 -0
  65. package/dist/console/static/js/async/1008.4df521b7.js +1 -0
  66. package/dist/console/static/js/async/102.1d473ec7.js +1 -0
  67. package/dist/console/static/js/async/1134.3f9fd9e7.js +1 -0
  68. package/dist/console/static/js/async/1318.4b8e48e7.js +1 -0
  69. package/dist/console/static/js/async/1360.5606da88.js +7 -0
  70. package/dist/console/static/js/async/1546.045f484f.js +1 -0
  71. package/dist/console/static/js/async/1562.187de2a8.js +1 -0
  72. package/dist/console/static/js/async/168.456d4813.js +1 -0
  73. package/dist/console/static/js/async/1750.e6dc2664.js +1 -0
  74. package/dist/console/static/js/async/1994.3fc86066.js +1 -0
  75. package/dist/console/static/js/async/2348.613ae3d9.js +1 -0
  76. package/dist/console/static/js/async/2390.1b890b9d.js +1 -0
  77. package/dist/console/static/js/async/2414.9d040212.js +1 -0
  78. package/dist/console/static/js/async/2454.4c1784ab.js +1 -0
  79. package/dist/console/static/js/async/2498.f5f92030.js +1 -0
  80. package/dist/console/static/js/async/2924.b823cd1a.js +1 -0
  81. package/dist/console/static/js/async/3062.63fddea6.js +1 -0
  82. package/dist/console/static/js/async/3078.dd712008.js +1 -0
  83. package/dist/console/static/js/async/3198.1f307065.js +1 -0
  84. package/dist/console/static/js/async/3246.3d5a899f.js +1 -0
  85. package/dist/console/static/js/async/3286.871676eb.js +1 -0
  86. package/dist/console/static/js/async/342.10bf3b90.js +1 -0
  87. package/dist/console/static/js/async/3446.9681a4d7.js +1 -0
  88. package/dist/console/static/js/async/3698.ccfaabec.js +1 -0
  89. package/dist/console/static/js/async/3790.2a1106a6.js +1 -0
  90. package/dist/console/static/js/async/4231.b29784d4.js +1 -0
  91. package/dist/console/static/js/async/4551.515bd41d.js +1 -0
  92. package/dist/console/static/js/async/4596.40f6e71b.js +1 -0
  93. package/dist/console/static/js/async/4600.4aaebe6d.js +1 -0
  94. package/dist/console/static/js/async/4718.1aae022f.js +1 -0
  95. package/dist/console/static/js/async/4846.a347c020.js +1 -0
  96. package/dist/console/static/js/async/4860.83dadf89.js +1 -0
  97. package/dist/console/static/js/async/500.fcfa37cb.js +1 -0
  98. package/dist/console/static/js/async/5096.b360203d.js +1 -0
  99. package/dist/console/static/js/async/5222.043274fe.js +1 -0
  100. package/dist/console/static/js/async/5362.f498001c.js +1 -0
  101. package/dist/console/static/js/async/54.c94f0755.js +1 -0
  102. package/dist/console/static/js/async/5478.50dd9ef0.js +2 -0
  103. package/dist/console/static/js/async/5478.50dd9ef0.js.LICENSE.txt +3 -0
  104. package/dist/console/static/js/async/5507.a6a1f793.js +1 -0
  105. package/dist/console/static/js/async/5638.bc6b102d.js +1 -0
  106. package/dist/console/static/js/async/5722.e0029049.js +1 -0
  107. package/dist/console/static/js/async/5942.74635c6b.js +1 -0
  108. package/dist/console/static/js/async/5994.1c5629c1.js +1 -0
  109. package/dist/console/static/js/async/6054.6bddf720.js +1 -0
  110. package/dist/console/static/js/async/6118.45e754e5.js +1 -0
  111. package/dist/console/static/js/async/6127.adcbcbb6.js +1 -0
  112. package/dist/console/static/js/async/614.3f434c20.js +1 -0
  113. package/dist/console/static/js/async/6234.ba3b002d.js +1 -0
  114. package/dist/console/static/js/async/6310.6546a9ba.js +1 -0
  115. package/dist/console/static/js/async/6378.9f805419.js +1 -0
  116. package/dist/console/static/js/async/6380.e4433c49.js +1 -0
  117. package/dist/console/static/js/async/6418.f23bcfda.js +1 -0
  118. package/dist/console/static/js/async/6428.77c86114.js +1 -0
  119. package/dist/console/static/js/async/6443.83318a6c.js +1 -0
  120. package/dist/console/static/js/async/6508.2b445d62.js +3 -0
  121. package/dist/console/static/js/async/6542.e82a26c8.js +1 -0
  122. package/dist/console/static/js/async/6544.62111ecb.js +1 -0
  123. package/dist/console/static/js/async/6612.a0c9fcf4.js +1 -0
  124. package/dist/console/static/js/async/6740.695aebf0.js +1 -0
  125. package/dist/console/static/js/async/6822.dbbb32bc.js +1 -0
  126. package/dist/console/static/js/async/6824.ad3540ab.js +1 -0
  127. package/dist/console/static/js/async/6930.585dab94.js +1 -0
  128. package/dist/console/static/js/async/6982.c81b95e6.js +1 -0
  129. package/dist/console/static/js/async/7046.ab2378c1.js +1 -0
  130. package/dist/console/static/js/async/7110.a603277f.js +1 -0
  131. package/dist/console/static/js/async/7142.4a21366f.js +1 -0
  132. package/dist/console/static/js/async/7348.15cc6148.js +1373 -0
  133. package/dist/console/static/js/async/7348.15cc6148.js.LICENSE.txt +14 -0
  134. package/dist/console/static/js/async/7374.b1ac5c44.js +1 -0
  135. package/dist/console/static/js/async/742.847f17ca.js +1 -0
  136. package/dist/console/static/js/async/7446.743954d8.js +1 -0
  137. package/dist/console/static/js/async/7673.59bbbaac.js +1 -0
  138. package/dist/console/static/js/async/7684.c5760c8c.js +1 -0
  139. package/dist/console/static/js/async/7714.c30d0f94.js +1 -0
  140. package/dist/console/static/js/async/8118.36d5a3bf.js +298 -0
  141. package/dist/console/static/js/async/8145.4bcf043a.js +1 -0
  142. package/dist/console/static/js/async/8246.408de938.js +1 -0
  143. package/dist/console/static/js/async/8390.bdae1f7d.js +1 -0
  144. package/dist/console/static/js/async/8422.fd94dbe1.js +1 -0
  145. package/dist/console/static/js/async/8434.94a0e2ae.js +1 -0
  146. package/dist/console/static/js/async/8518.3158de13.js +1 -0
  147. package/dist/console/static/js/async/8564.fc2eb841.js +1 -0
  148. package/dist/console/static/js/async/8678.73af4c9b.js +1 -0
  149. package/dist/console/static/js/async/8694.79747168.js +1 -0
  150. package/dist/console/static/js/async/8756.1de37b83.js +1 -0
  151. package/dist/console/static/js/async/8804.7fe6bdf9.js +3 -0
  152. package/dist/console/static/js/async/8883.e717ee94.js +1 -0
  153. package/dist/console/static/js/async/8886.fe6e876c.js +1 -0
  154. package/dist/console/static/js/async/9030.fc1ae402.js +1 -0
  155. package/dist/console/static/js/async/9094.5598d084.js +1 -0
  156. package/dist/console/static/js/async/9218.ee7b84b7.js +1 -0
  157. package/dist/console/static/js/async/94.9b80bc35.js +1 -0
  158. package/dist/console/static/js/async/9526.92aba34c.js +1 -0
  159. package/dist/console/static/js/async/9762.f83bc4f3.js +1 -0
  160. package/dist/console/static/js/async/984.e11c113a.js +1 -0
  161. package/dist/console/static/js/async/9846.246653cd.js +1 -0
  162. package/dist/console/static/js/index.4487e1ff.js +1 -0
  163. package/dist/console/static/js/lib-react.15d7ca9a.js +2 -0
  164. package/dist/console/static/js/lib-react.15d7ca9a.js.LICENSE.txt +49 -0
  165. package/dist/coze-C6PMDPBI.js +49 -0
  166. package/dist/coze-E6VGRNLV.js +48 -0
  167. package/dist/dashboard.command-J7XOZNXU.js +8 -0
  168. package/dist/dashboard.command-RV2NHDKW.js +7 -0
  169. package/dist/dingtalk-JNRNRN7E.js +77 -0
  170. package/dist/dingtalk-WZGGIAHJ.js +76 -0
  171. package/dist/douyin-miniprogram-AIJPPIZH.js +41 -0
  172. package/dist/douyin-miniprogram-HCYZ5NBW.js +42 -0
  173. package/dist/figma-2YYNSCDX.js +103 -0
  174. package/dist/figma-RYOBMENP.js +102 -0
  175. package/dist/firebase-2IJDDBXX.js +112 -0
  176. package/dist/firebase-OYSY6HPT.js +111 -0
  177. package/dist/firecrawl-2T3SBUW7.js +66 -0
  178. package/dist/firecrawl-IYYXLAZM.js +65 -0
  179. package/dist/flyai-7FJ4TRAG.js +81 -0
  180. package/dist/flyai-QS5Q6FJR.js +82 -0
  181. package/dist/gitagent-MWI75OIX.js +725 -0
  182. package/dist/gitagent-YBMWY7NZ.js +726 -0
  183. package/dist/gitee-3N7OFOM7.js +53 -0
  184. package/dist/gitee-KVNK6KLZ.js +54 -0
  185. package/dist/github-LUEC2LID.js +143 -0
  186. package/dist/github-XRO5Z3GC.js +142 -0
  187. package/dist/google-ads-A3QAJI4D.js +88 -0
  188. package/dist/google-ads-VPKWTX67.js +89 -0
  189. package/dist/google-analytics-C4UR5ZR2.js +50 -0
  190. package/dist/google-analytics-XDYZA2B7.js +49 -0
  191. package/dist/google-workspace-LL3EWVHH.js +320 -0
  192. package/dist/google-workspace-YX35SHHX.js +321 -0
  193. package/dist/huawei-xiaoyi-6BSMGJHR.js +40 -0
  194. package/dist/huawei-xiaoyi-KPWLTSHB.js +41 -0
  195. package/dist/hubspot-DIUHGEDI.js +45 -0
  196. package/dist/hubspot-FTIEMNZO.js +44 -0
  197. package/dist/huggingface-MJCOXA7E.js +116 -0
  198. package/dist/huggingface-UUXK2RHK.js +117 -0
  199. package/dist/index.d.ts +3013 -0
  200. package/dist/index.js +15649 -0
  201. package/dist/inference-ai-image-generation-CMI6R5T3.js +106 -0
  202. package/dist/inference-ai-image-generation-PXV6IG4U.js +107 -0
  203. package/dist/inference-sh-7AZOLEFI.js +94 -0
  204. package/dist/inference-sh-ABQOD3YF.js +95 -0
  205. package/dist/init.command-6E24K4H3.js +9 -0
  206. package/dist/init.command-O4HG4HKR.js +10 -0
  207. package/dist/klaviyo-6K5YEFNH.js +45 -0
  208. package/dist/klaviyo-LDPBWBSS.js +44 -0
  209. package/dist/kuaidi100-HGFM5VK2.js +42 -0
  210. package/dist/kuaidi100-UHPFCVXP.js +41 -0
  211. package/dist/lark-6LNA3LUQ.js +103 -0
  212. package/dist/lark-URVBZNS4.js +102 -0
  213. package/dist/linear-7QFSFPOD.js +57 -0
  214. package/dist/linear-T4ORUP7N.js +56 -0
  215. package/dist/lovart-PDUXRUHJ.js +99 -0
  216. package/dist/lovart-QO3SK55T.js +100 -0
  217. package/dist/meta-ads-SCNFI45S.js +42 -0
  218. package/dist/meta-ads-V6XPZWX3.js +41 -0
  219. package/dist/miclaw-5CNTW7VV.js +36 -0
  220. package/dist/miclaw-TPPPS2WN.js +35 -0
  221. package/dist/model-provider-AVSFJSZP.js +393 -0
  222. package/dist/model-provider-KFB76XV5.js +392 -0
  223. package/dist/notion-FZK76MN2.js +69 -0
  224. package/dist/notion-WFA7KGZZ.js +70 -0
  225. package/dist/oceanengine-3JZUS3PP.js +43 -0
  226. package/dist/oceanengine-5BRIJVJE.js +42 -0
  227. package/dist/opencli-PFXHGCS2.js +81 -0
  228. package/dist/opencli-VIGRJTGH.js +80 -0
  229. package/dist/paypal-33UADIPR.js +54 -0
  230. package/dist/paypal-Z5JYHIWD.js +55 -0
  231. package/dist/playwright-MG5WHK47.js +58 -0
  232. package/dist/playwright-SQAQ3DZG.js +59 -0
  233. package/dist/plugins-HZBWK3WQ.js +120 -0
  234. package/dist/plugins-I4GD5SZX.js +121 -0
  235. package/dist/posthog-MU5MAJOQ.js +79 -0
  236. package/dist/posthog-RJRRKDWB.js +80 -0
  237. package/dist/salesforce-34FVIJTG.js +82 -0
  238. package/dist/salesforce-3QZ6OFVO.js +83 -0
  239. package/dist/sentry-MCIRMACU.js +111 -0
  240. package/dist/sentry-PIWW46VA.js +110 -0
  241. package/dist/seo-suite-4SQ3I67Q.js +54 -0
  242. package/dist/seo-suite-WJXMA3S4.js +55 -0
  243. package/dist/serve.command-5FMIPQRY.js +10 -0
  244. package/dist/serve.command-DNE6GPMK.js +9 -0
  245. package/dist/shadowob-JELOWHWX.js +1068 -0
  246. package/dist/shadowob-PRSMI5MW.js +1069 -0
  247. package/dist/sherlock-2PKY2E2Y.js +66 -0
  248. package/dist/sherlock-C5ZWPPVT.js +67 -0
  249. package/dist/shopify-GL3NFVGE.js +94 -0
  250. package/dist/shopify-R4G3UXM6.js +93 -0
  251. package/dist/skill-discovery-7INAUP4D.js +77 -0
  252. package/dist/skill-discovery-YPXXV622.js +78 -0
  253. package/dist/state-7MCZBTR5.js +17 -0
  254. package/dist/state-FGOFLFBE.js +18 -0
  255. package/dist/stripe-C22RR4ZS.js +83 -0
  256. package/dist/stripe-LJNPQ3CQ.js +82 -0
  257. package/dist/supabase-IRNQ54FJ.js +98 -0
  258. package/dist/supabase-N4ONFJNQ.js +97 -0
  259. package/dist/taobao-aipaas-LRR4GMO3.js +45 -0
  260. package/dist/taobao-aipaas-RVKORSF4.js +46 -0
  261. package/dist/tapd-3JPVJ7XH.js +46 -0
  262. package/dist/tapd-TMQRSMFG.js +47 -0
  263. package/dist/tencent-ads-IGD33LO7.js +42 -0
  264. package/dist/tencent-ads-UHC6OPBV.js +43 -0
  265. package/dist/tencent-docs-C3A4J3CJ.js +47 -0
  266. package/dist/tencent-docs-O2SC4FHL.js +48 -0
  267. package/dist/tencent-maps-HMMWMNF4.js +37 -0
  268. package/dist/tencent-maps-OQOKHVW2.js +36 -0
  269. package/dist/vercel-KOXDDTHX.js +73 -0
  270. package/dist/vercel-OLNVDWMG.js +74 -0
  271. package/dist/webflow-FULU5Q2I.js +114 -0
  272. package/dist/webflow-OMJKZM54.js +115 -0
  273. package/dist/wechat-miniprogram-skyline-KYCDMQNW.js +74 -0
  274. package/dist/wechat-miniprogram-skyline-VR4FVIQL.js +75 -0
  275. package/dist/wechat-pay-BCMAJ6UW.js +50 -0
  276. package/dist/wechat-pay-YQQKXVUI.js +51 -0
  277. package/dist/wonda-NGWIORYN.js +81 -0
  278. package/dist/wonda-RBABXFNM.js +82 -0
  279. package/dist/wordpress-woocommerce-RDIUTHYT.js +57 -0
  280. package/dist/wordpress-woocommerce-RNA5HB3N.js +58 -0
  281. package/dist/wps-DAEFQHDE.js +47 -0
  282. package/dist/wps-LUWHMZQQ.js +48 -0
  283. package/dist/yuque-HCHTJWNI.js +72 -0
  284. package/dist/yuque-KRH5O74J.js +71 -0
  285. package/images/RUNNERS.md +270 -0
  286. package/images/cc-connect-runner/entrypoint.mjs +311 -0
  287. package/images/claude-runner/Dockerfile +88 -0
  288. package/images/claude-runner/RUNNER.md +222 -0
  289. package/images/claude-runner/entrypoint.mjs +2 -0
  290. package/images/codex-runner/Dockerfile +87 -0
  291. package/images/codex-runner/RUNNER.md +226 -0
  292. package/images/codex-runner/entrypoint.mjs +2 -0
  293. package/images/gemini-runner/Dockerfile +87 -0
  294. package/images/gemini-runner/RUNNER.md +218 -0
  295. package/images/gemini-runner/entrypoint.mjs +2 -0
  296. package/images/hermes-runner/Dockerfile +74 -0
  297. package/images/hermes-runner/RUNNER.md +243 -0
  298. package/images/hermes-runner/entrypoint.mjs +283 -0
  299. package/images/openclaw-runner/Dockerfile +212 -0
  300. package/images/openclaw-runner/RUNNER.md +170 -0
  301. package/images/openclaw-runner/entrypoint.mjs +1113 -0
  302. package/images/openclaw-runner/warm-runtime-deps.mjs +95 -0
  303. package/images/opencode-runner/Dockerfile +87 -0
  304. package/images/opencode-runner/RUNNER.md +202 -0
  305. package/images/opencode-runner/entrypoint.mjs +2 -0
  306. package/package.json +121 -0
  307. package/templates/agent-marketplace-buddy.template.json +131 -0
  308. package/templates/ai-werewolf.template.json +92 -0
  309. package/templates/bmad-method-buddy.template.json +123 -0
  310. package/templates/brain-fix.template.json +92 -0
  311. package/templates/claude-ads-buddy.template.json +123 -0
  312. package/templates/claude-financial-services-buddy.template.json +111 -0
  313. package/templates/claude-seo-buddy.template.json +123 -0
  314. package/templates/code-arena.template.json +92 -0
  315. package/templates/daily-brief.template.json +92 -0
  316. package/templates/e-wife.template.json +92 -0
  317. package/templates/everything-claude-code-buddy.template.json +125 -0
  318. package/templates/financial-freedom.template.json +92 -0
  319. package/templates/gitstory.template.json +92 -0
  320. package/templates/google-workspace-buddy.template.json +88 -0
  321. package/templates/gsd-buddy.template.json +119 -0
  322. package/templates/gstack-buddy.template.json +143 -0
  323. package/templates/gstack.template.json +92 -0
  324. package/templates/little-match-girl.template.json +114 -0
  325. package/templates/lovart-buddy.template.json +110 -0
  326. package/templates/marketingskills-buddy.template.json +102 -0
  327. package/templates/retire-buddy.template.json +92 -0
  328. package/templates/scientific-skills-buddy.template.json +119 -0
  329. package/templates/seomachine-buddy.template.json +113 -0
  330. package/templates/shadow-server-app-demo.template.json +105 -0
  331. package/templates/slavingia-skills-buddy.template.json +102 -0
  332. package/templates/superclaude-buddy.template.json +146 -0
  333. package/templates/superpowers-buddy.template.json +108 -0
  334. package/templates/world-pulse.template.json +92 -0
@@ -0,0 +1,1113 @@
1
+ /**
2
+ * OpenClaw Runner — container entrypoint.
3
+ *
4
+ * 1. Read agent config from ConfigMap (/etc/openclaw/config.json)
5
+ * 2. Resolve ${env:VAR} references from environment
6
+ * 3. Write generated OpenClaw config outside the mutable state directory
7
+ * 4. Verify extensions are loaded
8
+ * 5. Start OpenClaw gateway
9
+ * 6. Forward signals for graceful shutdown
10
+ */
11
+
12
+ import { spawn, spawnSync } from 'node:child_process'
13
+ import { randomBytes } from 'node:crypto'
14
+ import {
15
+ chmodSync,
16
+ cpSync,
17
+ createWriteStream,
18
+ existsSync,
19
+ mkdirSync,
20
+ readdirSync,
21
+ readFileSync,
22
+ rmSync,
23
+ writeFileSync,
24
+ } from 'node:fs'
25
+ import { createServer } from 'node:http'
26
+ import { basename, dirname, join, resolve } from 'node:path'
27
+
28
+ const RUNNER_HOME = process.env.HOME ?? '/home/shadow'
29
+ const OPENCLAW_STATE_DIR = process.env.OPENCLAW_STATE_DIR ?? join(RUNNER_HOME, '.openclaw')
30
+ const CONFIG_MOUNT =
31
+ process.env.SHADOW_RUNNER_CONFIG_MOUNT ?? process.env.OPENCLAW_CONFIG_MOUNT ?? '/etc/openclaw'
32
+ const EXTENSIONS_DIR = '/app/extensions'
33
+ const RUNTIME_FILES_PATH = join(CONFIG_MOUNT, 'runtime-files.json')
34
+ const RUNTIME_EXTENSIONS_PATH = join(CONFIG_MOUNT, 'runtime-extensions.json')
35
+ const DEFAULT_SHADOW_SLASH_COMMANDS_PATH = '/etc/shadowob/slash-commands.json'
36
+ const RUNTIME_CONFIG_DIR = process.env.OPENCLAW_RUNTIME_CONFIG_DIR || '/tmp/openclaw/config'
37
+ const RUNTIME_CONFIG_PATH = join(RUNTIME_CONFIG_DIR, 'openclaw.json')
38
+ const OPENCLAW_PACKAGE_DIR = '/app/node_modules/openclaw'
39
+ const PRICING_FETCH_TIMEOUT_MS = Number.parseInt(
40
+ process.env.OPENCLAW_MODEL_PRICING_FETCH_TIMEOUT_MS ?? '2500',
41
+ 10,
42
+ )
43
+ const HEALTH_PORT = parseInt(process.env.OPENCLAW_HEALTH_PORT ?? '3100', 10)
44
+ const OPENCLAW_HTTP_PORT = parseInt(
45
+ process.env.OPENCLAW_GATEWAY_PORT ?? String(HEALTH_PORT + 1),
46
+ 10,
47
+ )
48
+ const OPENCLAW_GATEWAY_BIND = normalizeEnvString(process.env.OPENCLAW_GATEWAY_BIND) || 'loopback'
49
+ const OPENCLAW_DISCOVERY_MDNS_MODE =
50
+ normalizeEnvString(process.env.OPENCLAW_DISCOVERY_MDNS_MODE) || 'off'
51
+ const OPENCLAW_MEMORY_VECTOR_ENABLED = normalizeEnvString(
52
+ process.env.OPENCLAW_MEMORY_VECTOR_ENABLED,
53
+ )
54
+ const LOG_DIR = '/var/log/openclaw'
55
+ const SHARED_WORKSPACE_PATH = process.env.SHARED_WORKSPACE_PATH ?? ''
56
+ const SKILLS_DIR = process.env.SKILLS_DIR ?? ''
57
+ const RUNTIME_DEPS_WARM_SCRIPT = '/app/warm-runtime-deps.mjs'
58
+ const DEFAULT_PLUGIN_STAGE_DIR = '/opt/openclaw-runtime-deps'
59
+ let runtimeDepsStageDir = process.env.OPENCLAW_PLUGIN_STAGE_DIR || DEFAULT_PLUGIN_STAGE_DIR
60
+ const OPENCLAW_VERSION = resolveOpenClawVersion()
61
+ const ALLOWED_RUNTIME_FILE_ROOTS = [RUNNER_HOME, '/home/openclaw', '/workspace', '/etc/shadowob']
62
+
63
+ function installFileLogging() {
64
+ try {
65
+ mkdirSync(LOG_DIR, { recursive: true })
66
+ const stream = createWriteStream(join(LOG_DIR, 'entrypoint.log'), { flags: 'a' })
67
+ const mirror = (original) => {
68
+ return (chunk, encoding, callback) => {
69
+ try {
70
+ stream.write(chunk)
71
+ } catch {
72
+ // Keep stdout/stderr healthy even if file logging fails.
73
+ }
74
+ return original(chunk, encoding, callback)
75
+ }
76
+ }
77
+ process.stdout.write = mirror(process.stdout.write.bind(process.stdout))
78
+ process.stderr.write = mirror(process.stderr.write.bind(process.stderr))
79
+ process.on('uncaughtException', (err) => {
80
+ console.error('[entrypoint] Uncaught exception:', err)
81
+ })
82
+ process.on('unhandledRejection', (reason) => {
83
+ console.error('[entrypoint] Unhandled rejection:', reason)
84
+ })
85
+ } catch (err) {
86
+ console.error('[entrypoint] Failed to install file logging:', err)
87
+ }
88
+ }
89
+
90
+ installFileLogging()
91
+
92
+ // ─── Config Loading ─────────────────────────────────────────────────────────
93
+
94
+ function loadMountedConfig() {
95
+ const configPath = join(CONFIG_MOUNT, 'config.json')
96
+ if (!existsSync(configPath)) {
97
+ console.log('[entrypoint] No mounted config found, using defaults')
98
+ return {}
99
+ }
100
+
101
+ try {
102
+ const raw = readFileSync(configPath, 'utf-8')
103
+ const config = JSON.parse(raw)
104
+ console.log('[entrypoint] Loaded config from ConfigMap')
105
+ return config
106
+ } catch (err) {
107
+ console.error('[entrypoint] Failed to parse mounted config:', err.message)
108
+ return {}
109
+ }
110
+ }
111
+
112
+ function loadRuntimeExtensions() {
113
+ if (!existsSync(RUNTIME_EXTENSIONS_PATH)) {
114
+ return {}
115
+ }
116
+
117
+ try {
118
+ const raw = readFileSync(RUNTIME_EXTENSIONS_PATH, 'utf-8')
119
+ const extensions = JSON.parse(raw)
120
+ if (!extensions || typeof extensions !== 'object' || Array.isArray(extensions)) {
121
+ console.warn('[entrypoint] Ignoring invalid runtime extensions payload')
122
+ return {}
123
+ }
124
+ console.log('[entrypoint] Loaded runtime extensions from ConfigMap')
125
+ return extensions
126
+ } catch (err) {
127
+ console.warn(`[entrypoint] Failed to parse runtime extensions: ${err.message}`)
128
+ return {}
129
+ }
130
+ }
131
+
132
+ function loadRuntimeFiles() {
133
+ if (!existsSync(RUNTIME_FILES_PATH)) {
134
+ return {}
135
+ }
136
+
137
+ try {
138
+ const raw = readFileSync(RUNTIME_FILES_PATH, 'utf-8')
139
+ const files = JSON.parse(raw)
140
+ if (!files || typeof files !== 'object' || Array.isArray(files)) {
141
+ console.warn('[entrypoint] Ignoring invalid runtime files payload')
142
+ return {}
143
+ }
144
+ console.log('[entrypoint] Loaded runtime files from ConfigMap')
145
+ return files
146
+ } catch (err) {
147
+ console.warn(`[entrypoint] Failed to parse runtime files: ${err.message}`)
148
+ return {}
149
+ }
150
+ }
151
+
152
+ function resolveRuntimeFilePlaceholders(value) {
153
+ return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, key) => process.env[key] ?? '')
154
+ }
155
+
156
+ function assertAllowedRuntimeFilePath(path) {
157
+ const absolute = resolve(path)
158
+ if (
159
+ !ALLOWED_RUNTIME_FILE_ROOTS.some((root) => absolute === root || absolute.startsWith(`${root}/`))
160
+ ) {
161
+ throw new Error(`Refusing to materialize runtime file outside allowed roots: ${path}`)
162
+ }
163
+ return absolute
164
+ }
165
+
166
+ function modeForRuntimeFile(path) {
167
+ if (
168
+ path.endsWith('/.env') ||
169
+ path.endsWith('.toml') ||
170
+ path.endsWith('.yaml') ||
171
+ path.endsWith('.json')
172
+ ) {
173
+ return 0o600
174
+ }
175
+ return 0o644
176
+ }
177
+
178
+ function materializeRuntimeFiles() {
179
+ const files = loadRuntimeFiles()
180
+ for (const [path, content] of Object.entries(files)) {
181
+ if (typeof content !== 'string') continue
182
+ const absolute = assertAllowedRuntimeFilePath(path)
183
+ const mode = modeForRuntimeFile(absolute)
184
+ mkdirSync(dirname(absolute), { recursive: true })
185
+ writeFileSync(absolute, resolveRuntimeFilePlaceholders(content), {
186
+ encoding: 'utf-8',
187
+ mode,
188
+ })
189
+ chmodSync(absolute, mode)
190
+ console.log(`[entrypoint] Wrote runtime file: ${absolute}`)
191
+ }
192
+ }
193
+
194
+ function runtimeArtifactPath(runtimeExtensions, kind) {
195
+ const artifacts = Array.isArray(runtimeExtensions?.artifacts) ? runtimeExtensions.artifacts : []
196
+ const artifact = artifacts.find((item) => item?.kind === kind && typeof item.path === 'string')
197
+ return artifact?.path
198
+ }
199
+
200
+ function applyRuntimeArtifacts(runtimeExtensions) {
201
+ if (!process.env.SHADOW_SLASH_COMMANDS_PATH && existsSync(DEFAULT_SHADOW_SLASH_COMMANDS_PATH)) {
202
+ process.env.SHADOW_SLASH_COMMANDS_PATH = DEFAULT_SHADOW_SLASH_COMMANDS_PATH
203
+ console.log(`[entrypoint] Slash command index: ${DEFAULT_SHADOW_SLASH_COMMANDS_PATH}`)
204
+ }
205
+
206
+ const slashIndexPath =
207
+ runtimeArtifactPath(runtimeExtensions, 'shadow.slashCommands') ??
208
+ runtimeExtensions?.slashCommands?.indexPath
209
+ if (typeof slashIndexPath === 'string' && slashIndexPath.trim()) {
210
+ console.log(`[entrypoint] Additional slash command index: ${slashIndexPath.trim()}`)
211
+ }
212
+ }
213
+
214
+ function parseFileMode(value) {
215
+ if (typeof value === 'number' && Number.isFinite(value)) return value
216
+ if (typeof value !== 'string' || !/^[0-7]{3,4}$/.test(value)) return 0o600
217
+ return Number.parseInt(value, 8)
218
+ }
219
+
220
+ function materializeCredentialFiles(runtimeExtensions) {
221
+ const files = Array.isArray(runtimeExtensions?.credentialFiles)
222
+ ? runtimeExtensions.credentialFiles
223
+ : []
224
+ for (const file of files) {
225
+ if (!file || typeof file.envKey !== 'string' || typeof file.path !== 'string') continue
226
+ if (!file.path.startsWith('/')) {
227
+ console.warn(`[entrypoint] Ignoring credential file with non-absolute path: ${file.path}`)
228
+ continue
229
+ }
230
+
231
+ const value = process.env[file.envKey]
232
+ if (!value) continue
233
+
234
+ try {
235
+ mkdirSync(dirname(file.path), { recursive: true })
236
+ const mode = parseFileMode(file.mode)
237
+ writeFileSync(file.path, value, { encoding: 'utf-8', mode })
238
+ chmodSync(file.path, mode)
239
+ console.log(`[entrypoint] Materialized credential file: ${file.path}`)
240
+ } catch (err) {
241
+ console.warn(
242
+ `[entrypoint] Failed to materialize credential file ${file.path}: ${err.message}`,
243
+ )
244
+ }
245
+ }
246
+ }
247
+
248
+ function resolveEnvVars(obj) {
249
+ if (typeof obj === 'string') {
250
+ return obj.replace(/\$\{env:([^}]+)\}/g, (_, key) => {
251
+ return process.env[key] ?? ''
252
+ })
253
+ }
254
+ if (Array.isArray(obj)) {
255
+ return obj.map(resolveEnvVars)
256
+ }
257
+ if (obj !== null && typeof obj === 'object') {
258
+ const result = {}
259
+ for (const [key, value] of Object.entries(obj)) {
260
+ result[key] = resolveEnvVars(value)
261
+ }
262
+ return result
263
+ }
264
+ return obj
265
+ }
266
+
267
+ // ─── OpenClaw Config Generation ─────────────────────────────────────────────
268
+
269
+ function generateOpenClawConfig(mountedConfig) {
270
+ const config = resolveEnvVars(mountedConfig)
271
+
272
+ // Set the actual OpenClaw gateway port. The runner health server is separate
273
+ // so in-container OpenClaw CLI commands can rely on OPENCLAW_GATEWAY_PORT.
274
+ if (!config.gateway) {
275
+ config.gateway = {}
276
+ }
277
+ config.gateway.port = OPENCLAW_HTTP_PORT
278
+ config.gateway.bind = OPENCLAW_GATEWAY_BIND
279
+ // Ensure gateway.mode is set — required by OpenClaw to start without cloud setup
280
+ if (!config.gateway.mode) {
281
+ config.gateway.mode = 'local'
282
+ }
283
+ if (!config.gateway.auth) {
284
+ config.gateway.auth = {}
285
+ }
286
+ if (!config.gateway.auth.mode || config.gateway.auth.mode === 'none') {
287
+ config.gateway.auth.mode = 'token'
288
+ }
289
+ if (
290
+ config.gateway.auth.mode === 'token' &&
291
+ (typeof config.gateway.auth.token !== 'string' || !config.gateway.auth.token.trim())
292
+ ) {
293
+ config.gateway.auth.token =
294
+ process.env.OPENCLAW_GATEWAY_TOKEN || randomBytes(24).toString('hex')
295
+ }
296
+ if (!config.gateway.controlUi || !isPlainObject(config.gateway.controlUi)) {
297
+ config.gateway.controlUi = {}
298
+ }
299
+ if (!Array.isArray(config.gateway.controlUi.allowedOrigins)) {
300
+ config.gateway.controlUi.allowedOrigins = [
301
+ `http://localhost:${OPENCLAW_HTTP_PORT}`,
302
+ `http://127.0.0.1:${OPENCLAW_HTTP_PORT}`,
303
+ ]
304
+ }
305
+ ensureDiscoveryConfigured(config)
306
+
307
+ // Set up shared workspace path — makes the PVC mount discoverable by OpenClaw
308
+ if (SHARED_WORKSPACE_PATH) {
309
+ if (!config.agents) config.agents = {}
310
+ if (!config.agents.defaults) config.agents.defaults = {}
311
+ if (!config.agents.defaults.workspace) {
312
+ config.agents.defaults.workspace = SHARED_WORKSPACE_PATH
313
+ }
314
+ console.log(`[entrypoint] Shared workspace: ${SHARED_WORKSPACE_PATH}`)
315
+ }
316
+
317
+ // Set up skills extra directories — lets OpenClaw discover cloud-installed skills
318
+ if (SKILLS_DIR) {
319
+ if (!config.skills) config.skills = {}
320
+ if (!config.skills.load) config.skills.load = {}
321
+ const extraDirs = new Set(config.skills.load.extraDirs ?? [])
322
+ extraDirs.add(SKILLS_DIR)
323
+ config.skills.load.extraDirs = [...extraDirs]
324
+ console.log(`[entrypoint] Skills directory: ${SKILLS_DIR}`)
325
+ }
326
+
327
+ ensureBundledExtensionsConfigured(config)
328
+ ensureOpenClawBuiltInPluginsAllowed(config)
329
+ ensureBonjourPluginDisabled(config)
330
+ ensureCloudMemorySearchDefaults(config)
331
+ ensureCloudBrowserDefaults(config)
332
+ ensureBrowserPluginEnabled(config)
333
+ if (!config.meta || !isPlainObject(config.meta)) {
334
+ config.meta = {}
335
+ }
336
+ config.meta.lastTouchedVersion = OPENCLAW_VERSION
337
+ config.meta.lastTouchedAt = new Date().toISOString()
338
+
339
+ return config
340
+ }
341
+
342
+ function resolveOpenClawVersion() {
343
+ try {
344
+ const pkg = JSON.parse(readFileSync('/app/node_modules/openclaw/package.json', 'utf-8'))
345
+ if (typeof pkg.version === 'string' && pkg.version.trim()) return pkg.version.trim()
346
+ } catch {
347
+ // Keep entrypoint bootable in tests that do not mount node_modules.
348
+ }
349
+ return 'shadow-cloud-openclaw-runner'
350
+ }
351
+
352
+ function ensureOpenClawBuiltInPluginsAllowed(config) {
353
+ if (!config.plugins || !isPlainObject(config.plugins)) return
354
+ if (!Array.isArray(config.plugins.allow) || config.plugins.allow.length === 0) return
355
+
356
+ const allow = new Set(config.plugins.allow.filter((value) => typeof value === 'string'))
357
+ allow.add('memory-core')
358
+ config.plugins.allow = [...allow]
359
+ }
360
+
361
+ function ensureDiscoveryConfigured(config) {
362
+ if (!config.discovery || !isPlainObject(config.discovery)) config.discovery = {}
363
+ if (!config.discovery.mdns || !isPlainObject(config.discovery.mdns)) config.discovery.mdns = {}
364
+ config.discovery.mdns.mode = OPENCLAW_DISCOVERY_MDNS_MODE
365
+ }
366
+
367
+ function ensureBonjourPluginDisabled(config) {
368
+ if (!config.plugins || !isPlainObject(config.plugins)) config.plugins = {}
369
+ if (!config.plugins.entries || !isPlainObject(config.plugins.entries)) {
370
+ config.plugins.entries = {}
371
+ }
372
+ const existing = config.plugins.entries.bonjour
373
+ config.plugins.entries.bonjour = isPlainObject(existing)
374
+ ? { ...existing, enabled: false }
375
+ : { enabled: false }
376
+ }
377
+
378
+ function ensureCloudMemorySearchDefaults(config) {
379
+ if (!config.agents || !isPlainObject(config.agents)) config.agents = {}
380
+ if (!config.agents.defaults || !isPlainObject(config.agents.defaults)) {
381
+ config.agents.defaults = {}
382
+ }
383
+ const defaults = config.agents.defaults
384
+ if (!defaults.memorySearch || !isPlainObject(defaults.memorySearch)) {
385
+ defaults.memorySearch = {}
386
+ }
387
+ if (!defaults.memorySearch.store || !isPlainObject(defaults.memorySearch.store)) {
388
+ defaults.memorySearch.store = {}
389
+ }
390
+ if (!defaults.memorySearch.store.vector || !isPlainObject(defaults.memorySearch.store.vector)) {
391
+ defaults.memorySearch.store.vector = {}
392
+ }
393
+ if (OPENCLAW_MEMORY_VECTOR_ENABLED) {
394
+ defaults.memorySearch.store.vector.enabled = OPENCLAW_MEMORY_VECTOR_ENABLED !== 'false'
395
+ } else if (typeof defaults.memorySearch.store.vector.enabled !== 'boolean') {
396
+ defaults.memorySearch.store.vector.enabled = true
397
+ }
398
+ if (
399
+ defaults.memorySearch.store.vector.enabled !== false &&
400
+ typeof defaults.memorySearch.store.vector.extensionPath !== 'string'
401
+ ) {
402
+ const extensionPath = resolveSqliteVecExtensionPath()
403
+ if (extensionPath) defaults.memorySearch.store.vector.extensionPath = extensionPath
404
+ }
405
+ }
406
+
407
+ function ensureCloudBrowserDefaults(config) {
408
+ if (!config.browser || !isPlainObject(config.browser)) config.browser = {}
409
+ const browser = config.browser
410
+ if (typeof browser.headless !== 'boolean') browser.headless = true
411
+ if (typeof browser.noSandbox !== 'boolean') browser.noSandbox = true
412
+ if (typeof browser.executablePath !== 'string' || !browser.executablePath.trim()) {
413
+ browser.executablePath = process.env.CHROME_BIN || '/usr/bin/chromium'
414
+ }
415
+ const extraArgs = Array.isArray(browser.extraArgs)
416
+ ? browser.extraArgs.filter((value) => typeof value === 'string' && value.trim())
417
+ : []
418
+ const args = new Set(extraArgs)
419
+ args.add('--disable-dev-shm-usage')
420
+ browser.extraArgs = [...args]
421
+ }
422
+
423
+ function ensureBrowserPluginEnabled(config) {
424
+ if (!config.plugins || !isPlainObject(config.plugins)) config.plugins = {}
425
+ if (Array.isArray(config.plugins.allow) && !config.plugins.allow.includes('browser')) {
426
+ config.plugins.allow = [...config.plugins.allow, 'browser']
427
+ }
428
+ if (!config.plugins.entries || !isPlainObject(config.plugins.entries)) {
429
+ config.plugins.entries = {}
430
+ }
431
+ const existing = config.plugins.entries.browser
432
+ config.plugins.entries.browser = isPlainObject(existing)
433
+ ? { ...existing, enabled: true }
434
+ : { enabled: true }
435
+ }
436
+
437
+ function normalizeEnvString(value) {
438
+ return typeof value === 'string' && value.trim() ? value.trim() : ''
439
+ }
440
+
441
+ function resolveSqliteVecExtensionPath() {
442
+ const os = process.platform === 'win32' ? 'windows' : process.platform
443
+ const suffix =
444
+ process.platform === 'win32' ? 'dll' : process.platform === 'darwin' ? 'dylib' : 'so'
445
+ const packageName = `sqlite-vec-${os}-${process.arch}`
446
+ const candidates = [
447
+ join('/app/node_modules', packageName, `vec0.${suffix}`),
448
+ join('/app/node_modules/openclaw/node_modules', packageName, `vec0.${suffix}`),
449
+ ]
450
+ return candidates.find((candidate) => existsSync(candidate)) ?? ''
451
+ }
452
+
453
+ function listChildDirs(dir) {
454
+ try {
455
+ return readdirSync(dir, { withFileTypes: true })
456
+ .filter((d) => d.isDirectory())
457
+ .map((d) => join(dir, d.name))
458
+ } catch {
459
+ return []
460
+ }
461
+ }
462
+
463
+ function resolveExtensionManifest(extensionDir) {
464
+ const manifestPath = join(extensionDir, 'openclaw.plugin.json')
465
+ if (!existsSync(manifestPath)) return null
466
+
467
+ try {
468
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
469
+ return isPlainObject(manifest) ? manifest : null
470
+ } catch (err) {
471
+ console.warn(`[entrypoint] Failed to parse extension manifest ${manifestPath}: ${err.message}`)
472
+ return null
473
+ }
474
+ }
475
+
476
+ function ensureBundledExtensionsConfigured(config) {
477
+ const extensionDirs = listChildDirs(EXTENSIONS_DIR).filter((dir) => {
478
+ return (
479
+ existsSync(join(dir, 'openclaw.plugin.json')) ||
480
+ existsSync(join(dir, 'package.json')) ||
481
+ existsSync(join(dir, 'dist', 'index.js')) ||
482
+ existsSync(join(dir, 'index.mjs'))
483
+ )
484
+ })
485
+ if (extensionDirs.length === 0) return
486
+
487
+ if (!config.plugins || !isPlainObject(config.plugins)) config.plugins = {}
488
+ if (config.plugins.enabled !== false) config.plugins.enabled = true
489
+ if (!config.plugins.load || !isPlainObject(config.plugins.load)) config.plugins.load = {}
490
+
491
+ const existingPaths = Array.isArray(config.plugins.load.paths)
492
+ ? config.plugins.load.paths.filter((value) => typeof value === 'string')
493
+ : []
494
+ config.plugins.load.paths = [...new Set([...existingPaths, ...extensionDirs])]
495
+
496
+ if (!config.plugins.entries || !isPlainObject(config.plugins.entries)) {
497
+ config.plugins.entries = {}
498
+ }
499
+
500
+ for (const extensionDir of extensionDirs) {
501
+ const manifest = resolveExtensionManifest(extensionDir)
502
+ const id =
503
+ typeof manifest?.id === 'string' && manifest.id.trim()
504
+ ? manifest.id.trim()
505
+ : basename(extensionDir)
506
+ const existing = config.plugins.entries[id]
507
+ if (isPlainObject(existing)) {
508
+ config.plugins.entries[id] = { enabled: true, ...existing }
509
+ } else if (existing == null) {
510
+ config.plugins.entries[id] = { enabled: true }
511
+ }
512
+ }
513
+
514
+ console.log(
515
+ `[entrypoint] Configured ${extensionDirs.length} bundled extension load path(s): ${extensionDirs.join(', ')}`,
516
+ )
517
+ }
518
+
519
+ function isPlainObject(value) {
520
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
521
+ }
522
+
523
+ function mergePlainObjects(target, source) {
524
+ const out = { ...(isPlainObject(target) ? target : {}) }
525
+ if (!isPlainObject(source)) return out
526
+
527
+ for (const [key, value] of Object.entries(source)) {
528
+ if (isPlainObject(value) && isPlainObject(out[key])) {
529
+ out[key] = mergePlainObjects(out[key], value)
530
+ } else {
531
+ out[key] = value
532
+ }
533
+ }
534
+ return out
535
+ }
536
+
537
+ function resolveManifestPatchPath(patch) {
538
+ if (typeof patch.manifestPath === 'string' && patch.manifestPath.trim()) {
539
+ const manifestPath = patch.manifestPath.trim()
540
+ return manifestPath.startsWith('/') ? manifestPath : join('/app', manifestPath)
541
+ }
542
+
543
+ if (typeof patch.extensionId === 'string' && /^[A-Za-z0-9._-]+$/.test(patch.extensionId)) {
544
+ return join(EXTENSIONS_DIR, patch.extensionId, 'openclaw.plugin.json')
545
+ }
546
+
547
+ return null
548
+ }
549
+
550
+ function runtimeManifestPatches(runtimeExtensions) {
551
+ const patches = runtimeExtensions?.openclaw?.manifestPatches
552
+ return Array.isArray(patches) ? patches.filter((patch) => isPlainObject(patch)) : []
553
+ }
554
+
555
+ function applyRuntimeManifestPatches(runtimeExtensions) {
556
+ for (const patch of runtimeManifestPatches(runtimeExtensions)) {
557
+ const manifestPath = resolveManifestPatchPath(patch)
558
+ if (!manifestPath || !existsSync(manifestPath)) continue
559
+
560
+ try {
561
+ let manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
562
+ if (!isPlainObject(manifest)) continue
563
+
564
+ if (isPlainObject(patch.merge)) {
565
+ manifest = mergePlainObjects(manifest, patch.merge)
566
+ }
567
+ if (isPlainObject(patch.channelEnvVars)) {
568
+ manifest.channelEnvVars = mergePlainObjects(manifest.channelEnvVars, patch.channelEnvVars)
569
+ }
570
+ if (isPlainObject(patch.channelConfigs)) {
571
+ manifest.channelConfigs = mergePlainObjects(manifest.channelConfigs, patch.channelConfigs)
572
+ }
573
+
574
+ writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8')
575
+ console.log(`[entrypoint] Applied runtime manifest patch: ${manifestPath}`)
576
+ } catch (err) {
577
+ console.warn(`[entrypoint] Failed to apply manifest patch ${manifestPath}: ${err.message}`)
578
+ }
579
+ }
580
+ }
581
+
582
+ function verifyExtensions() {
583
+ if (!existsSync(EXTENSIONS_DIR)) {
584
+ console.log('[entrypoint] No extensions directory')
585
+ return
586
+ }
587
+
588
+ const extensions = readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
589
+ .filter((e) => e.isDirectory())
590
+ .map((e) => e.name)
591
+
592
+ console.log(`[entrypoint] Found ${extensions.length} extension(s): ${extensions.join(', ')}`)
593
+
594
+ for (const extensionId of extensions) {
595
+ const extensionDir = join(EXTENSIONS_DIR, extensionId)
596
+ const hasEntry =
597
+ existsSync(join(extensionDir, 'index.mjs')) ||
598
+ existsSync(join(extensionDir, 'dist', 'index.js')) ||
599
+ existsSync(join(extensionDir, 'openclaw.plugin.json'))
600
+ if (hasEntry) {
601
+ console.log(`[entrypoint] ✓ extension verified: ${extensionId}`)
602
+ } else {
603
+ console.warn(`[entrypoint] ⚠ extension missing entry point: ${extensionId}`)
604
+ }
605
+ }
606
+ }
607
+
608
+ // ─── Health Check Server ────────────────────────────────────────────────────
609
+
610
+ let gatewayHealthy = false
611
+ let gatewayReady = false
612
+ let shadowChannelReady = false
613
+ let healthRequiresShadowChannel = true
614
+ let gatewayProcess = null
615
+ let gatewayGraceTimer = null
616
+ let gatewayRestarts = 0
617
+ const MAX_GATEWAY_RESTARTS = 5
618
+ const RESTART_DELAY_MS = 5000
619
+
620
+ // ─── Log Redaction ──────────────────────────────────────────────────────────
621
+
622
+ const KEY_PATTERNS = [
623
+ /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
624
+ /\bsk-proj-[A-Za-z0-9_-]{20,}\b/g,
625
+ /\bsk-[A-Za-z0-9]{20,}\b/g,
626
+ /\bgsk_[A-Za-z0-9]{20,}\b/g,
627
+ /\bxai-[A-Za-z0-9]{20,}\b/g,
628
+ /\bkey-[A-Za-z0-9]{20,}\b/g,
629
+ /\bghp_[A-Za-z0-9]{20,}\b/g,
630
+ /Bearer\s+[A-Za-z0-9._-]{20,}/g,
631
+ ]
632
+
633
+ function redact(line) {
634
+ let result = line
635
+ for (const pattern of KEY_PATTERNS) {
636
+ pattern.lastIndex = 0
637
+ result = result.replace(pattern, '[REDACTED]')
638
+ }
639
+ return result
640
+ }
641
+
642
+ function startHealthServer() {
643
+ const server = createServer((req, res) => {
644
+ if (req.url === '/live') {
645
+ res.writeHead(200, { 'Content-Type': 'application/json' })
646
+ res.end(
647
+ JSON.stringify({
648
+ status: 'live',
649
+ pid: gatewayProcess?.pid,
650
+ gatewayReady,
651
+ shadowChannelReady,
652
+ }),
653
+ )
654
+ return
655
+ }
656
+
657
+ if (req.url === '/ready' || req.url === '/health') {
658
+ const ready = healthRequiresShadowChannel ? shadowChannelReady : gatewayReady
659
+ res.writeHead(ready ? 200 : 503, { 'Content-Type': 'application/json' })
660
+ res.end(
661
+ JSON.stringify({
662
+ status: ready ? 'ready' : 'starting',
663
+ pid: gatewayProcess?.pid,
664
+ gatewayReady,
665
+ shadowChannelReady,
666
+ }),
667
+ )
668
+ } else {
669
+ res.writeHead(404)
670
+ res.end()
671
+ }
672
+ })
673
+
674
+ server.listen(HEALTH_PORT, '0.0.0.0', () => {
675
+ console.log(`[entrypoint] Health server listening on :${HEALTH_PORT}`)
676
+ })
677
+
678
+ return server
679
+ }
680
+
681
+ // ─── Gateway Process ────────────────────────────────────────────────────────
682
+
683
+ function clearStaleRuntimeDependencyLocks() {
684
+ const depsRoots = [runtimeDepsStageDir, join(OPENCLAW_STATE_DIR, 'plugin-runtime-deps')].filter(
685
+ (entry, index, values) => entry && values.indexOf(entry) === index,
686
+ )
687
+
688
+ for (const depsRoot of depsRoots) {
689
+ if (!existsSync(depsRoot)) continue
690
+
691
+ for (const runtimeDir of listChildDirs(depsRoot)) {
692
+ const lockDir = join(runtimeDir, '.openclaw-runtime-deps.lock')
693
+ if (!existsSync(lockDir)) continue
694
+
695
+ let ownerPid = null
696
+ try {
697
+ const owner = JSON.parse(readFileSync(join(lockDir, 'owner.json'), 'utf-8'))
698
+ if (typeof owner.pid === 'number') ownerPid = owner.pid
699
+ } catch {
700
+ // Treat unreadable lock metadata as stale; the gateway will recreate it.
701
+ }
702
+
703
+ const ownerAlive = ownerPid !== null && existsSync(`/proc/${ownerPid}`)
704
+ if (!ownerAlive) {
705
+ rmSync(lockDir, { recursive: true, force: true })
706
+ console.log(`[entrypoint] Removed stale OpenClaw runtime dependency lock: ${lockDir}`)
707
+ }
708
+ }
709
+ }
710
+ }
711
+
712
+ function prepareWritableRuntimeDepsStage() {
713
+ const imageStageDir = DEFAULT_PLUGIN_STAGE_DIR
714
+ const writableStageDir = join(OPENCLAW_STATE_DIR, 'plugin-runtime-deps')
715
+ const explicitStageDir = process.env.OPENCLAW_PLUGIN_STAGE_DIR
716
+
717
+ if (explicitStageDir && explicitStageDir !== imageStageDir) {
718
+ runtimeDepsStageDir = explicitStageDir
719
+ return
720
+ }
721
+
722
+ mkdirSync(writableStageDir, { recursive: true })
723
+ if (existsSync(imageStageDir)) {
724
+ for (const runtimeDir of listChildDirs(imageStageDir)) {
725
+ const dest = join(writableStageDir, basename(runtimeDir))
726
+ if (existsSync(dest)) continue
727
+ try {
728
+ cpSync(runtimeDir, dest, { recursive: true, dereference: false })
729
+ console.log(`[entrypoint] Seeded OpenClaw runtime deps from image: ${dest}`)
730
+ } catch (err) {
731
+ console.warn(`[entrypoint] Failed to seed runtime deps ${dest}: ${err.message}`)
732
+ }
733
+ }
734
+ }
735
+ runtimeDepsStageDir = writableStageDir
736
+ }
737
+
738
+ function runRuntimeDepsWarmup(configPath, stageDir) {
739
+ if (!existsSync(RUNTIME_DEPS_WARM_SCRIPT)) {
740
+ return { ok: false, reason: 'Bundled runtime dependency warmup script is missing' }
741
+ }
742
+
743
+ const timeout = Number.parseInt(process.env.OPENCLAW_RUNTIME_DEPS_WARM_TIMEOUT_MS ?? '240000', 10)
744
+ const env = {
745
+ ...process.env,
746
+ OPENCLAW_CONFIG_PATH: configPath,
747
+ OPENCLAW_STATE_DIR,
748
+ OPENCLAW_PLUGIN_STAGE_DIR: stageDir,
749
+ HOME: RUNNER_HOME,
750
+ NODE_ENV: 'production',
751
+ npm_config_cache: '/tmp/npm-cache',
752
+ }
753
+
754
+ console.log(`[entrypoint] Warming OpenClaw bundled runtime dependencies in ${stageDir}...`)
755
+ const result = spawnSync('node', [RUNTIME_DEPS_WARM_SCRIPT, configPath], {
756
+ env,
757
+ stdio: ['ignore', 'pipe', 'pipe'],
758
+ timeout,
759
+ })
760
+
761
+ const stdout = result.stdout?.toString().trim()
762
+ if (stdout) {
763
+ for (const line of stdout.split('\n')) process.stdout.write(`${redact(line)}\n`)
764
+ }
765
+ const stderr = result.stderr?.toString().trim()
766
+ if (stderr) {
767
+ for (const line of stderr.split('\n')) process.stderr.write(`${redact(line)}\n`)
768
+ }
769
+
770
+ if (result.error) {
771
+ return { ok: false, reason: result.error.message }
772
+ }
773
+ if (result.status !== 0) {
774
+ return { ok: false, reason: `exited ${result.status}` }
775
+ }
776
+ return { ok: true }
777
+ }
778
+
779
+ function warmBundledPluginRuntimeDeps(configPath) {
780
+ if (process.env.OPENCLAW_SKIP_RUNTIME_DEPS_WARMUP === '1') {
781
+ console.log('[entrypoint] Skipping bundled runtime dependency warmup')
782
+ return
783
+ }
784
+
785
+ const preferredStageDir = runtimeDepsStageDir
786
+ const first = runRuntimeDepsWarmup(configPath, preferredStageDir)
787
+ if (first.ok) {
788
+ runtimeDepsStageDir = preferredStageDir
789
+ console.log('[entrypoint] ✓ bundled runtime dependencies warmed')
790
+ return
791
+ }
792
+
793
+ const fallbackStageDir = join(OPENCLAW_STATE_DIR, 'plugin-runtime-deps')
794
+ console.warn(
795
+ `[entrypoint] Runtime dependency warmup in ${preferredStageDir} failed: ${first.reason}`,
796
+ )
797
+ if (preferredStageDir === fallbackStageDir) return
798
+
799
+ const fallback = runRuntimeDepsWarmup(configPath, fallbackStageDir)
800
+ if (fallback.ok) {
801
+ runtimeDepsStageDir = fallbackStageDir
802
+ console.log('[entrypoint] ✓ bundled runtime dependencies warmed in writable state dir')
803
+ return
804
+ }
805
+ console.warn(`[entrypoint] Runtime dependency fallback warmup failed: ${fallback.reason}`)
806
+ }
807
+
808
+ function findGatewayEntry() {
809
+ const candidates = [
810
+ '/app/node_modules/openclaw/dist/cli/index.js',
811
+ '/app/node_modules/openclaw/openclaw.mjs',
812
+ '/app/node_modules/.bin/openclaw',
813
+ ]
814
+
815
+ for (const path of candidates) {
816
+ if (existsSync(path)) return path
817
+ }
818
+ return 'openclaw' // Fallback to PATH
819
+ }
820
+
821
+ function listJavaScriptFiles(root, maxDepth = 3) {
822
+ if (maxDepth < 0 || !existsSync(root)) return []
823
+ const files = []
824
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
825
+ const path = join(root, entry.name)
826
+ if (entry.isDirectory()) {
827
+ files.push(...listJavaScriptFiles(path, maxDepth - 1))
828
+ continue
829
+ }
830
+ if (entry.isFile() && entry.name.endsWith('.js')) files.push(path)
831
+ }
832
+ return files
833
+ }
834
+
835
+ function patchOpenClawPricingTimeout() {
836
+ if (!Number.isFinite(PRICING_FETCH_TIMEOUT_MS) || PRICING_FETCH_TIMEOUT_MS <= 0) return
837
+ const distDir = join(OPENCLAW_PACKAGE_DIR, 'dist')
838
+ const pattern = /const FETCH_TIMEOUT_MS = [^;]+;/g
839
+ for (const file of listJavaScriptFiles(distDir)) {
840
+ let source
841
+ try {
842
+ source = readFileSync(file, 'utf-8')
843
+ } catch {
844
+ continue
845
+ }
846
+ if (!source.includes('OPENROUTER_MODELS_URL') || !source.includes('LITELLM_PRICING_URL')) {
847
+ continue
848
+ }
849
+ if (!pattern.test(source)) continue
850
+ pattern.lastIndex = 0
851
+ writeFileSync(
852
+ file,
853
+ source.replace(pattern, `const FETCH_TIMEOUT_MS = ${PRICING_FETCH_TIMEOUT_MS};`),
854
+ 'utf-8',
855
+ )
856
+ console.log(
857
+ `[entrypoint] OpenClaw model pricing fetch timeout set to ${PRICING_FETCH_TIMEOUT_MS}ms`,
858
+ )
859
+ return
860
+ }
861
+ }
862
+
863
+ function startGateway(_healthServer, configPath) {
864
+ clearStaleRuntimeDependencyLocks()
865
+ patchOpenClawPricingTimeout()
866
+
867
+ const entry = findGatewayEntry()
868
+ const gatewayPort = OPENCLAW_HTTP_PORT
869
+
870
+ console.log(`[entrypoint] Starting OpenClaw gateway: ${entry}`)
871
+ console.log(`[entrypoint] Config: ${configPath}`)
872
+ console.log(`[entrypoint] Gateway port: ${gatewayPort}`)
873
+
874
+ const env = {
875
+ ...process.env,
876
+ // OPENCLAW_CONFIG_PATH is what OpenClaw actually reads (not OPENCLAW_CONFIG).
877
+ // Matches the desktop app's env setup in paths.ts buildGatewayEnv().
878
+ OPENCLAW_CONFIG_PATH: configPath,
879
+ OPENCLAW_STATE_DIR: OPENCLAW_STATE_DIR,
880
+ OPENCLAW_GATEWAY_PORT: String(gatewayPort),
881
+ OPENCLAW_PLUGIN_STAGE_DIR: runtimeDepsStageDir,
882
+ OPENCLAW_LOG_DIR: LOG_DIR,
883
+ NODE_ENV: 'production',
884
+ // Disable OpenClaw's self-respawn mechanism — the original process would exit
885
+ // after spawning a child, causing our entrypoint to think the gateway crashed.
886
+ OPENCLAW_NO_RESPAWN: '1',
887
+ // Avoid overhead from compile-cache setup in containers
888
+ NODE_COMPILE_CACHE: '/tmp/openclaw-compile-cache',
889
+ // npm/npx writes cache to $HOME/.npm by default; HOME is read-only in containers,
890
+ // so redirect to /tmp to allow ACPX backend probes (e.g. npx @zed-industries/codex-acp)
891
+ npm_config_cache: '/tmp/npm-cache',
892
+ }
893
+
894
+ const proc = spawn(
895
+ 'node',
896
+ [entry, 'gateway', '--port', String(gatewayPort), '--allow-unconfigured'],
897
+ {
898
+ env,
899
+ stdio: ['ignore', 'pipe', 'pipe'],
900
+ cwd: OPENCLAW_STATE_DIR,
901
+ },
902
+ )
903
+
904
+ gatewayProcess = proc
905
+
906
+ // Keep the container unready until the gateway has actually started the
907
+ // Shadow channel. A fixed grace period marks pods ready while OpenClaw is
908
+ // still installing plugin runtime deps, which causes rolling updates to drop
909
+ // channel messages during handoff.
910
+ gatewayGraceTimer = setTimeout(() => {
911
+ if (!proc.killed && proc.exitCode === null) {
912
+ console.log('[entrypoint] Gateway still starting — waiting for channel readiness')
913
+ }
914
+ }, 120000)
915
+
916
+ proc.stdout.on('data', (data) => {
917
+ const line = data.toString().trim()
918
+ process.stdout.write(`[openclaw] ${redact(line)}\n`)
919
+
920
+ if (line.includes('[gateway] ready') || line.includes('Gateway ready')) {
921
+ gatewayReady = true
922
+ console.log('[entrypoint] Gateway HTTP server is ready')
923
+ if (!healthRequiresShadowChannel && !gatewayHealthy) {
924
+ gatewayHealthy = true
925
+ console.log('[entrypoint] Gateway is ready')
926
+ }
927
+ }
928
+
929
+ if (
930
+ healthRequiresShadowChannel &&
931
+ (line.includes('[ws] ✓ Joined channel room') ||
932
+ line.includes('[ws] Shadow channel monitor ready'))
933
+ ) {
934
+ shadowChannelReady = true
935
+ clearTimeout(gatewayGraceTimer)
936
+ if (!gatewayHealthy) {
937
+ gatewayHealthy = true
938
+ // Keep health server running — it now returns 200 since gatewayHealthy=true
939
+ console.log('[entrypoint] Shadow channel is ready')
940
+ }
941
+ }
942
+ })
943
+
944
+ proc.stderr.on('data', (data) => {
945
+ process.stderr.write(`[openclaw:err] ${redact(data.toString().trim())}\n`)
946
+ })
947
+
948
+ proc.on('exit', (code, signal) => {
949
+ console.log(`[entrypoint] Gateway exited: code=${code} signal=${signal}`)
950
+ clearTimeout(gatewayGraceTimer)
951
+ gatewayHealthy = false
952
+ gatewayReady = false
953
+ shadowChannelReady = false
954
+
955
+ if (signal === 'SIGTERM' || signal === 'SIGINT') {
956
+ return // Normal shutdown, signal handlers will handle process.exit
957
+ }
958
+
959
+ // Graceful degradation: restart the gateway instead of crashing the container
960
+ gatewayRestarts++
961
+ if (gatewayRestarts <= MAX_GATEWAY_RESTARTS) {
962
+ console.log(
963
+ `[entrypoint] Gateway crashed (${gatewayRestarts}/${MAX_GATEWAY_RESTARTS}), restarting in ${RESTART_DELAY_MS}ms...`,
964
+ )
965
+ setTimeout(() => {
966
+ startGateway(_healthServer, configPath)
967
+ }, RESTART_DELAY_MS)
968
+ } else {
969
+ console.log('[entrypoint] Gateway exceeded max restarts, shutting down container')
970
+ process.exit(code ?? 1)
971
+ }
972
+ })
973
+
974
+ return proc
975
+ }
976
+
977
+ // ─── Signal Handling ────────────────────────────────────────────────────────
978
+
979
+ function setupSignalHandlers() {
980
+ const shutdown = (signal) => {
981
+ console.log(`[entrypoint] Received ${signal}, shutting down...`)
982
+ gatewayHealthy = false
983
+
984
+ if (gatewayProcess && !gatewayProcess.killed) {
985
+ gatewayProcess.kill('SIGTERM')
986
+
987
+ // Force kill after 10s
988
+ setTimeout(() => {
989
+ if (gatewayProcess && !gatewayProcess.killed) {
990
+ console.log('[entrypoint] Force killing gateway...')
991
+ gatewayProcess.kill('SIGKILL')
992
+ }
993
+ process.exit(0)
994
+ }, 10_000)
995
+ } else {
996
+ process.exit(0)
997
+ }
998
+ }
999
+
1000
+ process.on('SIGTERM', () => shutdown('SIGTERM'))
1001
+ process.on('SIGINT', () => shutdown('SIGINT'))
1002
+ }
1003
+
1004
+ // ─── Main ───────────────────────────────────────────────────────────────────
1005
+
1006
+ async function main() {
1007
+ console.log('[entrypoint] Shadow Cloud OpenClaw Runner starting...')
1008
+ console.log(`[entrypoint] Agent: ${process.env.AGENT_ID ?? 'default'}`)
1009
+ console.log(`[entrypoint] Node: ${process.version}`)
1010
+
1011
+ // 1. Load config
1012
+ const mountedConfig = loadMountedConfig()
1013
+ const runtimeExtensions = loadRuntimeExtensions()
1014
+ materializeRuntimeFiles()
1015
+ applyRuntimeArtifacts(runtimeExtensions)
1016
+ materializeCredentialFiles(runtimeExtensions)
1017
+ const baseConfig = generateOpenClawConfig(mountedConfig)
1018
+ const openclawConfig = baseConfig
1019
+ healthRequiresShadowChannel =
1020
+ isPlainObject(openclawConfig.channels?.shadowob) &&
1021
+ openclawConfig.channels.shadowob.enabled !== false
1022
+
1023
+ // 2. Write config. Keep generated config out of ~/.openclaw so OpenClaw's
1024
+ // state/config writer cannot clobber it and trigger a gateway reload loop.
1025
+ mkdirSync(OPENCLAW_STATE_DIR, { recursive: true })
1026
+ mkdirSync(RUNTIME_CONFIG_DIR, { recursive: true })
1027
+ const configPath = RUNTIME_CONFIG_PATH
1028
+ writeFileSync(configPath, JSON.stringify(openclawConfig, null, 2), 'utf-8')
1029
+ console.log(`[entrypoint] Config written to ${configPath}`)
1030
+
1031
+ // 2b. Start live health server early. Readiness remains false until the
1032
+ // gateway has joined Shadow channel rooms.
1033
+ const healthServer = startHealthServer()
1034
+
1035
+ // 2c. Ensure shared workspace directory exists
1036
+ if (SHARED_WORKSPACE_PATH) {
1037
+ mkdirSync(SHARED_WORKSPACE_PATH, { recursive: true })
1038
+ console.log(`[entrypoint] Shared workspace ready: ${SHARED_WORKSPACE_PATH}`)
1039
+ }
1040
+
1041
+ // 2d. Run `openclaw setup` to initialize workspace with bootstrap files.
1042
+ // This seeds AGENTS.md, SOUL.md, IDENTITY.md, etc. from OpenClaw's internal templates.
1043
+ const workspaceDir =
1044
+ openclawConfig.agents?.defaults?.workspace ||
1045
+ SHARED_WORKSPACE_PATH ||
1046
+ join(OPENCLAW_STATE_DIR, 'workspace')
1047
+ mkdirSync(workspaceDir, { recursive: true })
1048
+ console.log(`[entrypoint] Initializing workspace: ${workspaceDir}`)
1049
+ const setupResult = spawnSync('openclaw', ['setup', '--workspace', workspaceDir], {
1050
+ env: { ...process.env, OPENCLAW_CONFIG_PATH: configPath, HOME: RUNNER_HOME },
1051
+ stdio: ['ignore', 'pipe', 'pipe'],
1052
+ timeout: 30000,
1053
+ })
1054
+ if (setupResult.status === 0) {
1055
+ console.log('[entrypoint] ✓ openclaw setup completed')
1056
+ } else {
1057
+ const stderr = setupResult.stderr?.toString().trim()
1058
+ console.warn(
1059
+ `[entrypoint] ⚠ openclaw setup exited ${setupResult.status}: ${stderr || '(no output)'}`,
1060
+ )
1061
+ }
1062
+ writeFileSync(configPath, JSON.stringify(openclawConfig, null, 2), 'utf-8')
1063
+ console.log('[entrypoint] Runtime config restored after setup')
1064
+
1065
+ // 2e. Overlay workspace files from ConfigMap (SOUL.md, AGENTS.md, etc.)
1066
+ // These are agent-specific files generated by the cloud config builder that
1067
+ // override the default bootstrap files created by `openclaw setup`.
1068
+ const WORKSPACE_BOOTSTRAP_FILES = [
1069
+ 'SOUL.md',
1070
+ 'IDENTITY.md',
1071
+ 'TOOLS.md',
1072
+ 'AGENTS.md',
1073
+ 'USER.md',
1074
+ 'HEARTBEAT.md',
1075
+ 'BOOTSTRAP.md',
1076
+ ]
1077
+ for (const filename of WORKSPACE_BOOTSTRAP_FILES) {
1078
+ const srcPath = join(CONFIG_MOUNT, filename)
1079
+ if (existsSync(srcPath)) {
1080
+ const destPath = join(workspaceDir, filename)
1081
+ try {
1082
+ writeFileSync(destPath, readFileSync(srcPath, 'utf-8'), 'utf-8')
1083
+ console.log(`[entrypoint] Wrote ${filename} to workspace`)
1084
+ } catch (err) {
1085
+ console.warn(`[entrypoint] Failed to write ${filename}: ${err.message}`)
1086
+ }
1087
+ }
1088
+ }
1089
+
1090
+ // 2f. Ensure skills directory exists
1091
+ if (SKILLS_DIR) {
1092
+ mkdirSync(SKILLS_DIR, { recursive: true })
1093
+ console.log(`[entrypoint] Skills directory ready: ${SKILLS_DIR}`)
1094
+ }
1095
+
1096
+ // 3. Apply plugin-provided runtime metadata, then pre-stage plugin runtime deps
1097
+ // before chat traffic.
1098
+ applyRuntimeManifestPatches(runtimeExtensions)
1099
+ verifyExtensions()
1100
+ prepareWritableRuntimeDepsStage()
1101
+ warmBundledPluginRuntimeDeps(configPath)
1102
+
1103
+ // 4. Start gateway
1104
+ startGateway(healthServer, configPath)
1105
+
1106
+ // 5. Setup signal handlers
1107
+ setupSignalHandlers()
1108
+ }
1109
+
1110
+ main().catch((err) => {
1111
+ console.error('[entrypoint] Fatal error:', err)
1112
+ process.exit(1)
1113
+ })