@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.
- package/README.md +509 -0
- package/dist/agent-browser-CERTMCDL.js +117 -0
- package/dist/agent-browser-CIRZRIY4.js +118 -0
- package/dist/agent-pack-LF3O5TR4.js +1236 -0
- package/dist/agent-pack-RQT27V7R.js +1235 -0
- package/dist/airtable-BG2Q75G2.js +82 -0
- package/dist/airtable-JCQXFM5D.js +83 -0
- package/dist/alipay-MZX2XCDB.js +52 -0
- package/dist/alipay-TZQI34RB.js +51 -0
- package/dist/amap-5RQB3VGC.js +45 -0
- package/dist/amap-KPCLZYYL.js +44 -0
- package/dist/atlassian-LGOEWYC7.js +54 -0
- package/dist/atlassian-TVS2A4IU.js +55 -0
- package/dist/baidu-appbuilder-6UMESXHW.js +41 -0
- package/dist/baidu-appbuilder-QRRL3ETM.js +42 -0
- package/dist/baidu-maps-HEPMVP5D.js +44 -0
- package/dist/baidu-maps-HXC4FBVP.js +45 -0
- package/dist/baidu-netdisk-G5Q6B5NH.js +45 -0
- package/dist/baidu-netdisk-RS2K5W2M.js +44 -0
- package/dist/baidu-smartprogram-EWTK5WKK.js +41 -0
- package/dist/baidu-smartprogram-JHD3XWF6.js +40 -0
- package/dist/browserbase-IUIYVYI7.js +67 -0
- package/dist/browserbase-JFO2PCIA.js +68 -0
- package/dist/canva-3YOFL7JS.js +62 -0
- package/dist/canva-FMYN65SM.js +61 -0
- package/dist/chunk-6P2K6QZR.js +529 -0
- package/dist/chunk-7VMRQ7MG.js +90 -0
- package/dist/chunk-AD3JTIU3.js +17 -0
- package/dist/chunk-BF6CV2Y4.js +64 -0
- package/dist/chunk-CTNUKOQE.js +439 -0
- package/dist/chunk-EEFMJYKB.js +97 -0
- package/dist/chunk-EJKFQ35I.js +739 -0
- package/dist/chunk-HUICDC56.js +62 -0
- package/dist/chunk-JUPAE5IA.js +527 -0
- package/dist/chunk-JY2HTT7Q.js +437 -0
- package/dist/chunk-KEPTCLUO.js +121 -0
- package/dist/chunk-KKK5H7YX.js +3622 -0
- package/dist/chunk-POSVEKIY.js +210 -0
- package/dist/chunk-QET4LT4J.js +5769 -0
- package/dist/chunk-QV4XWO3P.js +30 -0
- package/dist/chunk-R52J3PH2.js +120 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RECNVWMT.js +212 -0
- package/dist/chunk-RTPBU5HF.js +92 -0
- package/dist/chunk-SUZ2ATT6.js +5774 -0
- package/dist/chunk-SVMXSIMG.js +98 -0
- package/dist/chunk-TV3CBM7R.js +28 -0
- package/dist/chunk-V2LU736V.js +3495 -0
- package/dist/chunk-ZUYL3W53.js +741 -0
- package/dist/claude-plugin-577TAQVS.js +1463 -0
- package/dist/claude-plugin-L3MXJJ6J.js +1464 -0
- package/dist/cli.js +7021 -0
- package/dist/cloudflare-HBBABPK6.js +114 -0
- package/dist/cloudflare-RDFPKMM5.js +115 -0
- package/dist/cnb-FLP3QX46.js +44 -0
- package/dist/cnb-YAVVEYFB.js +45 -0
- package/dist/console/index.html +12 -0
- package/dist/console/logo.png +0 -0
- package/dist/console/static/css/5079.f9e0918d.css +1 -0
- package/dist/console/static/css/index.7f91f806.css +1 -0
- package/dist/console/static/font/codicon.5b7d6fac.ttf +0 -0
- package/dist/console/static/js/5079.72a51ca2.js +699 -0
- package/dist/console/static/js/5079.72a51ca2.js.LICENSE.txt +35 -0
- package/dist/console/static/js/7426.f8d483ea.js +1 -0
- package/dist/console/static/js/async/1008.4df521b7.js +1 -0
- package/dist/console/static/js/async/102.1d473ec7.js +1 -0
- package/dist/console/static/js/async/1134.3f9fd9e7.js +1 -0
- package/dist/console/static/js/async/1318.4b8e48e7.js +1 -0
- package/dist/console/static/js/async/1360.5606da88.js +7 -0
- package/dist/console/static/js/async/1546.045f484f.js +1 -0
- package/dist/console/static/js/async/1562.187de2a8.js +1 -0
- package/dist/console/static/js/async/168.456d4813.js +1 -0
- package/dist/console/static/js/async/1750.e6dc2664.js +1 -0
- package/dist/console/static/js/async/1994.3fc86066.js +1 -0
- package/dist/console/static/js/async/2348.613ae3d9.js +1 -0
- package/dist/console/static/js/async/2390.1b890b9d.js +1 -0
- package/dist/console/static/js/async/2414.9d040212.js +1 -0
- package/dist/console/static/js/async/2454.4c1784ab.js +1 -0
- package/dist/console/static/js/async/2498.f5f92030.js +1 -0
- package/dist/console/static/js/async/2924.b823cd1a.js +1 -0
- package/dist/console/static/js/async/3062.63fddea6.js +1 -0
- package/dist/console/static/js/async/3078.dd712008.js +1 -0
- package/dist/console/static/js/async/3198.1f307065.js +1 -0
- package/dist/console/static/js/async/3246.3d5a899f.js +1 -0
- package/dist/console/static/js/async/3286.871676eb.js +1 -0
- package/dist/console/static/js/async/342.10bf3b90.js +1 -0
- package/dist/console/static/js/async/3446.9681a4d7.js +1 -0
- package/dist/console/static/js/async/3698.ccfaabec.js +1 -0
- package/dist/console/static/js/async/3790.2a1106a6.js +1 -0
- package/dist/console/static/js/async/4231.b29784d4.js +1 -0
- package/dist/console/static/js/async/4551.515bd41d.js +1 -0
- package/dist/console/static/js/async/4596.40f6e71b.js +1 -0
- package/dist/console/static/js/async/4600.4aaebe6d.js +1 -0
- package/dist/console/static/js/async/4718.1aae022f.js +1 -0
- package/dist/console/static/js/async/4846.a347c020.js +1 -0
- package/dist/console/static/js/async/4860.83dadf89.js +1 -0
- package/dist/console/static/js/async/500.fcfa37cb.js +1 -0
- package/dist/console/static/js/async/5096.b360203d.js +1 -0
- package/dist/console/static/js/async/5222.043274fe.js +1 -0
- package/dist/console/static/js/async/5362.f498001c.js +1 -0
- package/dist/console/static/js/async/54.c94f0755.js +1 -0
- package/dist/console/static/js/async/5478.50dd9ef0.js +2 -0
- package/dist/console/static/js/async/5478.50dd9ef0.js.LICENSE.txt +3 -0
- package/dist/console/static/js/async/5507.a6a1f793.js +1 -0
- package/dist/console/static/js/async/5638.bc6b102d.js +1 -0
- package/dist/console/static/js/async/5722.e0029049.js +1 -0
- package/dist/console/static/js/async/5942.74635c6b.js +1 -0
- package/dist/console/static/js/async/5994.1c5629c1.js +1 -0
- package/dist/console/static/js/async/6054.6bddf720.js +1 -0
- package/dist/console/static/js/async/6118.45e754e5.js +1 -0
- package/dist/console/static/js/async/6127.adcbcbb6.js +1 -0
- package/dist/console/static/js/async/614.3f434c20.js +1 -0
- package/dist/console/static/js/async/6234.ba3b002d.js +1 -0
- package/dist/console/static/js/async/6310.6546a9ba.js +1 -0
- package/dist/console/static/js/async/6378.9f805419.js +1 -0
- package/dist/console/static/js/async/6380.e4433c49.js +1 -0
- package/dist/console/static/js/async/6418.f23bcfda.js +1 -0
- package/dist/console/static/js/async/6428.77c86114.js +1 -0
- package/dist/console/static/js/async/6443.83318a6c.js +1 -0
- package/dist/console/static/js/async/6508.2b445d62.js +3 -0
- package/dist/console/static/js/async/6542.e82a26c8.js +1 -0
- package/dist/console/static/js/async/6544.62111ecb.js +1 -0
- package/dist/console/static/js/async/6612.a0c9fcf4.js +1 -0
- package/dist/console/static/js/async/6740.695aebf0.js +1 -0
- package/dist/console/static/js/async/6822.dbbb32bc.js +1 -0
- package/dist/console/static/js/async/6824.ad3540ab.js +1 -0
- package/dist/console/static/js/async/6930.585dab94.js +1 -0
- package/dist/console/static/js/async/6982.c81b95e6.js +1 -0
- package/dist/console/static/js/async/7046.ab2378c1.js +1 -0
- package/dist/console/static/js/async/7110.a603277f.js +1 -0
- package/dist/console/static/js/async/7142.4a21366f.js +1 -0
- package/dist/console/static/js/async/7348.15cc6148.js +1373 -0
- package/dist/console/static/js/async/7348.15cc6148.js.LICENSE.txt +14 -0
- package/dist/console/static/js/async/7374.b1ac5c44.js +1 -0
- package/dist/console/static/js/async/742.847f17ca.js +1 -0
- package/dist/console/static/js/async/7446.743954d8.js +1 -0
- package/dist/console/static/js/async/7673.59bbbaac.js +1 -0
- package/dist/console/static/js/async/7684.c5760c8c.js +1 -0
- package/dist/console/static/js/async/7714.c30d0f94.js +1 -0
- package/dist/console/static/js/async/8118.36d5a3bf.js +298 -0
- package/dist/console/static/js/async/8145.4bcf043a.js +1 -0
- package/dist/console/static/js/async/8246.408de938.js +1 -0
- package/dist/console/static/js/async/8390.bdae1f7d.js +1 -0
- package/dist/console/static/js/async/8422.fd94dbe1.js +1 -0
- package/dist/console/static/js/async/8434.94a0e2ae.js +1 -0
- package/dist/console/static/js/async/8518.3158de13.js +1 -0
- package/dist/console/static/js/async/8564.fc2eb841.js +1 -0
- package/dist/console/static/js/async/8678.73af4c9b.js +1 -0
- package/dist/console/static/js/async/8694.79747168.js +1 -0
- package/dist/console/static/js/async/8756.1de37b83.js +1 -0
- package/dist/console/static/js/async/8804.7fe6bdf9.js +3 -0
- package/dist/console/static/js/async/8883.e717ee94.js +1 -0
- package/dist/console/static/js/async/8886.fe6e876c.js +1 -0
- package/dist/console/static/js/async/9030.fc1ae402.js +1 -0
- package/dist/console/static/js/async/9094.5598d084.js +1 -0
- package/dist/console/static/js/async/9218.ee7b84b7.js +1 -0
- package/dist/console/static/js/async/94.9b80bc35.js +1 -0
- package/dist/console/static/js/async/9526.92aba34c.js +1 -0
- package/dist/console/static/js/async/9762.f83bc4f3.js +1 -0
- package/dist/console/static/js/async/984.e11c113a.js +1 -0
- package/dist/console/static/js/async/9846.246653cd.js +1 -0
- package/dist/console/static/js/index.4487e1ff.js +1 -0
- package/dist/console/static/js/lib-react.15d7ca9a.js +2 -0
- package/dist/console/static/js/lib-react.15d7ca9a.js.LICENSE.txt +49 -0
- package/dist/coze-C6PMDPBI.js +49 -0
- package/dist/coze-E6VGRNLV.js +48 -0
- package/dist/dashboard.command-J7XOZNXU.js +8 -0
- package/dist/dashboard.command-RV2NHDKW.js +7 -0
- package/dist/dingtalk-JNRNRN7E.js +77 -0
- package/dist/dingtalk-WZGGIAHJ.js +76 -0
- package/dist/douyin-miniprogram-AIJPPIZH.js +41 -0
- package/dist/douyin-miniprogram-HCYZ5NBW.js +42 -0
- package/dist/figma-2YYNSCDX.js +103 -0
- package/dist/figma-RYOBMENP.js +102 -0
- package/dist/firebase-2IJDDBXX.js +112 -0
- package/dist/firebase-OYSY6HPT.js +111 -0
- package/dist/firecrawl-2T3SBUW7.js +66 -0
- package/dist/firecrawl-IYYXLAZM.js +65 -0
- package/dist/flyai-7FJ4TRAG.js +81 -0
- package/dist/flyai-QS5Q6FJR.js +82 -0
- package/dist/gitagent-MWI75OIX.js +725 -0
- package/dist/gitagent-YBMWY7NZ.js +726 -0
- package/dist/gitee-3N7OFOM7.js +53 -0
- package/dist/gitee-KVNK6KLZ.js +54 -0
- package/dist/github-LUEC2LID.js +143 -0
- package/dist/github-XRO5Z3GC.js +142 -0
- package/dist/google-ads-A3QAJI4D.js +88 -0
- package/dist/google-ads-VPKWTX67.js +89 -0
- package/dist/google-analytics-C4UR5ZR2.js +50 -0
- package/dist/google-analytics-XDYZA2B7.js +49 -0
- package/dist/google-workspace-LL3EWVHH.js +320 -0
- package/dist/google-workspace-YX35SHHX.js +321 -0
- package/dist/huawei-xiaoyi-6BSMGJHR.js +40 -0
- package/dist/huawei-xiaoyi-KPWLTSHB.js +41 -0
- package/dist/hubspot-DIUHGEDI.js +45 -0
- package/dist/hubspot-FTIEMNZO.js +44 -0
- package/dist/huggingface-MJCOXA7E.js +116 -0
- package/dist/huggingface-UUXK2RHK.js +117 -0
- package/dist/index.d.ts +3013 -0
- package/dist/index.js +15649 -0
- package/dist/inference-ai-image-generation-CMI6R5T3.js +106 -0
- package/dist/inference-ai-image-generation-PXV6IG4U.js +107 -0
- package/dist/inference-sh-7AZOLEFI.js +94 -0
- package/dist/inference-sh-ABQOD3YF.js +95 -0
- package/dist/init.command-6E24K4H3.js +9 -0
- package/dist/init.command-O4HG4HKR.js +10 -0
- package/dist/klaviyo-6K5YEFNH.js +45 -0
- package/dist/klaviyo-LDPBWBSS.js +44 -0
- package/dist/kuaidi100-HGFM5VK2.js +42 -0
- package/dist/kuaidi100-UHPFCVXP.js +41 -0
- package/dist/lark-6LNA3LUQ.js +103 -0
- package/dist/lark-URVBZNS4.js +102 -0
- package/dist/linear-7QFSFPOD.js +57 -0
- package/dist/linear-T4ORUP7N.js +56 -0
- package/dist/lovart-PDUXRUHJ.js +99 -0
- package/dist/lovart-QO3SK55T.js +100 -0
- package/dist/meta-ads-SCNFI45S.js +42 -0
- package/dist/meta-ads-V6XPZWX3.js +41 -0
- package/dist/miclaw-5CNTW7VV.js +36 -0
- package/dist/miclaw-TPPPS2WN.js +35 -0
- package/dist/model-provider-AVSFJSZP.js +393 -0
- package/dist/model-provider-KFB76XV5.js +392 -0
- package/dist/notion-FZK76MN2.js +69 -0
- package/dist/notion-WFA7KGZZ.js +70 -0
- package/dist/oceanengine-3JZUS3PP.js +43 -0
- package/dist/oceanengine-5BRIJVJE.js +42 -0
- package/dist/opencli-PFXHGCS2.js +81 -0
- package/dist/opencli-VIGRJTGH.js +80 -0
- package/dist/paypal-33UADIPR.js +54 -0
- package/dist/paypal-Z5JYHIWD.js +55 -0
- package/dist/playwright-MG5WHK47.js +58 -0
- package/dist/playwright-SQAQ3DZG.js +59 -0
- package/dist/plugins-HZBWK3WQ.js +120 -0
- package/dist/plugins-I4GD5SZX.js +121 -0
- package/dist/posthog-MU5MAJOQ.js +79 -0
- package/dist/posthog-RJRRKDWB.js +80 -0
- package/dist/salesforce-34FVIJTG.js +82 -0
- package/dist/salesforce-3QZ6OFVO.js +83 -0
- package/dist/sentry-MCIRMACU.js +111 -0
- package/dist/sentry-PIWW46VA.js +110 -0
- package/dist/seo-suite-4SQ3I67Q.js +54 -0
- package/dist/seo-suite-WJXMA3S4.js +55 -0
- package/dist/serve.command-5FMIPQRY.js +10 -0
- package/dist/serve.command-DNE6GPMK.js +9 -0
- package/dist/shadowob-JELOWHWX.js +1068 -0
- package/dist/shadowob-PRSMI5MW.js +1069 -0
- package/dist/sherlock-2PKY2E2Y.js +66 -0
- package/dist/sherlock-C5ZWPPVT.js +67 -0
- package/dist/shopify-GL3NFVGE.js +94 -0
- package/dist/shopify-R4G3UXM6.js +93 -0
- package/dist/skill-discovery-7INAUP4D.js +77 -0
- package/dist/skill-discovery-YPXXV622.js +78 -0
- package/dist/state-7MCZBTR5.js +17 -0
- package/dist/state-FGOFLFBE.js +18 -0
- package/dist/stripe-C22RR4ZS.js +83 -0
- package/dist/stripe-LJNPQ3CQ.js +82 -0
- package/dist/supabase-IRNQ54FJ.js +98 -0
- package/dist/supabase-N4ONFJNQ.js +97 -0
- package/dist/taobao-aipaas-LRR4GMO3.js +45 -0
- package/dist/taobao-aipaas-RVKORSF4.js +46 -0
- package/dist/tapd-3JPVJ7XH.js +46 -0
- package/dist/tapd-TMQRSMFG.js +47 -0
- package/dist/tencent-ads-IGD33LO7.js +42 -0
- package/dist/tencent-ads-UHC6OPBV.js +43 -0
- package/dist/tencent-docs-C3A4J3CJ.js +47 -0
- package/dist/tencent-docs-O2SC4FHL.js +48 -0
- package/dist/tencent-maps-HMMWMNF4.js +37 -0
- package/dist/tencent-maps-OQOKHVW2.js +36 -0
- package/dist/vercel-KOXDDTHX.js +73 -0
- package/dist/vercel-OLNVDWMG.js +74 -0
- package/dist/webflow-FULU5Q2I.js +114 -0
- package/dist/webflow-OMJKZM54.js +115 -0
- package/dist/wechat-miniprogram-skyline-KYCDMQNW.js +74 -0
- package/dist/wechat-miniprogram-skyline-VR4FVIQL.js +75 -0
- package/dist/wechat-pay-BCMAJ6UW.js +50 -0
- package/dist/wechat-pay-YQQKXVUI.js +51 -0
- package/dist/wonda-NGWIORYN.js +81 -0
- package/dist/wonda-RBABXFNM.js +82 -0
- package/dist/wordpress-woocommerce-RDIUTHYT.js +57 -0
- package/dist/wordpress-woocommerce-RNA5HB3N.js +58 -0
- package/dist/wps-DAEFQHDE.js +47 -0
- package/dist/wps-LUWHMZQQ.js +48 -0
- package/dist/yuque-HCHTJWNI.js +72 -0
- package/dist/yuque-KRH5O74J.js +71 -0
- package/images/RUNNERS.md +270 -0
- package/images/cc-connect-runner/entrypoint.mjs +311 -0
- package/images/claude-runner/Dockerfile +88 -0
- package/images/claude-runner/RUNNER.md +222 -0
- package/images/claude-runner/entrypoint.mjs +2 -0
- package/images/codex-runner/Dockerfile +87 -0
- package/images/codex-runner/RUNNER.md +226 -0
- package/images/codex-runner/entrypoint.mjs +2 -0
- package/images/gemini-runner/Dockerfile +87 -0
- package/images/gemini-runner/RUNNER.md +218 -0
- package/images/gemini-runner/entrypoint.mjs +2 -0
- package/images/hermes-runner/Dockerfile +74 -0
- package/images/hermes-runner/RUNNER.md +243 -0
- package/images/hermes-runner/entrypoint.mjs +283 -0
- package/images/openclaw-runner/Dockerfile +212 -0
- package/images/openclaw-runner/RUNNER.md +170 -0
- package/images/openclaw-runner/entrypoint.mjs +1113 -0
- package/images/openclaw-runner/warm-runtime-deps.mjs +95 -0
- package/images/opencode-runner/Dockerfile +87 -0
- package/images/opencode-runner/RUNNER.md +202 -0
- package/images/opencode-runner/entrypoint.mjs +2 -0
- package/package.json +121 -0
- package/templates/agent-marketplace-buddy.template.json +131 -0
- package/templates/ai-werewolf.template.json +92 -0
- package/templates/bmad-method-buddy.template.json +123 -0
- package/templates/brain-fix.template.json +92 -0
- package/templates/claude-ads-buddy.template.json +123 -0
- package/templates/claude-financial-services-buddy.template.json +111 -0
- package/templates/claude-seo-buddy.template.json +123 -0
- package/templates/code-arena.template.json +92 -0
- package/templates/daily-brief.template.json +92 -0
- package/templates/e-wife.template.json +92 -0
- package/templates/everything-claude-code-buddy.template.json +125 -0
- package/templates/financial-freedom.template.json +92 -0
- package/templates/gitstory.template.json +92 -0
- package/templates/google-workspace-buddy.template.json +88 -0
- package/templates/gsd-buddy.template.json +119 -0
- package/templates/gstack-buddy.template.json +143 -0
- package/templates/gstack.template.json +92 -0
- package/templates/little-match-girl.template.json +114 -0
- package/templates/lovart-buddy.template.json +110 -0
- package/templates/marketingskills-buddy.template.json +102 -0
- package/templates/retire-buddy.template.json +92 -0
- package/templates/scientific-skills-buddy.template.json +119 -0
- package/templates/seomachine-buddy.template.json +113 -0
- package/templates/shadow-server-app-demo.template.json +105 -0
- package/templates/slavingia-skills-buddy.template.json +102 -0
- package/templates/superclaude-buddy.template.json +146 -0
- package/templates/superpowers-buddy.template.json +108 -0
- 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
|
+
})
|