@shadowob/cloud 1.1.7 → 1.1.9
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 +159 -11
- package/dist/{agent-browser-CERTMCDL.js → agent-browser-EI7FIK3X.js} +3 -3
- package/dist/{agent-browser-CIRZRIY4.js → agent-browser-YXE4ES6Q.js} +3 -3
- package/dist/{agent-pack-LF3O5TR4.js → agent-pack-35TFCZKP.js} +1 -1
- package/dist/{agent-pack-RQT27V7R.js → agent-pack-UOG6ZAUL.js} +1 -1
- package/dist/agentmemory-KP5O7GHB.js +101 -0
- package/dist/agentmemory-UV74POU5.js +100 -0
- package/dist/{airtable-BG2Q75G2.js → airtable-CXXS3YUN.js} +3 -3
- package/dist/{airtable-JCQXFM5D.js → airtable-YZ5JR5JC.js} +3 -3
- package/dist/{alipay-TZQI34RB.js → alipay-WED5P3XC.js} +3 -3
- package/dist/{alipay-MZX2XCDB.js → alipay-WJTVREMG.js} +3 -3
- package/dist/{amap-KPCLZYYL.js → amap-AIN23TQ7.js} +3 -3
- package/dist/{amap-5RQB3VGC.js → amap-F6GF7QKB.js} +3 -3
- package/dist/{atlassian-LGOEWYC7.js → atlassian-G6PM6UVM.js} +3 -3
- package/dist/{atlassian-TVS2A4IU.js → atlassian-IDR2NPJC.js} +3 -3
- package/dist/{baidu-appbuilder-QRRL3ETM.js → baidu-appbuilder-JKQIA5TG.js} +3 -3
- package/dist/{baidu-appbuilder-6UMESXHW.js → baidu-appbuilder-KVMCIFYH.js} +3 -3
- package/dist/{baidu-maps-HEPMVP5D.js → baidu-maps-GWMXV6YT.js} +3 -3
- package/dist/{baidu-maps-HXC4FBVP.js → baidu-maps-ZXGT6QZM.js} +3 -3
- package/dist/{baidu-netdisk-G5Q6B5NH.js → baidu-netdisk-J5SLQVWW.js} +3 -3
- package/dist/{baidu-netdisk-RS2K5W2M.js → baidu-netdisk-PONM3DY6.js} +3 -3
- package/dist/{baidu-smartprogram-JHD3XWF6.js → baidu-smartprogram-WJ5JMO6V.js} +3 -3
- package/dist/{baidu-smartprogram-EWTK5WKK.js → baidu-smartprogram-Y5WBR7RX.js} +3 -3
- package/dist/{browserbase-IUIYVYI7.js → browserbase-CGVQHRC4.js} +3 -3
- package/dist/{browserbase-JFO2PCIA.js → browserbase-PVWYTEZC.js} +3 -3
- package/dist/{canva-3YOFL7JS.js → canva-COCBQAT2.js} +3 -3
- package/dist/{canva-FMYN65SM.js → canva-RSTJSEX5.js} +3 -3
- package/dist/{chunk-35LJYCQF.js → chunk-3CT6RQNM.js} +745 -482
- package/dist/{chunk-KODMGZUC.js → chunk-4YO3NA26.js} +1 -1
- package/dist/{chunk-RECNVWMT.js → chunk-6V7MW4HU.js} +17 -3
- package/dist/{chunk-C6OI4ZNO.js → chunk-EVV774KS.js} +1 -1
- package/dist/{chunk-SVMXSIMG.js → chunk-F6CQ6GAG.js} +2 -1
- package/dist/{chunk-JUPAE5IA.js → chunk-OL5VH6RN.js} +72 -69
- package/dist/{chunk-POSVEKIY.js → chunk-OYY64ZSX.js} +17 -3
- package/dist/{chunk-ZHVYNIHA.js → chunk-P5Y6F2NH.js} +745 -482
- package/dist/{chunk-EEFMJYKB.js → chunk-PSK2SYZ3.js} +2 -1
- package/dist/{chunk-6YAYCWGK.js → chunk-PYJRFKPN.js} +1 -1
- package/dist/{chunk-JY2HTT7Q.js → chunk-RMDY3W4V.js} +6 -0
- package/dist/{chunk-EWB7L7IW.js → chunk-X2SREECR.js} +6 -6
- package/dist/{chunk-LXJBQBGL.js → chunk-X5VOIA72.js} +6 -6
- package/dist/{chunk-CTNUKOQE.js → chunk-Y5BJ3EW2.js} +6 -0
- package/dist/{chunk-SAP2DBHO.js → chunk-Y6BKVDG7.js} +1 -1
- package/dist/{chunk-6P2K6QZR.js → chunk-ZGMWSSCC.js} +72 -69
- package/dist/{claude-plugin-577TAQVS.js → claude-plugin-FPN32WMT.js} +1 -1
- package/dist/{claude-plugin-L3MXJJ6J.js → claude-plugin-IYHOVTVL.js} +1 -1
- package/dist/cli.js +930 -149
- package/dist/{cloudflare-RDFPKMM5.js → cloudflare-U3RHJJKK.js} +3 -3
- package/dist/{cloudflare-HBBABPK6.js → cloudflare-ZHN7UGPX.js} +3 -3
- package/dist/{cnb-FLP3QX46.js → cnb-AMXC5I7D.js} +3 -3
- package/dist/{cnb-YAVVEYFB.js → cnb-P3IZ4JTD.js} +3 -3
- package/dist/console/index.html +1 -1
- package/dist/console/static/css/index.f4563d95.css +1 -0
- package/dist/console/static/js/index.020abc71.js +1 -0
- package/dist/{coze-E6VGRNLV.js → coze-66RYMKVB.js} +3 -3
- package/dist/{coze-C6PMDPBI.js → coze-YE3BINXP.js} +3 -3
- package/dist/{dashboard.command-ZMQFKLNQ.js → dashboard.command-BRPZCZER.js} +1 -1
- package/dist/{dashboard.command-2AM45SIT.js → dashboard.command-GUHSJ2CN.js} +1 -1
- package/dist/{dingtalk-JNRNRN7E.js → dingtalk-4RFQG7N2.js} +3 -3
- package/dist/{dingtalk-WZGGIAHJ.js → dingtalk-VNFKXD2P.js} +3 -3
- package/dist/{douyin-miniprogram-AIJPPIZH.js → douyin-miniprogram-UEALAGOS.js} +3 -3
- package/dist/{douyin-miniprogram-HCYZ5NBW.js → douyin-miniprogram-UNB6UO2I.js} +3 -3
- package/dist/{figma-2YYNSCDX.js → figma-A264OWU5.js} +3 -3
- package/dist/{figma-RYOBMENP.js → figma-Y4TGSDZP.js} +3 -3
- package/dist/{firebase-OYSY6HPT.js → firebase-AI3MAGYG.js} +3 -3
- package/dist/{firebase-2IJDDBXX.js → firebase-ZGQARUIH.js} +3 -3
- package/dist/{firecrawl-2T3SBUW7.js → firecrawl-2JW7DMTH.js} +3 -3
- package/dist/{firecrawl-IYYXLAZM.js → firecrawl-UURQ5P5N.js} +3 -3
- package/dist/{flyai-QS5Q6FJR.js → flyai-EJGDMYFA.js} +3 -3
- package/dist/{flyai-7FJ4TRAG.js → flyai-ZFMZBBHJ.js} +3 -3
- package/dist/{gitagent-MWI75OIX.js → gitagent-5SDBYFNA.js} +1 -1
- package/dist/{gitagent-YBMWY7NZ.js → gitagent-ODXPCR4X.js} +1 -1
- package/dist/{gitee-3N7OFOM7.js → gitee-5UMJ4BC7.js} +3 -3
- package/dist/{gitee-KVNK6KLZ.js → gitee-D6NAZTCO.js} +3 -3
- package/dist/{github-LUEC2LID.js → github-JBLDKIA3.js} +3 -3
- package/dist/{github-XRO5Z3GC.js → github-PZQAVEZP.js} +3 -3
- package/dist/{google-ads-VPKWTX67.js → google-ads-BIFQOJ5M.js} +3 -3
- package/dist/{google-ads-A3QAJI4D.js → google-ads-QU3LJE4O.js} +3 -3
- package/dist/{google-analytics-C4UR5ZR2.js → google-analytics-7VZ6YZVA.js} +3 -3
- package/dist/{google-analytics-XDYZA2B7.js → google-analytics-HXMPCL5V.js} +3 -3
- package/dist/{google-workspace-YX35SHHX.js → google-workspace-6SEBJ4VA.js} +2 -2
- package/dist/{google-workspace-LL3EWVHH.js → google-workspace-L3AMJLCF.js} +2 -2
- package/dist/{huawei-xiaoyi-KPWLTSHB.js → huawei-xiaoyi-C6QIJMPM.js} +3 -3
- package/dist/{huawei-xiaoyi-6BSMGJHR.js → huawei-xiaoyi-JGLXWU5P.js} +3 -3
- package/dist/{hubspot-FTIEMNZO.js → hubspot-LACJGE6D.js} +3 -3
- package/dist/{hubspot-DIUHGEDI.js → hubspot-XWPRO4KZ.js} +3 -3
- package/dist/{huggingface-UUXK2RHK.js → huggingface-26QQZK4C.js} +3 -3
- package/dist/{huggingface-MJCOXA7E.js → huggingface-CQICNA2R.js} +3 -3
- package/dist/index.d.ts +1338 -1
- package/dist/index.js +1364 -226
- package/dist/{inference-ai-image-generation-PXV6IG4U.js → inference-ai-image-generation-5KYIUWT6.js} +3 -3
- package/dist/{inference-ai-image-generation-CMI6R5T3.js → inference-ai-image-generation-J2NYDCLZ.js} +3 -3
- package/dist/{inference-sh-7AZOLEFI.js → inference-sh-5SWQTK73.js} +3 -3
- package/dist/{inference-sh-ABQOD3YF.js → inference-sh-PTQF6T3R.js} +3 -3
- package/dist/{init.command-YVG4X6II.js → init.command-C7UKPK2Y.js} +3 -3
- package/dist/{init.command-JKE3SXAS.js → init.command-UNL66BMR.js} +3 -3
- package/dist/{klaviyo-LDPBWBSS.js → klaviyo-4UNPMBFT.js} +3 -3
- package/dist/{klaviyo-6K5YEFNH.js → klaviyo-SLYNEULT.js} +3 -3
- package/dist/{kuaidi100-HGFM5VK2.js → kuaidi100-HZKV5AIS.js} +3 -3
- package/dist/{kuaidi100-UHPFCVXP.js → kuaidi100-ZQUW7GHH.js} +3 -3
- package/dist/lark-HQUZNHDI.js +382 -0
- package/dist/lark-PAV7XWJS.js +381 -0
- package/dist/{linear-T4ORUP7N.js → linear-PYGQ5SLK.js} +3 -3
- package/dist/{linear-7QFSFPOD.js → linear-VJLYNTUF.js} +3 -3
- package/dist/{lovart-PDUXRUHJ.js → lovart-KC6SVNAJ.js} +3 -3
- package/dist/{lovart-QO3SK55T.js → lovart-WVKY4RR4.js} +3 -3
- package/dist/{meta-ads-SCNFI45S.js → meta-ads-E6XT33GI.js} +3 -3
- package/dist/{meta-ads-V6XPZWX3.js → meta-ads-RJ6DWRYN.js} +3 -3
- package/dist/{miclaw-TPPPS2WN.js → miclaw-4BA3A2YN.js} +3 -3
- package/dist/{miclaw-5CNTW7VV.js → miclaw-LUV6DCHX.js} +3 -3
- package/dist/{model-provider-KFB76XV5.js → model-provider-SYXJZ3JD.js} +1 -1
- package/dist/{model-provider-AVSFJSZP.js → model-provider-U7NEYA3Y.js} +1 -1
- package/dist/nature-skills-G76ABIWZ.js +143 -0
- package/dist/nature-skills-SQHMFXKT.js +142 -0
- package/dist/{notion-WFA7KGZZ.js → notion-2JZAKOFP.js} +1 -1
- package/dist/{notion-FZK76MN2.js → notion-O3NO5TJH.js} +1 -1
- package/dist/{oceanengine-3JZUS3PP.js → oceanengine-D23UZGVB.js} +3 -3
- package/dist/{oceanengine-5BRIJVJE.js → oceanengine-WDK2OXX5.js} +3 -3
- package/dist/{opencli-PFXHGCS2.js → opencli-36P63YNU.js} +3 -3
- package/dist/{opencli-VIGRJTGH.js → opencli-SUHDFR33.js} +3 -3
- package/dist/{paypal-Z5JYHIWD.js → paypal-K27SUW3B.js} +3 -3
- package/dist/{paypal-33UADIPR.js → paypal-OPZ3KOV5.js} +3 -3
- package/dist/{playwright-SQAQ3DZG.js → playwright-2ULT3NIC.js} +3 -3
- package/dist/{playwright-MG5WHK47.js → playwright-3Q7LBILG.js} +3 -3
- package/dist/{plugins-HZBWK3WQ.js → plugins-2MITZ4ZD.js} +2 -2
- package/dist/{plugins-I4GD5SZX.js → plugins-UK2QWD6G.js} +2 -2
- package/dist/{posthog-MU5MAJOQ.js → posthog-E3EHXLAN.js} +3 -3
- package/dist/{posthog-RJRRKDWB.js → posthog-KPJVLGX6.js} +3 -3
- package/dist/{salesforce-34FVIJTG.js → salesforce-FGPNG7FB.js} +3 -3
- package/dist/{salesforce-3QZ6OFVO.js → salesforce-TVHISKBC.js} +3 -3
- package/dist/{sentry-PIWW46VA.js → sentry-BZ3J3MZM.js} +3 -3
- package/dist/{sentry-MCIRMACU.js → sentry-XC57YRAJ.js} +3 -3
- package/dist/{seo-suite-WJXMA3S4.js → seo-suite-2MDEDLAB.js} +3 -3
- package/dist/{seo-suite-4SQ3I67Q.js → seo-suite-U75O3QP6.js} +3 -3
- package/dist/{serve.command-XLBJUOV6.js → serve.command-G5RVQFUD.js} +3 -3
- package/dist/{serve.command-RD6I6MFD.js → serve.command-PYGDG7K3.js} +3 -3
- package/dist/{shadowob-PRSMI5MW.js → shadowob-3QZ7DLDW.js} +158 -31
- package/dist/{shadowob-JELOWHWX.js → shadowob-CJLOEKFP.js} +158 -31
- package/dist/{sherlock-2PKY2E2Y.js → sherlock-CQFUHKDH.js} +3 -3
- package/dist/{sherlock-C5ZWPPVT.js → sherlock-DONK2I6E.js} +3 -3
- package/dist/{shopify-GL3NFVGE.js → shopify-NO5GI3WD.js} +3 -3
- package/dist/{shopify-R4G3UXM6.js → shopify-VW2KLKH5.js} +3 -3
- package/dist/{skill-discovery-YPXXV622.js → skill-discovery-6JEPPKKM.js} +3 -3
- package/dist/{skill-discovery-7INAUP4D.js → skill-discovery-PWRAVGIS.js} +3 -3
- package/dist/skills/shadowob-cli/SKILL.md +7 -0
- package/dist/{stripe-LJNPQ3CQ.js → stripe-HCNCKG4C.js} +1 -1
- package/dist/{stripe-C22RR4ZS.js → stripe-IU3KTJ4H.js} +1 -1
- package/dist/{supabase-IRNQ54FJ.js → supabase-KRL7JW2D.js} +3 -3
- package/dist/{supabase-N4ONFJNQ.js → supabase-TYEBTZNO.js} +3 -3
- package/dist/{taobao-aipaas-LRR4GMO3.js → taobao-aipaas-PEUIDOYP.js} +3 -3
- package/dist/{taobao-aipaas-RVKORSF4.js → taobao-aipaas-SA5E4MZA.js} +3 -3
- package/dist/{tapd-TMQRSMFG.js → tapd-6DDIUPVQ.js} +3 -3
- package/dist/{tapd-3JPVJ7XH.js → tapd-OTYLSZGY.js} +3 -3
- package/dist/{tencent-ads-UHC6OPBV.js → tencent-ads-OW2TAMH5.js} +3 -3
- package/dist/{tencent-ads-IGD33LO7.js → tencent-ads-XTQZ27YT.js} +3 -3
- package/dist/{tencent-docs-C3A4J3CJ.js → tencent-docs-6D6A2VCO.js} +3 -3
- package/dist/{tencent-docs-O2SC4FHL.js → tencent-docs-K3TMUIWD.js} +3 -3
- package/dist/{tencent-maps-OQOKHVW2.js → tencent-maps-IZYWITJZ.js} +3 -3
- package/dist/{tencent-maps-HMMWMNF4.js → tencent-maps-SWI7CLQY.js} +3 -3
- package/dist/text-to-cad-B2UP6PKA.js +192 -0
- package/dist/text-to-cad-I4B6VBFV.js +193 -0
- package/dist/{vercel-KOXDDTHX.js → vercel-CCKRC76D.js} +3 -3
- package/dist/{vercel-OLNVDWMG.js → vercel-SBGEMIJJ.js} +3 -3
- package/dist/{webflow-OMJKZM54.js → webflow-C3EHNNSN.js} +3 -3
- package/dist/{webflow-FULU5Q2I.js → webflow-ZFBJH4CR.js} +3 -3
- package/dist/{wechat-miniprogram-skyline-KYCDMQNW.js → wechat-miniprogram-skyline-VNCRERHX.js} +3 -3
- package/dist/{wechat-miniprogram-skyline-VR4FVIQL.js → wechat-miniprogram-skyline-Z5JQUV5Q.js} +3 -3
- package/dist/{wechat-pay-BCMAJ6UW.js → wechat-pay-RPDKPUEB.js} +3 -3
- package/dist/{wechat-pay-YQQKXVUI.js → wechat-pay-XVDGJRF2.js} +3 -3
- package/dist/{wonda-NGWIORYN.js → wonda-EL2P44S7.js} +3 -3
- package/dist/{wonda-RBABXFNM.js → wonda-XK5JK4X3.js} +3 -3
- package/dist/{wordpress-woocommerce-RNA5HB3N.js → wordpress-woocommerce-4MEE5A2M.js} +3 -3
- package/dist/{wordpress-woocommerce-RDIUTHYT.js → wordpress-woocommerce-6ECNM2QU.js} +3 -3
- package/dist/{wps-LUWHMZQQ.js → wps-E4OZEMOF.js} +3 -3
- package/dist/{wps-DAEFQHDE.js → wps-JNQUC4JS.js} +3 -3
- package/dist/{yuque-HCHTJWNI.js → yuque-GLAAOS7X.js} +3 -3
- package/dist/{yuque-KRH5O74J.js → yuque-MEF6VFLJ.js} +3 -3
- package/images/RUNNERS.md +15 -0
- package/images/cc-connect-runner/entrypoint.mjs +228 -0
- package/images/claude-runner/RUNNER.md +5 -3
- package/images/codex-runner/RUNNER.md +5 -3
- package/images/gemini-runner/RUNNER.md +5 -2
- package/images/hermes-runner/RUNNER.md +4 -2
- package/images/hermes-runner/entrypoint.mjs +269 -1
- package/images/openclaw-runner/Dockerfile +1 -0
- package/images/openclaw-runner/RUNNER.md +3 -0
- package/images/openclaw-runner/entrypoint.mjs +249 -1
- package/images/openclaw-runner/warm-runtime-deps.mjs +1 -3
- package/images/opencode-runner/RUNNER.md +5 -3
- package/package.json +3 -3
- package/templates/agent-marketplace-buddy.template.json +4 -1
- package/templates/bmad-method-buddy.template.json +4 -1
- package/templates/code-trainer.template.json +331 -0
- package/templates/gstack-buddy.template.json +4 -1
- package/templates/little-match-girl.template.json +10 -3
- package/dist/console/static/css/index.7f91f806.css +0 -1
- package/dist/console/static/js/index.4487e1ff.js +0 -1
- package/dist/lark-6LNA3LUQ.js +0 -103
- package/dist/lark-URVBZNS4.js +0 -102
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { spawn, spawnSync } from 'node:child_process'
|
|
9
|
+
import { createHash } from 'node:crypto'
|
|
9
10
|
import {
|
|
10
11
|
chmodSync,
|
|
11
12
|
cpSync,
|
|
@@ -24,6 +25,11 @@ const CONFIG_MOUNT = process.env.SHADOW_RUNNER_CONFIG_MOUNT ?? '/etc/openclaw'
|
|
|
24
25
|
const RUNTIME_FILES_PATH = join(CONFIG_MOUNT, 'runtime-files.json')
|
|
25
26
|
const RUNTIME_EXTENSIONS_PATH = join(CONFIG_MOUNT, 'runtime-extensions.json')
|
|
26
27
|
const RUNNER_HOME = process.env.HOME ?? '/home/shadow'
|
|
28
|
+
const HERMES_HOME = process.env.HERMES_HOME ?? join(RUNNER_HOME, '.hermes')
|
|
29
|
+
const TEMPLATE_ROUTINES_PATH =
|
|
30
|
+
process.env.SHADOW_TEMPLATE_ROUTINES_PATH ?? '/etc/shadowob/template-routines.json'
|
|
31
|
+
const HERMES_CRON_STORE_PATH =
|
|
32
|
+
process.env.HERMES_CRON_STORE_PATH ?? join(HERMES_HOME, 'cron', 'jobs.json')
|
|
27
33
|
const HEALTH_PORT = Number.parseInt(
|
|
28
34
|
process.env.SHADOW_RUNNER_HEALTH_PORT ?? process.env.OPENCLAW_GATEWAY_PORT ?? '3100',
|
|
29
35
|
10,
|
|
@@ -76,6 +82,267 @@ function modeForPath(path) {
|
|
|
76
82
|
return 0o644
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
function isPlainObject(value) {
|
|
86
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function stableValue(value) {
|
|
90
|
+
if (Array.isArray(value)) return value.map(stableValue)
|
|
91
|
+
if (!isPlainObject(value)) return value
|
|
92
|
+
return Object.fromEntries(
|
|
93
|
+
Object.entries(value)
|
|
94
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
95
|
+
.map(([key, item]) => [key, stableValue(item)]),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function stableHash(value) {
|
|
100
|
+
return createHash('sha256')
|
|
101
|
+
.update(JSON.stringify(stableValue(value)))
|
|
102
|
+
.digest('hex')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readJsonFile(path, fallback) {
|
|
106
|
+
if (!existsSync(path)) return fallback
|
|
107
|
+
try {
|
|
108
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'))
|
|
109
|
+
return parsed && typeof parsed === 'object' ? parsed : fallback
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.warn(`[entrypoint] Failed to parse ${path}: ${err.message}`)
|
|
112
|
+
return fallback
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parseRoutineEveryMinutes(interval) {
|
|
117
|
+
if (typeof interval !== 'string') return null
|
|
118
|
+
const match = interval.trim().match(/^(\d+)\s*(m|h|d)$/i)
|
|
119
|
+
if (!match) return null
|
|
120
|
+
const amount = Number.parseInt(match[1], 10)
|
|
121
|
+
if (!Number.isFinite(amount) || amount <= 0) return null
|
|
122
|
+
const unit = match[2].toLowerCase()
|
|
123
|
+
return amount * (unit === 'm' ? 1 : unit === 'h' ? 60 : 1440)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isoNow() {
|
|
127
|
+
return new Date().toISOString()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function addMinutesIso(minutes) {
|
|
131
|
+
return new Date(Date.now() + minutes * 60_000).toISOString()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildHermesRoutineSchedule(routine) {
|
|
135
|
+
const schedule = isPlainObject(routine.schedule) ? routine.schedule : {}
|
|
136
|
+
if (typeof schedule.cron === 'string' && schedule.cron.trim()) {
|
|
137
|
+
const expr = schedule.cron.trim()
|
|
138
|
+
return {
|
|
139
|
+
schedule: { kind: 'cron', expr, display: expr },
|
|
140
|
+
scheduleDisplay: expr,
|
|
141
|
+
nextRunAt: null,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const minutes = parseRoutineEveryMinutes(schedule.interval)
|
|
145
|
+
if (minutes) {
|
|
146
|
+
const display = `every ${minutes}m`
|
|
147
|
+
return {
|
|
148
|
+
schedule: { kind: 'interval', minutes, display },
|
|
149
|
+
scheduleDisplay: display,
|
|
150
|
+
nextRunAt: addMinutesIso(minutes),
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resolveShadowobRoutineDelivery(routine) {
|
|
157
|
+
const deliveries = Array.isArray(routine.deliveries) ? routine.deliveries : []
|
|
158
|
+
for (const delivery of deliveries) {
|
|
159
|
+
if (
|
|
160
|
+
!isPlainObject(delivery) ||
|
|
161
|
+
delivery.pluginId !== 'shadowob' ||
|
|
162
|
+
delivery.kind !== 'channel'
|
|
163
|
+
) {
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
const target = isPlainObject(delivery.target) ? delivery.target : {}
|
|
167
|
+
const channelEnvKey =
|
|
168
|
+
typeof target.channelEnvKey === 'string' && target.channelEnvKey.trim()
|
|
169
|
+
? target.channelEnvKey.trim()
|
|
170
|
+
: null
|
|
171
|
+
const channelId =
|
|
172
|
+
(channelEnvKey ? process.env[channelEnvKey] : undefined) ??
|
|
173
|
+
(typeof target.channelId === 'string' ? target.channelId : undefined)
|
|
174
|
+
if (!channelId) continue
|
|
175
|
+
return {
|
|
176
|
+
channelId,
|
|
177
|
+
threadId:
|
|
178
|
+
typeof target.threadId === 'string' && target.threadId.trim()
|
|
179
|
+
? target.threadId.trim()
|
|
180
|
+
: null,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function managedRoutineJobId(routine) {
|
|
187
|
+
const raw = `shadow-template-${routine.agentId ?? 'agent'}-${routine.id ?? 'routine'}`
|
|
188
|
+
.toLowerCase()
|
|
189
|
+
.replace(/[^a-z0-9._:-]+/g, '-')
|
|
190
|
+
.replace(/^-+|-+$/g, '')
|
|
191
|
+
if (raw.length >= 8 && raw.length <= 64) return raw
|
|
192
|
+
return `shadow-template-${stableHash({ agentId: routine.agentId, id: routine.id }).slice(0, 20)}`
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function hermesManagedJobShape(job) {
|
|
196
|
+
return {
|
|
197
|
+
id: job.id,
|
|
198
|
+
name: job.name,
|
|
199
|
+
prompt: job.prompt,
|
|
200
|
+
skills: job.skills,
|
|
201
|
+
skill: job.skill,
|
|
202
|
+
model: job.model,
|
|
203
|
+
provider: job.provider,
|
|
204
|
+
base_url: job.base_url,
|
|
205
|
+
script: job.script,
|
|
206
|
+
no_agent: job.no_agent,
|
|
207
|
+
context_from: job.context_from,
|
|
208
|
+
schedule: job.schedule,
|
|
209
|
+
schedule_display: job.schedule_display,
|
|
210
|
+
repeat: job.repeat,
|
|
211
|
+
enabled: job.enabled,
|
|
212
|
+
state: job.state,
|
|
213
|
+
deliver: job.deliver,
|
|
214
|
+
origin: job.origin,
|
|
215
|
+
enabled_toolsets: job.enabled_toolsets,
|
|
216
|
+
workdir: job.workdir,
|
|
217
|
+
profile: job.profile,
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function buildHermesRoutineJob(routine, now) {
|
|
222
|
+
if (
|
|
223
|
+
!isPlainObject(routine) ||
|
|
224
|
+
typeof routine.id !== 'string' ||
|
|
225
|
+
typeof routine.agentId !== 'string'
|
|
226
|
+
) {
|
|
227
|
+
return null
|
|
228
|
+
}
|
|
229
|
+
const schedule = buildHermesRoutineSchedule(routine)
|
|
230
|
+
const delivery = resolveShadowobRoutineDelivery(routine)
|
|
231
|
+
if (!schedule || !delivery) return null
|
|
232
|
+
|
|
233
|
+
const origin = {
|
|
234
|
+
platform: 'shadowob',
|
|
235
|
+
chat_id: delivery.channelId,
|
|
236
|
+
...(delivery.threadId ? { thread_id: delivery.threadId } : {}),
|
|
237
|
+
}
|
|
238
|
+
const job = {
|
|
239
|
+
id: managedRoutineJobId(routine),
|
|
240
|
+
name:
|
|
241
|
+
typeof routine.title === 'string' && routine.title.trim() ? routine.title.trim() : routine.id,
|
|
242
|
+
prompt: String(routine.prompt ?? ''),
|
|
243
|
+
skills: [],
|
|
244
|
+
skill: null,
|
|
245
|
+
model: null,
|
|
246
|
+
provider: null,
|
|
247
|
+
base_url: null,
|
|
248
|
+
script: null,
|
|
249
|
+
no_agent: false,
|
|
250
|
+
context_from: null,
|
|
251
|
+
schedule: schedule.schedule,
|
|
252
|
+
schedule_display: schedule.scheduleDisplay,
|
|
253
|
+
repeat: { times: null, completed: 0 },
|
|
254
|
+
enabled: routine.enabled !== false,
|
|
255
|
+
state: routine.enabled === false ? 'paused' : 'scheduled',
|
|
256
|
+
paused_at: null,
|
|
257
|
+
paused_reason: null,
|
|
258
|
+
created_at: now,
|
|
259
|
+
next_run_at: schedule.nextRunAt,
|
|
260
|
+
last_run_at: null,
|
|
261
|
+
last_status: null,
|
|
262
|
+
last_error: null,
|
|
263
|
+
last_delivery_error: null,
|
|
264
|
+
deliver: delivery.threadId ? 'origin' : `shadowob:${delivery.channelId}`,
|
|
265
|
+
origin,
|
|
266
|
+
enabled_toolsets: null,
|
|
267
|
+
workdir: '/workspace',
|
|
268
|
+
profile: null,
|
|
269
|
+
}
|
|
270
|
+
const managedSpecHash = stableHash(hermesManagedJobShape(job))
|
|
271
|
+
return {
|
|
272
|
+
...job,
|
|
273
|
+
shadowTemplateRoutine: {
|
|
274
|
+
version: 1,
|
|
275
|
+
routineId: routine.id,
|
|
276
|
+
agentId: routine.agentId,
|
|
277
|
+
sourceHash: typeof routine.sourceHash === 'string' ? routine.sourceHash : null,
|
|
278
|
+
managedSpecHash,
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function syncTemplateRoutinesToHermesCron() {
|
|
284
|
+
if (!existsSync(TEMPLATE_ROUTINES_PATH)) return
|
|
285
|
+
const seed = readJsonFile(TEMPLATE_ROUTINES_PATH, null)
|
|
286
|
+
const routines = Array.isArray(seed?.routines) ? seed.routines : []
|
|
287
|
+
if (routines.length === 0) return
|
|
288
|
+
|
|
289
|
+
const now = isoNow()
|
|
290
|
+
const store = readJsonFile(HERMES_CRON_STORE_PATH, { jobs: [] })
|
|
291
|
+
const jobs = Array.isArray(store.jobs) ? store.jobs.filter(Boolean) : []
|
|
292
|
+
let changed = false
|
|
293
|
+
|
|
294
|
+
for (const routine of routines) {
|
|
295
|
+
const desired = buildHermesRoutineJob(routine, now)
|
|
296
|
+
if (!desired) {
|
|
297
|
+
console.warn(
|
|
298
|
+
`[entrypoint] Skipping invalid template routine: ${JSON.stringify(routine?.id ?? null)}`,
|
|
299
|
+
)
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
const existingIndex = jobs.findIndex((job) => {
|
|
303
|
+
const marker = isPlainObject(job?.shadowTemplateRoutine) ? job.shadowTemplateRoutine : null
|
|
304
|
+
return marker?.routineId === desired.shadowTemplateRoutine.routineId || job?.id === desired.id
|
|
305
|
+
})
|
|
306
|
+
if (existingIndex < 0) {
|
|
307
|
+
jobs.push(desired)
|
|
308
|
+
changed = true
|
|
309
|
+
console.log(`[entrypoint] Seeded Hermes cron routine: ${desired.id}`)
|
|
310
|
+
continue
|
|
311
|
+
}
|
|
312
|
+
const existing = jobs[existingIndex]
|
|
313
|
+
const marker = isPlainObject(existing.shadowTemplateRoutine)
|
|
314
|
+
? existing.shadowTemplateRoutine
|
|
315
|
+
: null
|
|
316
|
+
const currentManagedSpecHash = stableHash(hermesManagedJobShape(existing))
|
|
317
|
+
if (!marker || marker.managedSpecHash !== currentManagedSpecHash) {
|
|
318
|
+
console.log(
|
|
319
|
+
`[entrypoint] Preserved user-edited Hermes cron routine: ${existing.id ?? desired.id}`,
|
|
320
|
+
)
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
if (marker.managedSpecHash === desired.shadowTemplateRoutine.managedSpecHash) continue
|
|
324
|
+
jobs[existingIndex] = {
|
|
325
|
+
...desired,
|
|
326
|
+
id: existing.id ?? desired.id,
|
|
327
|
+
created_at: existing.created_at ?? desired.created_at,
|
|
328
|
+
last_run_at: existing.last_run_at ?? null,
|
|
329
|
+
last_status: existing.last_status ?? null,
|
|
330
|
+
last_error: existing.last_error ?? null,
|
|
331
|
+
last_delivery_error: existing.last_delivery_error ?? null,
|
|
332
|
+
}
|
|
333
|
+
changed = true
|
|
334
|
+
console.log(`[entrypoint] Updated Hermes cron routine from template: ${jobs[existingIndex].id}`)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!changed) return
|
|
338
|
+
mkdirSync(dirname(HERMES_CRON_STORE_PATH), { recursive: true })
|
|
339
|
+
writeFileSync(HERMES_CRON_STORE_PATH, `${JSON.stringify({ jobs, updated_at: now }, null, 2)}\n`, {
|
|
340
|
+
encoding: 'utf-8',
|
|
341
|
+
mode: 0o600,
|
|
342
|
+
})
|
|
343
|
+
chmodSync(HERMES_CRON_STORE_PATH, 0o600)
|
|
344
|
+
}
|
|
345
|
+
|
|
79
346
|
function materializeRuntimeFiles() {
|
|
80
347
|
const files = loadJson(RUNTIME_FILES_PATH, {})
|
|
81
348
|
for (const [path, content] of Object.entries(files)) {
|
|
@@ -226,7 +493,7 @@ function startHermes() {
|
|
|
226
493
|
const proc = spawn('hermes', ['gateway'], {
|
|
227
494
|
env: {
|
|
228
495
|
...process.env,
|
|
229
|
-
HERMES_HOME
|
|
496
|
+
HERMES_HOME,
|
|
230
497
|
},
|
|
231
498
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
232
499
|
cwd: '/workspace',
|
|
@@ -262,6 +529,7 @@ async function main() {
|
|
|
262
529
|
materializeRuntimeFiles()
|
|
263
530
|
materializeCredentialFiles()
|
|
264
531
|
materializePluginRuntimeAssets()
|
|
532
|
+
syncTemplateRoutinesToHermesCron()
|
|
265
533
|
|
|
266
534
|
if (process.env.SHADOW_RUNNER_VALIDATE_ONLY === '1') {
|
|
267
535
|
verifyBinary('hermes', ['--version'])
|
|
@@ -61,6 +61,7 @@ RUN find node_modules -type d \( -name ".github" -o -name "test" -o -name "tests
|
|
|
61
61
|
# TypeScript source changes.
|
|
62
62
|
WORKDIR /workspace
|
|
63
63
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json .npmrc ./
|
|
64
|
+
COPY patches patches
|
|
64
65
|
COPY packages/shared/package.json packages/shared/package.json
|
|
65
66
|
COPY packages/sdk/package.json packages/sdk/package.json
|
|
66
67
|
COPY packages/cli/package.json packages/cli/package.json
|
|
@@ -17,6 +17,9 @@ The current image already follows this model more closely than the ACP runners:
|
|
|
17
17
|
ShadowOB connector CLI.
|
|
18
18
|
- `entrypoint.mjs` reads `/etc/openclaw/config.json`.
|
|
19
19
|
- Runtime extensions are read from `/etc/openclaw/runtime-extensions.json`.
|
|
20
|
+
- Template routines are read from `/etc/shadowob/template-routines.json` and
|
|
21
|
+
synced into the OpenClaw cron store before the gateway starts. The sync keeps a
|
|
22
|
+
managed spec hash so runtime-edited jobs are preserved on redeploy.
|
|
20
23
|
- The generated OpenClaw config is written outside mutable state by default.
|
|
21
24
|
- File logging goes to `/var/log/openclaw/entrypoint.log`.
|
|
22
25
|
- Shadow slash command artifacts are surfaced through
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { spawn, spawnSync } from 'node:child_process'
|
|
13
|
-
import { randomBytes } from 'node:crypto'
|
|
13
|
+
import { createHash, randomBytes } from 'node:crypto'
|
|
14
14
|
import {
|
|
15
15
|
chmodSync,
|
|
16
16
|
cpSync,
|
|
@@ -33,6 +33,8 @@ const EXTENSIONS_DIR = '/app/extensions'
|
|
|
33
33
|
const RUNTIME_FILES_PATH = join(CONFIG_MOUNT, 'runtime-files.json')
|
|
34
34
|
const RUNTIME_EXTENSIONS_PATH = join(CONFIG_MOUNT, 'runtime-extensions.json')
|
|
35
35
|
const DEFAULT_SHADOW_SLASH_COMMANDS_PATH = '/etc/shadowob/slash-commands.json'
|
|
36
|
+
const TEMPLATE_ROUTINES_PATH =
|
|
37
|
+
process.env.SHADOW_TEMPLATE_ROUTINES_PATH ?? '/etc/shadowob/template-routines.json'
|
|
36
38
|
const RUNTIME_CONFIG_DIR = process.env.OPENCLAW_RUNTIME_CONFIG_DIR || '/tmp/openclaw/config'
|
|
37
39
|
const RUNTIME_CONFIG_PATH = join(RUNTIME_CONFIG_DIR, 'openclaw.json')
|
|
38
40
|
const OPENCLAW_PACKAGE_DIR = '/app/node_modules/openclaw'
|
|
@@ -245,6 +247,251 @@ function materializeCredentialFiles(runtimeExtensions) {
|
|
|
245
247
|
}
|
|
246
248
|
}
|
|
247
249
|
|
|
250
|
+
function stableValue(value) {
|
|
251
|
+
if (Array.isArray(value)) return value.map(stableValue)
|
|
252
|
+
if (!isPlainObject(value)) return value
|
|
253
|
+
return Object.fromEntries(
|
|
254
|
+
Object.entries(value)
|
|
255
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
256
|
+
.map(([key, item]) => [key, stableValue(item)]),
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function stableHash(value) {
|
|
261
|
+
return createHash('sha256')
|
|
262
|
+
.update(JSON.stringify(stableValue(value)))
|
|
263
|
+
.digest('hex')
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function readJsonFile(path, fallback) {
|
|
267
|
+
if (!existsSync(path)) return fallback
|
|
268
|
+
try {
|
|
269
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'))
|
|
270
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.warn(`[entrypoint] Failed to parse ${path}: ${err.message}`)
|
|
273
|
+
return fallback
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function resolveRunnerHomePath(rawPath) {
|
|
278
|
+
if (typeof rawPath !== 'string' || !rawPath.trim()) return null
|
|
279
|
+
const trimmed = rawPath.trim()
|
|
280
|
+
if (trimmed === '~') return RUNNER_HOME
|
|
281
|
+
if (trimmed.startsWith('~/')) return join(RUNNER_HOME, trimmed.slice(2))
|
|
282
|
+
return resolve(trimmed)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveOpenClawCronStorePath(openclawConfig) {
|
|
286
|
+
const configured = isPlainObject(openclawConfig.cron) ? openclawConfig.cron.store : undefined
|
|
287
|
+
const candidate =
|
|
288
|
+
resolveRunnerHomePath(configured) ?? join(OPENCLAW_STATE_DIR, 'cron', 'jobs.json')
|
|
289
|
+
const allowedRoots = [RUNNER_HOME, '/home/openclaw']
|
|
290
|
+
if (!allowedRoots.some((root) => candidate === root || candidate.startsWith(`${root}/`))) {
|
|
291
|
+
console.warn(
|
|
292
|
+
`[entrypoint] Skipping template routine sync; cron store outside runner home: ${candidate}`,
|
|
293
|
+
)
|
|
294
|
+
return null
|
|
295
|
+
}
|
|
296
|
+
return candidate
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function parseRoutineEveryMs(interval) {
|
|
300
|
+
if (typeof interval !== 'string') return null
|
|
301
|
+
const match = interval.trim().match(/^(\d+)\s*(s|m|h|d)$/i)
|
|
302
|
+
if (!match) return null
|
|
303
|
+
const amount = Number.parseInt(match[1], 10)
|
|
304
|
+
if (!Number.isFinite(amount) || amount <= 0) return null
|
|
305
|
+
const unit = match[2].toLowerCase()
|
|
306
|
+
const multiplier =
|
|
307
|
+
unit === 's' ? 1000 : unit === 'm' ? 60_000 : unit === 'h' ? 3_600_000 : 86_400_000
|
|
308
|
+
return amount * multiplier
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function buildOpenClawRoutineSchedule(routine) {
|
|
312
|
+
const schedule = isPlainObject(routine.schedule) ? routine.schedule : {}
|
|
313
|
+
if (typeof schedule.cron === 'string' && schedule.cron.trim()) {
|
|
314
|
+
return {
|
|
315
|
+
kind: 'cron',
|
|
316
|
+
expr: schedule.cron.trim(),
|
|
317
|
+
...(typeof schedule.timezone === 'string' && schedule.timezone.trim()
|
|
318
|
+
? { tz: schedule.timezone.trim() }
|
|
319
|
+
: {}),
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const everyMs = parseRoutineEveryMs(schedule.interval)
|
|
323
|
+
if (everyMs) return { kind: 'every', everyMs }
|
|
324
|
+
return null
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolveRoutineDeliveryTarget(delivery) {
|
|
328
|
+
if (!isPlainObject(delivery) || delivery.pluginId !== 'shadowob' || delivery.kind !== 'channel') {
|
|
329
|
+
return null
|
|
330
|
+
}
|
|
331
|
+
const target = isPlainObject(delivery.target) ? delivery.target : {}
|
|
332
|
+
const channelEnvKey =
|
|
333
|
+
typeof target.channelEnvKey === 'string' && target.channelEnvKey.trim()
|
|
334
|
+
? target.channelEnvKey.trim()
|
|
335
|
+
: null
|
|
336
|
+
const channelId =
|
|
337
|
+
(channelEnvKey ? process.env[channelEnvKey] : undefined) ??
|
|
338
|
+
(typeof target.channelId === 'string' ? target.channelId : undefined)
|
|
339
|
+
if (!channelId) return null
|
|
340
|
+
return {
|
|
341
|
+
mode: 'announce',
|
|
342
|
+
channel: 'shadowob',
|
|
343
|
+
to: `shadowob:channel:${channelId}`,
|
|
344
|
+
...(typeof target.threadId === 'string' && target.threadId.trim()
|
|
345
|
+
? { threadId: target.threadId.trim() }
|
|
346
|
+
: {}),
|
|
347
|
+
...(typeof target.accountId === 'string' && target.accountId.trim()
|
|
348
|
+
? { accountId: target.accountId.trim() }
|
|
349
|
+
: {}),
|
|
350
|
+
bestEffort: true,
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function managedRoutineJobId(routine) {
|
|
355
|
+
const raw = `shadow-template-${routine.agentId ?? 'agent'}-${routine.id ?? 'routine'}`
|
|
356
|
+
.toLowerCase()
|
|
357
|
+
.replace(/[^a-z0-9._:-]+/g, '-')
|
|
358
|
+
.replace(/^-+|-+$/g, '')
|
|
359
|
+
if (raw.length >= 12 && raw.length <= 96) return raw
|
|
360
|
+
return `shadow-template-${stableHash({ agentId: routine.agentId, id: routine.id }).slice(0, 24)}`
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function openClawManagedJobShape(job) {
|
|
364
|
+
return {
|
|
365
|
+
agentId: job.agentId,
|
|
366
|
+
sessionKey: job.sessionKey,
|
|
367
|
+
name: job.name,
|
|
368
|
+
description: job.description,
|
|
369
|
+
enabled: job.enabled,
|
|
370
|
+
deleteAfterRun: job.deleteAfterRun,
|
|
371
|
+
schedule: job.schedule,
|
|
372
|
+
sessionTarget: job.sessionTarget,
|
|
373
|
+
wakeMode: job.wakeMode,
|
|
374
|
+
payload: job.payload,
|
|
375
|
+
delivery: job.delivery,
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function buildOpenClawRoutineJob(routine, now) {
|
|
380
|
+
if (
|
|
381
|
+
!isPlainObject(routine) ||
|
|
382
|
+
typeof routine.id !== 'string' ||
|
|
383
|
+
typeof routine.agentId !== 'string'
|
|
384
|
+
) {
|
|
385
|
+
return null
|
|
386
|
+
}
|
|
387
|
+
const schedule = buildOpenClawRoutineSchedule(routine)
|
|
388
|
+
if (!schedule) return null
|
|
389
|
+
const delivery = Array.isArray(routine.deliveries)
|
|
390
|
+
? routine.deliveries.map(resolveRoutineDeliveryTarget).find(Boolean)
|
|
391
|
+
: null
|
|
392
|
+
if (!delivery) return null
|
|
393
|
+
const job = {
|
|
394
|
+
id: managedRoutineJobId(routine),
|
|
395
|
+
agentId: routine.agentId,
|
|
396
|
+
name:
|
|
397
|
+
typeof routine.title === 'string' && routine.title.trim() ? routine.title.trim() : routine.id,
|
|
398
|
+
description:
|
|
399
|
+
typeof routine.description === 'string' && routine.description.trim()
|
|
400
|
+
? routine.description.trim()
|
|
401
|
+
: `Shadow Cloud template routine ${routine.id}`,
|
|
402
|
+
enabled: routine.enabled !== false,
|
|
403
|
+
createdAtMs: now,
|
|
404
|
+
updatedAtMs: now,
|
|
405
|
+
schedule,
|
|
406
|
+
sessionTarget: 'isolated',
|
|
407
|
+
wakeMode: 'now',
|
|
408
|
+
payload: { kind: 'agentTurn', message: String(routine.prompt ?? '') },
|
|
409
|
+
delivery,
|
|
410
|
+
state: {},
|
|
411
|
+
}
|
|
412
|
+
const managedSpecHash = stableHash(openClawManagedJobShape(job))
|
|
413
|
+
return {
|
|
414
|
+
...job,
|
|
415
|
+
shadowTemplateRoutine: {
|
|
416
|
+
version: 1,
|
|
417
|
+
routineId: routine.id,
|
|
418
|
+
agentId: routine.agentId,
|
|
419
|
+
sourceHash: typeof routine.sourceHash === 'string' ? routine.sourceHash : null,
|
|
420
|
+
managedSpecHash,
|
|
421
|
+
},
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function syncTemplateRoutinesToOpenClawCron(openclawConfig) {
|
|
426
|
+
if (!existsSync(TEMPLATE_ROUTINES_PATH)) return
|
|
427
|
+
const seed = readJsonFile(TEMPLATE_ROUTINES_PATH, null)
|
|
428
|
+
const routines = Array.isArray(seed?.routines) ? seed.routines : []
|
|
429
|
+
if (routines.length === 0) return
|
|
430
|
+
|
|
431
|
+
const storePath = resolveOpenClawCronStorePath(openclawConfig)
|
|
432
|
+
if (!storePath) return
|
|
433
|
+
|
|
434
|
+
const now = Date.now()
|
|
435
|
+
const store = readJsonFile(storePath, { version: 1, jobs: [] })
|
|
436
|
+
const jobs = Array.isArray(store.jobs) ? store.jobs.filter(Boolean) : []
|
|
437
|
+
let changed = false
|
|
438
|
+
|
|
439
|
+
for (const routine of routines) {
|
|
440
|
+
const desired = buildOpenClawRoutineJob(routine, now)
|
|
441
|
+
if (!desired) {
|
|
442
|
+
console.warn(
|
|
443
|
+
`[entrypoint] Skipping invalid template routine: ${JSON.stringify(routine?.id ?? null)}`,
|
|
444
|
+
)
|
|
445
|
+
continue
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const existingIndex = jobs.findIndex((job) => {
|
|
449
|
+
const marker = isPlainObject(job?.shadowTemplateRoutine) ? job.shadowTemplateRoutine : null
|
|
450
|
+
return marker?.routineId === desired.shadowTemplateRoutine.routineId || job?.id === desired.id
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
if (existingIndex < 0) {
|
|
454
|
+
jobs.push(desired)
|
|
455
|
+
changed = true
|
|
456
|
+
console.log(`[entrypoint] Seeded OpenClaw cron routine: ${desired.id}`)
|
|
457
|
+
continue
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const existing = jobs[existingIndex]
|
|
461
|
+
const marker = isPlainObject(existing.shadowTemplateRoutine)
|
|
462
|
+
? existing.shadowTemplateRoutine
|
|
463
|
+
: null
|
|
464
|
+
const currentManagedSpecHash = stableHash(openClawManagedJobShape(existing))
|
|
465
|
+
if (!marker || marker.managedSpecHash !== currentManagedSpecHash) {
|
|
466
|
+
console.log(
|
|
467
|
+
`[entrypoint] Preserved user-edited OpenClaw cron routine: ${existing.id ?? desired.id}`,
|
|
468
|
+
)
|
|
469
|
+
continue
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (marker.managedSpecHash === desired.shadowTemplateRoutine.managedSpecHash) continue
|
|
473
|
+
jobs[existingIndex] = {
|
|
474
|
+
...desired,
|
|
475
|
+
id: existing.id ?? desired.id,
|
|
476
|
+
createdAtMs: existing.createdAtMs ?? desired.createdAtMs,
|
|
477
|
+
updatedAtMs: now,
|
|
478
|
+
state: isPlainObject(existing.state) ? existing.state : {},
|
|
479
|
+
}
|
|
480
|
+
changed = true
|
|
481
|
+
console.log(
|
|
482
|
+
`[entrypoint] Updated OpenClaw cron routine from template: ${jobs[existingIndex].id}`,
|
|
483
|
+
)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!changed) return
|
|
487
|
+
mkdirSync(dirname(storePath), { recursive: true })
|
|
488
|
+
writeFileSync(storePath, `${JSON.stringify({ version: 1, jobs }, null, 2)}\n`, {
|
|
489
|
+
encoding: 'utf-8',
|
|
490
|
+
mode: 0o600,
|
|
491
|
+
})
|
|
492
|
+
chmodSync(storePath, 0o600)
|
|
493
|
+
}
|
|
494
|
+
|
|
248
495
|
function resolveEnvVars(obj) {
|
|
249
496
|
if (typeof obj === 'string') {
|
|
250
497
|
return obj.replace(/\$\{env:([^}]+)\}/g, (_, key) => {
|
|
@@ -1061,6 +1308,7 @@ async function main() {
|
|
|
1061
1308
|
}
|
|
1062
1309
|
writeFileSync(configPath, JSON.stringify(openclawConfig, null, 2), 'utf-8')
|
|
1063
1310
|
console.log('[entrypoint] Runtime config restored after setup')
|
|
1311
|
+
syncTemplateRoutinesToOpenClawCron(openclawConfig)
|
|
1064
1312
|
|
|
1065
1313
|
// 2e. Overlay workspace files from ConfigMap (SOUL.md, AGENTS.md, etc.)
|
|
1066
1314
|
// These are agent-specific files generated by the cloud config builder that
|
|
@@ -63,9 +63,7 @@ async function main() {
|
|
|
63
63
|
})
|
|
64
64
|
|
|
65
65
|
if (scan.conflicts.length > 0) {
|
|
66
|
-
console.warn(
|
|
67
|
-
`[runtime-deps] ${scan.conflicts.length} bundled dependency conflict(s) detected`,
|
|
68
|
-
)
|
|
66
|
+
console.warn(`[runtime-deps] ${scan.conflicts.length} bundled dependency conflict(s) detected`)
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
if (scan.missing.length === 0) {
|
|
@@ -145,9 +145,11 @@ type = "shadowob"
|
|
|
145
145
|
- Skills: materialize `.opencode/skills` first; optionally also emit
|
|
146
146
|
`.agents/skills` if a workflow must be shared with Codex.
|
|
147
147
|
- MCP: write the OpenCode `mcp` object, not OpenClaw `mcp`.
|
|
148
|
-
- Cron/routine:
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
- Cron/routine: Cloud template routines are materialized by the shared
|
|
149
|
+
cc-connect runner from `/etc/shadowob/template-routines.json` into
|
|
150
|
+
`~/.cc-connect/crons/jobs.json`. Managed jobs use deterministic ids and a
|
|
151
|
+
spec hash so user-edited schedules are preserved. ShadowOB delivery uses
|
|
152
|
+
`session_key = "shadowob:channel:<channel_id>"` or a thread-qualified variant.
|
|
151
153
|
- Hooks: prefer OpenCode plugins for deterministic lifecycle behavior.
|
|
152
154
|
- Subagents: use OpenCode `agent` and command `subtask` config instead of
|
|
153
155
|
OpenClaw multi-agent routing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shadowob/cloud",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "shadowob-cloud — deploy AI agents to Kubernetes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"yaml": "^2.8.4",
|
|
59
59
|
"zod": "^3.25.76",
|
|
60
60
|
"zustand": "^5.0.13",
|
|
61
|
-
"@shadowob/sdk": "1.1.
|
|
62
|
-
"@shadowob/shared": "1.1.
|
|
61
|
+
"@shadowob/sdk": "1.1.9",
|
|
62
|
+
"@shadowob/shared": "1.1.9"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@playwright/test": "^1.52.0",
|