@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,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermes runner entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* Materializes generated Hermes files and starts `hermes gateway` with the
|
|
5
|
+
* ShadowOB platform plugin enabled.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
9
|
+
import {
|
|
10
|
+
chmodSync,
|
|
11
|
+
cpSync,
|
|
12
|
+
existsSync,
|
|
13
|
+
mkdirSync,
|
|
14
|
+
readdirSync,
|
|
15
|
+
readFileSync,
|
|
16
|
+
statSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
} from 'node:fs'
|
|
19
|
+
import { createServer } from 'node:http'
|
|
20
|
+
import { dirname, join, resolve } from 'node:path'
|
|
21
|
+
|
|
22
|
+
const RUNTIME_NAME = 'hermes-runner'
|
|
23
|
+
const CONFIG_MOUNT = process.env.SHADOW_RUNNER_CONFIG_MOUNT ?? '/etc/openclaw'
|
|
24
|
+
const RUNTIME_FILES_PATH = join(CONFIG_MOUNT, 'runtime-files.json')
|
|
25
|
+
const RUNTIME_EXTENSIONS_PATH = join(CONFIG_MOUNT, 'runtime-extensions.json')
|
|
26
|
+
const RUNNER_HOME = process.env.HOME ?? '/home/shadow'
|
|
27
|
+
const HEALTH_PORT = Number.parseInt(
|
|
28
|
+
process.env.SHADOW_RUNNER_HEALTH_PORT ?? process.env.OPENCLAW_GATEWAY_PORT ?? '3100',
|
|
29
|
+
10,
|
|
30
|
+
)
|
|
31
|
+
const LOG_DIR = process.env.SHADOW_RUNNER_LOG_DIR ?? '/var/log/shadowob'
|
|
32
|
+
const ALLOWED_FILE_ROOTS = [RUNNER_HOME, '/home/openclaw', '/workspace', '/etc/shadowob']
|
|
33
|
+
|
|
34
|
+
let ready = false
|
|
35
|
+
let child = null
|
|
36
|
+
|
|
37
|
+
const KEY_PATTERNS = [
|
|
38
|
+
/\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
|
|
39
|
+
/\bsk-proj-[A-Za-z0-9_-]{20,}\b/g,
|
|
40
|
+
/\bsk-[A-Za-z0-9]{20,}\b/g,
|
|
41
|
+
/\bgsk_[A-Za-z0-9]{20,}\b/g,
|
|
42
|
+
/\bBearer\s+[A-Za-z0-9._-]{20,}/g,
|
|
43
|
+
/\bshadow-[A-Za-z0-9._-]{20,}/g,
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
function redact(line) {
|
|
47
|
+
let result = line
|
|
48
|
+
for (const pattern of KEY_PATTERNS) {
|
|
49
|
+
pattern.lastIndex = 0
|
|
50
|
+
result = result.replace(pattern, '[REDACTED]')
|
|
51
|
+
}
|
|
52
|
+
return result
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function loadJson(path, fallback) {
|
|
56
|
+
if (!existsSync(path)) return fallback
|
|
57
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'))
|
|
58
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return fallback
|
|
59
|
+
return parsed
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveEnvPlaceholders(value) {
|
|
63
|
+
return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, key) => process.env[key] ?? '')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function assertAllowedPath(path) {
|
|
67
|
+
const absolute = resolve(path)
|
|
68
|
+
if (!ALLOWED_FILE_ROOTS.some((root) => absolute === root || absolute.startsWith(`${root}/`))) {
|
|
69
|
+
throw new Error(`Refusing to materialize runtime file outside allowed roots: ${path}`)
|
|
70
|
+
}
|
|
71
|
+
return absolute
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function modeForPath(path) {
|
|
75
|
+
if (path.endsWith('/.env') || path.endsWith('.yaml') || path.endsWith('.json')) return 0o600
|
|
76
|
+
return 0o644
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function materializeRuntimeFiles() {
|
|
80
|
+
const files = loadJson(RUNTIME_FILES_PATH, {})
|
|
81
|
+
for (const [path, content] of Object.entries(files)) {
|
|
82
|
+
if (typeof content !== 'string') continue
|
|
83
|
+
const absolute = assertAllowedPath(path)
|
|
84
|
+
const mode = modeForPath(absolute)
|
|
85
|
+
mkdirSync(dirname(absolute), { recursive: true })
|
|
86
|
+
writeFileSync(absolute, resolveEnvPlaceholders(content), { encoding: 'utf-8', mode })
|
|
87
|
+
chmodSync(absolute, mode)
|
|
88
|
+
console.log(`[entrypoint] wrote ${absolute}`)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function materializeCredentialFiles() {
|
|
93
|
+
const runtimeExtensions = loadJson(RUNTIME_EXTENSIONS_PATH, {})
|
|
94
|
+
const files = Array.isArray(runtimeExtensions.credentialFiles)
|
|
95
|
+
? runtimeExtensions.credentialFiles
|
|
96
|
+
: []
|
|
97
|
+
for (const file of files) {
|
|
98
|
+
if (!file || typeof file.envKey !== 'string' || typeof file.path !== 'string') continue
|
|
99
|
+
const value = process.env[file.envKey]
|
|
100
|
+
if (!value) continue
|
|
101
|
+
const absolute = assertAllowedPath(file.path)
|
|
102
|
+
const mode =
|
|
103
|
+
typeof file.mode === 'string' && /^[0-7]{3,4}$/.test(file.mode)
|
|
104
|
+
? Number.parseInt(file.mode, 8)
|
|
105
|
+
: 0o600
|
|
106
|
+
mkdirSync(dirname(absolute), { recursive: true })
|
|
107
|
+
writeFileSync(absolute, value, { encoding: 'utf-8', mode })
|
|
108
|
+
chmodSync(absolute, mode)
|
|
109
|
+
console.log(`[entrypoint] materialized credential file ${absolute}`)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function entriesWithMarker(root, marker) {
|
|
114
|
+
if (!existsSync(root)) return []
|
|
115
|
+
if (existsSync(join(root, marker))) return [{ source: root, name: root.split('/').pop() }]
|
|
116
|
+
return readdirSync(root)
|
|
117
|
+
.map((name) => ({ source: join(root, name), name }))
|
|
118
|
+
.filter((entry) => {
|
|
119
|
+
try {
|
|
120
|
+
return statSync(entry.source).isDirectory() && existsSync(join(entry.source, marker))
|
|
121
|
+
} catch {
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function subagentEntries(root) {
|
|
128
|
+
if (!existsSync(root)) return []
|
|
129
|
+
return readdirSync(root)
|
|
130
|
+
.map((name) => ({ source: join(root, name), name }))
|
|
131
|
+
.filter((entry) => {
|
|
132
|
+
try {
|
|
133
|
+
const stat = statSync(entry.source)
|
|
134
|
+
if (stat.isFile()) return entry.name.endsWith('.md')
|
|
135
|
+
return stat.isDirectory()
|
|
136
|
+
} catch {
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function copyIfMissing(source, destination) {
|
|
143
|
+
if (existsSync(destination)) return false
|
|
144
|
+
mkdirSync(dirname(destination), { recursive: true })
|
|
145
|
+
cpSync(source, destination, { recursive: true })
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isAllowedPluginAssetRoot(path) {
|
|
150
|
+
const absolute = resolve(path)
|
|
151
|
+
return [RUNNER_HOME, '/home/openclaw', '/workspace'].some(
|
|
152
|
+
(root) => absolute === root || absolute.startsWith(`${root}/`),
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function materializePluginRuntimeAssets() {
|
|
157
|
+
const runtimeExtensions = loadJson(RUNTIME_EXTENSIONS_PATH, {})
|
|
158
|
+
const skillRoots = Array.isArray(runtimeExtensions.skillSources)
|
|
159
|
+
? runtimeExtensions.skillSources
|
|
160
|
+
.map((source) => (typeof source?.targetPath === 'string' ? source.targetPath : undefined))
|
|
161
|
+
.filter((path) => path && isAllowedPluginAssetRoot(path))
|
|
162
|
+
.filter(Boolean)
|
|
163
|
+
: []
|
|
164
|
+
const subagentRoots = Array.isArray(runtimeExtensions.subagentSources)
|
|
165
|
+
? runtimeExtensions.subagentSources
|
|
166
|
+
.map((source) => (typeof source?.targetPath === 'string' ? source.targetPath : undefined))
|
|
167
|
+
.filter((path) => path && isAllowedPluginAssetRoot(path))
|
|
168
|
+
.filter(Boolean)
|
|
169
|
+
: []
|
|
170
|
+
const skillDestinations = ['/workspace/.agents/skills', join(RUNNER_HOME, '.hermes/skills')]
|
|
171
|
+
const subagentDestinations = ['/workspace/.agents/agents', join(RUNNER_HOME, '.hermes/agents')]
|
|
172
|
+
|
|
173
|
+
for (const root of skillRoots) {
|
|
174
|
+
for (const entry of entriesWithMarker(root, 'SKILL.md')) {
|
|
175
|
+
for (const destinationRoot of skillDestinations) {
|
|
176
|
+
if (copyIfMissing(entry.source, join(destinationRoot, entry.name))) {
|
|
177
|
+
console.log(`[entrypoint] mirrored plugin skill ${entry.name} to ${destinationRoot}`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const root of subagentRoots) {
|
|
183
|
+
for (const entry of subagentEntries(root)) {
|
|
184
|
+
for (const destinationRoot of subagentDestinations) {
|
|
185
|
+
if (copyIfMissing(entry.source, join(destinationRoot, entry.name))) {
|
|
186
|
+
console.log(`[entrypoint] mirrored plugin subagent ${entry.name} to ${destinationRoot}`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function startHealthServer() {
|
|
194
|
+
const server = createServer((req, res) => {
|
|
195
|
+
if (req.url === '/health' || req.url === '/ready') {
|
|
196
|
+
res.writeHead(ready ? 200 : 503, { 'Content-Type': 'application/json' })
|
|
197
|
+
res.end(JSON.stringify({ status: ready ? 'ready' : 'starting', runtime: RUNTIME_NAME }))
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
if (req.url === '/live') {
|
|
201
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
202
|
+
res.end(JSON.stringify({ status: 'live', runtime: RUNTIME_NAME }))
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
res.writeHead(404)
|
|
206
|
+
res.end()
|
|
207
|
+
})
|
|
208
|
+
server.listen(HEALTH_PORT, '0.0.0.0', () => {
|
|
209
|
+
console.log(`[entrypoint] health server listening on :${HEALTH_PORT}`)
|
|
210
|
+
})
|
|
211
|
+
return server
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function verifyBinary(command, args) {
|
|
215
|
+
const result = spawnSync(command, args, {
|
|
216
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
217
|
+
timeout: 10_000,
|
|
218
|
+
})
|
|
219
|
+
if (result.status !== 0) {
|
|
220
|
+
throw new Error(`${command} ${args.join(' ')} failed: ${result.stderr?.toString() ?? ''}`)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function startHermes() {
|
|
225
|
+
mkdirSync(LOG_DIR, { recursive: true })
|
|
226
|
+
const proc = spawn('hermes', ['gateway'], {
|
|
227
|
+
env: {
|
|
228
|
+
...process.env,
|
|
229
|
+
HERMES_HOME: process.env.HERMES_HOME ?? join(RUNNER_HOME, '.hermes'),
|
|
230
|
+
},
|
|
231
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
232
|
+
cwd: '/workspace',
|
|
233
|
+
})
|
|
234
|
+
child = proc
|
|
235
|
+
proc.stdout.on('data', (chunk) => process.stdout.write(redact(chunk.toString())))
|
|
236
|
+
proc.stderr.on('data', (chunk) => process.stderr.write(redact(chunk.toString())))
|
|
237
|
+
proc.on('exit', (code, signal) => {
|
|
238
|
+
ready = false
|
|
239
|
+
console.error(`[entrypoint] hermes exited code=${code ?? 'null'} signal=${signal ?? 'null'}`)
|
|
240
|
+
process.exit(code ?? 1)
|
|
241
|
+
})
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
if (!proc.killed) ready = true
|
|
244
|
+
}, 3000)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function setupSignals(server) {
|
|
248
|
+
const shutdown = (signal) => {
|
|
249
|
+
ready = false
|
|
250
|
+
server.close()
|
|
251
|
+
if (child && !child.killed) child.kill(signal)
|
|
252
|
+
setTimeout(() => process.exit(0), 5000).unref()
|
|
253
|
+
}
|
|
254
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
255
|
+
process.on('SIGINT', () => shutdown('SIGINT'))
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function main() {
|
|
259
|
+
console.log(`[entrypoint] ${RUNTIME_NAME} starting`)
|
|
260
|
+
mkdirSync('/workspace', { recursive: true })
|
|
261
|
+
mkdirSync('/etc/shadowob', { recursive: true })
|
|
262
|
+
materializeRuntimeFiles()
|
|
263
|
+
materializeCredentialFiles()
|
|
264
|
+
materializePluginRuntimeAssets()
|
|
265
|
+
|
|
266
|
+
if (process.env.SHADOW_RUNNER_VALIDATE_ONLY === '1') {
|
|
267
|
+
verifyBinary('hermes', ['--version'])
|
|
268
|
+
verifyBinary('shadowob', ['--help'])
|
|
269
|
+
verifyBinary('shadowob-connector', ['--help'])
|
|
270
|
+
ready = true
|
|
271
|
+
console.log('[entrypoint] validation completed')
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const server = startHealthServer()
|
|
276
|
+
setupSignals(server)
|
|
277
|
+
startHermes()
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
main().catch((err) => {
|
|
281
|
+
console.error('[entrypoint] Fatal:', err)
|
|
282
|
+
process.exit(1)
|
|
283
|
+
})
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.7
|
|
2
|
+
|
|
3
|
+
# ─── OpenClaw Runner ──────────────────────────────────────────────────────
|
|
4
|
+
# Container image for OpenClaw gateway runtime.
|
|
5
|
+
#
|
|
6
|
+
# User: shadow:1000
|
|
7
|
+
# Home: /home/shadow
|
|
8
|
+
# State: /home/shadow/.openclaw (emptyDir mount)
|
|
9
|
+
# Config: /etc/openclaw (ConfigMap mount, read-only)
|
|
10
|
+
# Logs: /var/log/openclaw (emptyDir mount)
|
|
11
|
+
#
|
|
12
|
+
# Build from the repository root so the image can include the local
|
|
13
|
+
# packages/openclaw-shadowob build:
|
|
14
|
+
# docker build -t ghcr.io/buggyblues/openclaw-runner:latest \
|
|
15
|
+
# -f apps/cloud/images/openclaw-runner/Dockerfile .
|
|
16
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
# ── Stage 1: Builder ─────────────────────────────────────────────────────
|
|
19
|
+
FROM node:22-bookworm-slim AS builder
|
|
20
|
+
|
|
21
|
+
WORKDIR /build
|
|
22
|
+
|
|
23
|
+
ARG OPENCLAW_VERSION=2026.5.7
|
|
24
|
+
ARG SHADOWOB_OPENCLAW_PLUGIN_VERSION=latest
|
|
25
|
+
ARG PLAYWRIGHT_VERSION=1.59.1
|
|
26
|
+
|
|
27
|
+
# git is required by some npm packages during install. Enable pnpm here so the
|
|
28
|
+
# local Shadow workspace packages are built inside the image, not copied as
|
|
29
|
+
# host-generated dist artifacts.
|
|
30
|
+
RUN apt-get update && \
|
|
31
|
+
apt-get install -y --no-install-recommends ca-certificates git && \
|
|
32
|
+
rm -rf /var/lib/apt/lists/* && \
|
|
33
|
+
corepack enable && \
|
|
34
|
+
corepack prepare pnpm@10.19.0 --activate
|
|
35
|
+
|
|
36
|
+
# Install OpenClaw and the published shadowob plugin first. This provides a
|
|
37
|
+
# stable runtime dependency layer; local package source changes below only
|
|
38
|
+
# invalidate the small workspace build and overlay layers.
|
|
39
|
+
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
|
|
40
|
+
npm init -y && \
|
|
41
|
+
npm install --prefer-offline --no-audit --fund=false \
|
|
42
|
+
"openclaw@${OPENCLAW_VERSION}" \
|
|
43
|
+
"@shadowob/openclaw-shadowob@${SHADOWOB_OPENCLAW_PLUGIN_VERSION}" \
|
|
44
|
+
"@shadowob/cli@latest" \
|
|
45
|
+
"@shadowob/connector@latest" \
|
|
46
|
+
"playwright-core@${PLAYWRIGHT_VERSION}"
|
|
47
|
+
|
|
48
|
+
# Cleanup dev artifacts to reduce image size. This depends only on npm deps,
|
|
49
|
+
# so local plugin edits do not force the dependency install/cleanup layers.
|
|
50
|
+
RUN find node_modules -type d \( -name ".github" -o -name "test" -o -name "tests" \
|
|
51
|
+
-o -name "__tests__" -o -name "examples" \) \
|
|
52
|
+
-exec rm -rf {} + 2>/dev/null || true && \
|
|
53
|
+
find node_modules -type d -name "docs" ! -path "*/openclaw/docs*" \
|
|
54
|
+
-exec rm -rf {} + 2>/dev/null || true && \
|
|
55
|
+
find node_modules \( -name "*.d.ts" -o -name "*.d.ts.map" -o -name "*.js.map" \
|
|
56
|
+
-o -name "CHANGELOG.md" -o -name "README.md" \) \
|
|
57
|
+
-delete 2>/dev/null || true
|
|
58
|
+
|
|
59
|
+
# Install the minimal workspace needed to build the local Shadow plugin. Copy
|
|
60
|
+
# only lockfiles and manifests first so dependency resolution stays cached while
|
|
61
|
+
# TypeScript source changes.
|
|
62
|
+
WORKDIR /workspace
|
|
63
|
+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json .npmrc ./
|
|
64
|
+
COPY packages/shared/package.json packages/shared/package.json
|
|
65
|
+
COPY packages/sdk/package.json packages/sdk/package.json
|
|
66
|
+
COPY packages/cli/package.json packages/cli/package.json
|
|
67
|
+
COPY packages/openclaw-shadowob/package.json packages/openclaw-shadowob/package.json
|
|
68
|
+
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
|
69
|
+
pnpm config set store-dir /pnpm/store && \
|
|
70
|
+
pnpm install --frozen-lockfile --ignore-scripts \
|
|
71
|
+
--filter @shadowob/openclaw-shadowob... \
|
|
72
|
+
--filter @shadowob/cli...
|
|
73
|
+
|
|
74
|
+
# Build local packages in-container. This makes the image reproducible in CI and
|
|
75
|
+
# keeps it independent from whatever dist/ happens to exist on the host.
|
|
76
|
+
COPY packages/shared packages/shared
|
|
77
|
+
COPY packages/sdk packages/sdk
|
|
78
|
+
COPY packages/cli packages/cli
|
|
79
|
+
COPY packages/openclaw-shadowob packages/openclaw-shadowob
|
|
80
|
+
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
|
81
|
+
pnpm --filter @shadowob/shared build && \
|
|
82
|
+
pnpm --filter @shadowob/sdk build && \
|
|
83
|
+
pnpm --filter @shadowob/cli build && \
|
|
84
|
+
pnpm --filter @shadowob/openclaw-shadowob build
|
|
85
|
+
|
|
86
|
+
# Overlay the freshly built local Shadow packages into the runtime dependency
|
|
87
|
+
# tree, preventing the bundled extension from resolving older published code.
|
|
88
|
+
WORKDIR /build
|
|
89
|
+
RUN for pkg in shared sdk cli; do \
|
|
90
|
+
rm -rf "node_modules/@shadowob/${pkg}" && \
|
|
91
|
+
mkdir -p "node_modules/@shadowob/${pkg}" && \
|
|
92
|
+
cp -r "/workspace/packages/${pkg}/package.json" "/workspace/packages/${pkg}/dist" "node_modules/@shadowob/${pkg}/"; \
|
|
93
|
+
done
|
|
94
|
+
RUN mkdir -p extensions/shadowob && \
|
|
95
|
+
cp -r /workspace/packages/openclaw-shadowob/package.json \
|
|
96
|
+
/workspace/packages/openclaw-shadowob/openclaw.plugin.json \
|
|
97
|
+
/workspace/packages/openclaw-shadowob/dist \
|
|
98
|
+
/workspace/packages/openclaw-shadowob/skills \
|
|
99
|
+
extensions/shadowob/
|
|
100
|
+
|
|
101
|
+
# ── Stage 2: Runner ─────────────────────────────────────────────────────
|
|
102
|
+
FROM node:22-bookworm-slim AS runner
|
|
103
|
+
|
|
104
|
+
LABEL org.opencontainers.image.source="https://github.com/nicepkg/shadow"
|
|
105
|
+
LABEL org.opencontainers.image.description="Shadow Cloud OpenClaw Runner"
|
|
106
|
+
|
|
107
|
+
ARG PLAYWRIGHT_VERSION=1.59.1
|
|
108
|
+
|
|
109
|
+
# Install runtime dependencies (rarely changes → good cache layer). Chromium
|
|
110
|
+
# and Playwright browsers are intentionally baked into the image so OpenClaw
|
|
111
|
+
# browser/canvas tools do not try to bootstrap them after the gateway is live.
|
|
112
|
+
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
|
113
|
+
RUN apt-get update && \
|
|
114
|
+
apt-get install -y --no-install-recommends \
|
|
115
|
+
ca-certificates \
|
|
116
|
+
chromium \
|
|
117
|
+
chromium-driver \
|
|
118
|
+
curl \
|
|
119
|
+
fonts-liberation \
|
|
120
|
+
fonts-noto-cjk \
|
|
121
|
+
fonts-noto-color-emoji \
|
|
122
|
+
git \
|
|
123
|
+
libasound2 \
|
|
124
|
+
libatk-bridge2.0-0 \
|
|
125
|
+
libatk1.0-0 \
|
|
126
|
+
libcups2 \
|
|
127
|
+
libdrm2 \
|
|
128
|
+
libgbm1 \
|
|
129
|
+
libgtk-3-0 \
|
|
130
|
+
libnss3 \
|
|
131
|
+
libx11-xcb1 \
|
|
132
|
+
libxcomposite1 \
|
|
133
|
+
libxdamage1 \
|
|
134
|
+
libxrandr2 \
|
|
135
|
+
python-is-python3 \
|
|
136
|
+
python3 \
|
|
137
|
+
python3-pip \
|
|
138
|
+
python3-venv \
|
|
139
|
+
tini \
|
|
140
|
+
xdg-utils && \
|
|
141
|
+
rm -rf /var/lib/apt/lists/* && \
|
|
142
|
+
mkdir -p /ms-playwright && \
|
|
143
|
+
npx -y "playwright@${PLAYWRIGHT_VERSION}" install --with-deps chromium && \
|
|
144
|
+
chmod -R 755 /ms-playwright
|
|
145
|
+
|
|
146
|
+
# Create non-root user shadow:1000
|
|
147
|
+
# node images ship with node:node at UID/GID 1000 — remove it first
|
|
148
|
+
RUN userdel -r node 2>/dev/null || true; \
|
|
149
|
+
groupdel node 2>/dev/null || true; \
|
|
150
|
+
groupadd -g 1000 shadow; \
|
|
151
|
+
useradd -u 1000 -g shadow -m -d /home/shadow -s /usr/sbin/nologin shadow
|
|
152
|
+
|
|
153
|
+
# Setup directories
|
|
154
|
+
WORKDIR /app
|
|
155
|
+
RUN mkdir -p /home/shadow/.openclaw /etc/openclaw /etc/shadowob /var/log/openclaw \
|
|
156
|
+
/workspace /tmp/openclaw /tmp/npm-cache && \
|
|
157
|
+
ln -s /home/shadow /home/openclaw && \
|
|
158
|
+
chown -R shadow:shadow /home/shadow /etc/shadowob /var/log/openclaw \
|
|
159
|
+
/workspace /tmp/openclaw /tmp/npm-cache /app
|
|
160
|
+
|
|
161
|
+
# Copy stable OpenClaw runtime deps first.
|
|
162
|
+
COPY --from=builder --chown=shadow:shadow /build/node_modules ./node_modules
|
|
163
|
+
COPY --from=builder --chown=shadow:shadow /build/package.json ./package.json
|
|
164
|
+
|
|
165
|
+
# Symlink openclaw to PATH (local install only, no global npm install)
|
|
166
|
+
RUN ln -s /app/node_modules/.bin/openclaw /usr/local/bin/openclaw && \
|
|
167
|
+
ln -s /app/node_modules/.bin/shadowob /usr/local/bin/shadowob && \
|
|
168
|
+
ln -s /app/node_modules/.bin/shadowob-connector /usr/local/bin/shadowob-connector
|
|
169
|
+
|
|
170
|
+
# Copy local extensions before dependency warmup so the image carries the exact
|
|
171
|
+
# plugin runtime dependency set used at startup. The extension layer is still
|
|
172
|
+
# after node_modules, so ordinary plugin edits do not invalidate the large
|
|
173
|
+
# pnpm install/build layers.
|
|
174
|
+
COPY --from=builder --chown=shadow:shadow /build/extensions ./extensions
|
|
175
|
+
|
|
176
|
+
# Stage bundled plugin runtime deps in the image so the first real Buddy reply
|
|
177
|
+
# does not spend minutes installing optional tool/provider packages.
|
|
178
|
+
COPY --chown=shadow:shadow apps/cloud/images/openclaw-runner/warm-runtime-deps.mjs ./warm-runtime-deps.mjs
|
|
179
|
+
ENV OPENCLAW_PLUGIN_STAGE_DIR=/opt/openclaw-runtime-deps
|
|
180
|
+
ENV npm_config_cache=/tmp/npm-cache
|
|
181
|
+
RUN --mount=type=cache,target=/tmp/npm-cache,sharing=locked \
|
|
182
|
+
mkdir -p /opt/openclaw-runtime-deps && \
|
|
183
|
+
node /app/warm-runtime-deps.mjs && \
|
|
184
|
+
chown -R shadow:shadow /opt/openclaw-runtime-deps
|
|
185
|
+
|
|
186
|
+
# Copy entrypoint (small, changes often → last COPY for cache)
|
|
187
|
+
COPY --chown=shadow:shadow apps/cloud/images/openclaw-runner/entrypoint.mjs ./entrypoint.mjs
|
|
188
|
+
|
|
189
|
+
# Health check
|
|
190
|
+
HEALTHCHECK --interval=15s --timeout=5s --start-period=30s --retries=3 \
|
|
191
|
+
CMD curl -f http://localhost:3100/health || exit 1
|
|
192
|
+
|
|
193
|
+
EXPOSE 3100
|
|
194
|
+
|
|
195
|
+
ENV NODE_ENV=production
|
|
196
|
+
ENV HOME=/home/shadow
|
|
197
|
+
ENV OPENCLAW_HEALTH_PORT=3100
|
|
198
|
+
ENV OPENCLAW_GATEWAY_PORT=3101
|
|
199
|
+
ENV OPENCLAW_NO_RESPAWN=1
|
|
200
|
+
ENV OPENCLAW_MODEL_PRICING_FETCH_TIMEOUT_MS=2500
|
|
201
|
+
ENV OPENCLAW_SKIP_STARTUP_MODEL_PREWARM=1
|
|
202
|
+
ENV OPENCLAW_PLUGIN_STAGE_DIR=/opt/openclaw-runtime-deps
|
|
203
|
+
ENV CHROME_BIN=/usr/bin/chromium
|
|
204
|
+
ENV CHROMIUM_PATH=/usr/bin/chromium
|
|
205
|
+
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
|
206
|
+
|
|
207
|
+
# Run as non-root
|
|
208
|
+
USER shadow
|
|
209
|
+
|
|
210
|
+
# Use tini as PID 1 for proper signal handling
|
|
211
|
+
ENTRYPOINT ["tini", "--"]
|
|
212
|
+
CMD ["node", "entrypoint.mjs"]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# OpenClaw Runner Research
|
|
2
|
+
|
|
3
|
+
Research date: 2026-05-14.
|
|
4
|
+
|
|
5
|
+
## Target role
|
|
6
|
+
|
|
7
|
+
`openclaw-runner` remains the one runner whose native process boundary is the
|
|
8
|
+
OpenClaw gateway. It should keep using the ShadowOB OpenClaw plugin and the
|
|
9
|
+
OpenClaw configuration adapter. The refactor should isolate this path rather
|
|
10
|
+
than letting it define the config contract for every other runner.
|
|
11
|
+
|
|
12
|
+
## Current repository state
|
|
13
|
+
|
|
14
|
+
The current image already follows this model more closely than the ACP runners:
|
|
15
|
+
|
|
16
|
+
- Dockerfile installs OpenClaw, the ShadowOB OpenClaw plugin, ShadowOB CLI, and
|
|
17
|
+
ShadowOB connector CLI.
|
|
18
|
+
- `entrypoint.mjs` reads `/etc/openclaw/config.json`.
|
|
19
|
+
- Runtime extensions are read from `/etc/openclaw/runtime-extensions.json`.
|
|
20
|
+
- The generated OpenClaw config is written outside mutable state by default.
|
|
21
|
+
- File logging goes to `/var/log/openclaw/entrypoint.log`.
|
|
22
|
+
- Shadow slash command artifacts are surfaced through
|
|
23
|
+
`SHADOW_SLASH_COMMANDS_PATH`.
|
|
24
|
+
|
|
25
|
+
## Native configuration surfaces
|
|
26
|
+
|
|
27
|
+
OpenClaw configuration is broad and should remain represented as native
|
|
28
|
+
OpenClaw config when this runner is selected:
|
|
29
|
+
|
|
30
|
+
| Concern | OpenClaw config area |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| Gateway process | `gateway.*` for port, bind mode, auth, TLS, push, reload, remote access. |
|
|
33
|
+
| Channels | `channels.*` plus plugin channels such as `channels.shadowob`. |
|
|
34
|
+
| Agents and workspaces | `agents.defaults`, `agents.list`, routing, workspace, sandbox, heartbeat. |
|
|
35
|
+
| Models | `agents.defaults.model`, `agents.defaults.models`, `models.providers`, failover and dynamic discovery. |
|
|
36
|
+
| Skills | `skills.*`, `agents.defaults.skills`, `agents.list[].skills`. |
|
|
37
|
+
| MCP | `mcp.*` and tool-related config. |
|
|
38
|
+
| Automation | `cron.*`, `hooks.*`, tasks, standing orders, and background task config. |
|
|
39
|
+
| Plugins | `plugins.*`, plugin entries, plugin resources, runtime extensions. |
|
|
40
|
+
| Logs | `openclaw logs`, gateway logs, and container logs under `/var/log/openclaw`. |
|
|
41
|
+
|
|
42
|
+
## Schema and type anchors
|
|
43
|
+
|
|
44
|
+
- Static schema URL: none found in the official docs.
|
|
45
|
+
- Official schema source: `openclaw config schema` prints the live JSON Schema
|
|
46
|
+
used by validation and Control UI.
|
|
47
|
+
- Official drill-down source: gateway `config.schema.lookup` returns
|
|
48
|
+
path-scoped schema nodes.
|
|
49
|
+
- Cloud type anchor: `OpenClawConfig` and related types under
|
|
50
|
+
`apps/cloud/src/config/schema/openclaw.schema.ts`.
|
|
51
|
+
- Test rule: adapter unit tests should run a generated config through OpenClaw's
|
|
52
|
+
schema command or a checked-in fixture generated by the same command.
|
|
53
|
+
|
|
54
|
+
## Provider and authentication notes
|
|
55
|
+
|
|
56
|
+
- OpenClaw is the only phase-1 runner that should consume OpenClaw-native
|
|
57
|
+
`models.providers`, failover, and dynamic discovery config.
|
|
58
|
+
- Provider secrets must remain env-backed or secret-file backed. ConfigMaps may
|
|
59
|
+
contain `${env:...}` references but not raw API keys.
|
|
60
|
+
- Any subscription/OAuth provider support is OpenClaw-provider specific; it must
|
|
61
|
+
not become the compatibility path for Claude Code, Codex, OpenCode, Gemini, or
|
|
62
|
+
Hermes adapters.
|
|
63
|
+
- Cost/audit should continue to use OpenClaw's status and usage surfaces for
|
|
64
|
+
OpenClaw only. Native runners collect their own usage/log surfaces.
|
|
65
|
+
|
|
66
|
+
## Security, audit, cost, network, and tools
|
|
67
|
+
|
|
68
|
+
- Permissions: `tools.profile`, `tools.allow`, `tools.deny`, per-agent
|
|
69
|
+
`agents.list[].tools.*`, and `tools.exec.*`; deny must win over allow.
|
|
70
|
+
- Tool surface: built-in tool groups, plugin tools, MCP tools, and bundled tools
|
|
71
|
+
must resolve before model invocation. An explicit allowlist that resolves to
|
|
72
|
+
zero tools should fail closed.
|
|
73
|
+
- Sandbox: `agents.defaults.sandbox.*` and `agents.list[].sandbox.*` decide
|
|
74
|
+
where tool execution runs; gateway process itself is not sandboxed.
|
|
75
|
+
- Elevated mode: `tools.elevated.*` is an exec-only escape hatch and cannot
|
|
76
|
+
override tool policy.
|
|
77
|
+
- Exec approvals: host exec approval state combines OpenClaw config and
|
|
78
|
+
host-local `~/.openclaw/exec-approvals.json`.
|
|
79
|
+
- Network: sandbox Docker network defaults to no network; browser sandbox has
|
|
80
|
+
separate CDP and container network controls.
|
|
81
|
+
- Cost/audit: OpenClaw `/status` and API usage/cost docs expose token/cost
|
|
82
|
+
estimates for API-key auth paths; runner logs must keep those events while
|
|
83
|
+
redacting credentials.
|
|
84
|
+
- Logs: collect OpenClaw gateway logs, entrypoint logs, sandbox explain output,
|
|
85
|
+
and plugin load warnings.
|
|
86
|
+
|
|
87
|
+
## Shadow integration
|
|
88
|
+
|
|
89
|
+
Keep ShadowOB integration as an OpenClaw plugin concern:
|
|
90
|
+
|
|
91
|
+
- plugin package: `@shadowob/openclaw-shadowob`
|
|
92
|
+
- config section: `channels.shadowob`
|
|
93
|
+
- runtime artifacts: slash commands, credential files, plugin resources, and
|
|
94
|
+
verification checks from Cloud plugin runtime extensions
|
|
95
|
+
- official OpenClaw slash commands are generated into
|
|
96
|
+
`/etc/shadowob/slash-commands.json` with `dispatch=passthrough`, registered
|
|
97
|
+
with Shadow for the input picker, and then forwarded to OpenClaw as the
|
|
98
|
+
original standalone `/...` message instead of being rewritten into a prompt
|
|
99
|
+
- plugin-provided slash command indexes are loaded as additional runtime
|
|
100
|
+
artifacts after the official OpenClaw catalog. Duplicate command names keep
|
|
101
|
+
the first loaded definition, so plugin commands do not replace OpenClaw's
|
|
102
|
+
official commands.
|
|
103
|
+
|
|
104
|
+
The OpenClaw command catalog is owned by
|
|
105
|
+
`apps/cloud/src/runtimes/slash-commands/openclaw.ts`. The researched official
|
|
106
|
+
list includes `/new`, `/compact`, `/stop`, `/session`, `/think`, `/verbose`,
|
|
107
|
+
`/fast`, `/reasoning`, `/elevated`, `/exec`, `/model`, `/models`, `/queue`,
|
|
108
|
+
`/help`, `/commands`, `/tools`, `/status`, `/tasks`, `/context`,
|
|
109
|
+
`/export-session`, `/whoami`, `/skill`, `/allowlist`, `/approve`, `/btw`,
|
|
110
|
+
`/subagents`, `/acp`, `/focus`, `/unfocus`, `/agents`, `/kill`, `/steer`,
|
|
111
|
+
`/config`, `/mcp`, `/plugins`, `/debug`, `/usage`, `/tts`, `/restart`,
|
|
112
|
+
`/activation`, `/send`, and `/bash`.
|
|
113
|
+
|
|
114
|
+
This runner can continue to support OpenClaw-native slash commands, skills,
|
|
115
|
+
cron jobs, hooks, and channels. No cc-connect bridge is needed.
|
|
116
|
+
|
|
117
|
+
## Migration implications
|
|
118
|
+
|
|
119
|
+
- `buildOpenClawConfig` should move behind, or be invoked only by, the OpenClaw
|
|
120
|
+
runner adapter.
|
|
121
|
+
- OpenClaw plugin fragments should not be merged into non-OpenClaw runner
|
|
122
|
+
packages.
|
|
123
|
+
- Existing `openclaw-runner` health checks and log redaction should be kept as
|
|
124
|
+
the baseline for the OpenClaw image only.
|
|
125
|
+
- Any Cloud API field named `openclaw` should be documented as OpenClaw-specific
|
|
126
|
+
instead of generic runtime configuration.
|
|
127
|
+
|
|
128
|
+
## Adapter and smoke tests
|
|
129
|
+
|
|
130
|
+
Unit tests:
|
|
131
|
+
|
|
132
|
+
- Generated config validates with OpenClaw schema.
|
|
133
|
+
- Tool profile, allow, deny, sandbox, elevated, and exec approval fields map
|
|
134
|
+
exactly from Cloud policy inputs.
|
|
135
|
+
- Plugin runtime extensions still materialize slash command artifacts,
|
|
136
|
+
credential files, MCP servers, skills, and subagents.
|
|
137
|
+
- Secret refs stay out of `configData` snapshots.
|
|
138
|
+
|
|
139
|
+
Container smoke:
|
|
140
|
+
|
|
141
|
+
- Start image with a minimal ShadowOB config and confirm gateway health.
|
|
142
|
+
- Run `openclaw config schema` and `openclaw config validate` inside the
|
|
143
|
+
container.
|
|
144
|
+
- Inspect `/var/log/openclaw` for startup logs and redaction.
|
|
145
|
+
- Assert workspace write behavior for `workspaceAccess` modes.
|
|
146
|
+
|
|
147
|
+
## Open questions
|
|
148
|
+
|
|
149
|
+
- Whether Cloud should continue to accept legacy top-level OpenClaw config for a
|
|
150
|
+
non-OpenClaw runtime and translate a small safe subset, or reject it early.
|
|
151
|
+
- Whether OpenClaw runtime extensions should have a parallel runner-extension
|
|
152
|
+
schema for cc-connect and Hermes.
|
|
153
|
+
|
|
154
|
+
## Sources
|
|
155
|
+
|
|
156
|
+
- OpenClaw docs index: https://docs.openclaw.ai/llms.txt
|
|
157
|
+
- Gateway configuration:
|
|
158
|
+
https://docs.openclaw.ai/gateway/configuration
|
|
159
|
+
- Configuration reference:
|
|
160
|
+
https://docs.openclaw.ai/gateway/configuration-reference
|
|
161
|
+
- Sandboxing: https://docs.openclaw.ai/gateway/sandboxing
|
|
162
|
+
- Tools: https://docs.openclaw.ai/tools
|
|
163
|
+
- Slash commands: https://docs.openclaw.ai/tools/slash-commands
|
|
164
|
+
- Exec approvals: https://docs.openclaw.ai/tools/exec-approvals
|
|
165
|
+
- API usage and costs:
|
|
166
|
+
https://docs.openclaw.ai/reference/api-usage-costs
|
|
167
|
+
- Cron jobs: https://docs.openclaw.ai/automation/cron-jobs
|
|
168
|
+
- Hooks: https://docs.openclaw.ai/automation/hooks
|
|
169
|
+
- Skills: https://docs.openclaw.ai/tools/skills
|
|
170
|
+
- Logs CLI: https://docs.openclaw.ai/cli/logs
|