@shadowob/cloud 1.1.6 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +159 -11
  2. package/dist/{agent-browser-CERTMCDL.js → agent-browser-EI7FIK3X.js} +3 -3
  3. package/dist/{agent-browser-CIRZRIY4.js → agent-browser-YXE4ES6Q.js} +3 -3
  4. package/dist/{agent-pack-LF3O5TR4.js → agent-pack-35TFCZKP.js} +1 -1
  5. package/dist/{agent-pack-RQT27V7R.js → agent-pack-UOG6ZAUL.js} +1 -1
  6. package/dist/agentmemory-KP5O7GHB.js +101 -0
  7. package/dist/agentmemory-UV74POU5.js +100 -0
  8. package/dist/{airtable-BG2Q75G2.js → airtable-CXXS3YUN.js} +3 -3
  9. package/dist/{airtable-JCQXFM5D.js → airtable-YZ5JR5JC.js} +3 -3
  10. package/dist/{alipay-TZQI34RB.js → alipay-WED5P3XC.js} +3 -3
  11. package/dist/{alipay-MZX2XCDB.js → alipay-WJTVREMG.js} +3 -3
  12. package/dist/{amap-KPCLZYYL.js → amap-AIN23TQ7.js} +3 -3
  13. package/dist/{amap-5RQB3VGC.js → amap-F6GF7QKB.js} +3 -3
  14. package/dist/{atlassian-LGOEWYC7.js → atlassian-G6PM6UVM.js} +3 -3
  15. package/dist/{atlassian-TVS2A4IU.js → atlassian-IDR2NPJC.js} +3 -3
  16. package/dist/{baidu-appbuilder-QRRL3ETM.js → baidu-appbuilder-JKQIA5TG.js} +3 -3
  17. package/dist/{baidu-appbuilder-6UMESXHW.js → baidu-appbuilder-KVMCIFYH.js} +3 -3
  18. package/dist/{baidu-maps-HEPMVP5D.js → baidu-maps-GWMXV6YT.js} +3 -3
  19. package/dist/{baidu-maps-HXC4FBVP.js → baidu-maps-ZXGT6QZM.js} +3 -3
  20. package/dist/{baidu-netdisk-G5Q6B5NH.js → baidu-netdisk-J5SLQVWW.js} +3 -3
  21. package/dist/{baidu-netdisk-RS2K5W2M.js → baidu-netdisk-PONM3DY6.js} +3 -3
  22. package/dist/{baidu-smartprogram-JHD3XWF6.js → baidu-smartprogram-WJ5JMO6V.js} +3 -3
  23. package/dist/{baidu-smartprogram-EWTK5WKK.js → baidu-smartprogram-Y5WBR7RX.js} +3 -3
  24. package/dist/{browserbase-IUIYVYI7.js → browserbase-CGVQHRC4.js} +3 -3
  25. package/dist/{browserbase-JFO2PCIA.js → browserbase-PVWYTEZC.js} +3 -3
  26. package/dist/{canva-3YOFL7JS.js → canva-COCBQAT2.js} +3 -3
  27. package/dist/{canva-FMYN65SM.js → canva-RSTJSEX5.js} +3 -3
  28. package/dist/{chunk-ULOJLXQV.js → chunk-3CT6RQNM.js} +766 -482
  29. package/dist/{chunk-SIDBK5SZ.js → chunk-4YO3NA26.js} +1 -1
  30. package/dist/{chunk-RECNVWMT.js → chunk-6V7MW4HU.js} +17 -3
  31. package/dist/{chunk-2FWJSE6Z.js → chunk-EVV774KS.js} +1 -1
  32. package/dist/{chunk-SVMXSIMG.js → chunk-F6CQ6GAG.js} +2 -1
  33. package/dist/{chunk-JUPAE5IA.js → chunk-OL5VH6RN.js} +72 -69
  34. package/dist/{chunk-POSVEKIY.js → chunk-OYY64ZSX.js} +17 -3
  35. package/dist/{chunk-FSW4TNHN.js → chunk-P5Y6F2NH.js} +766 -482
  36. package/dist/{chunk-EEFMJYKB.js → chunk-PSK2SYZ3.js} +2 -1
  37. package/dist/{chunk-HRTBOZ7O.js → chunk-PYJRFKPN.js} +1 -1
  38. package/dist/{chunk-JY2HTT7Q.js → chunk-RMDY3W4V.js} +6 -0
  39. package/dist/{chunk-I3NRNCDR.js → chunk-X2SREECR.js} +6 -6
  40. package/dist/{chunk-XFJX4NWN.js → chunk-X5VOIA72.js} +6 -6
  41. package/dist/{chunk-CTNUKOQE.js → chunk-Y5BJ3EW2.js} +6 -0
  42. package/dist/{chunk-IXN3FU5J.js → chunk-Y6BKVDG7.js} +1 -1
  43. package/dist/{chunk-6P2K6QZR.js → chunk-ZGMWSSCC.js} +72 -69
  44. package/dist/{claude-plugin-577TAQVS.js → claude-plugin-FPN32WMT.js} +1 -1
  45. package/dist/{claude-plugin-L3MXJJ6J.js → claude-plugin-IYHOVTVL.js} +1 -1
  46. package/dist/cli.js +930 -149
  47. package/dist/{cloudflare-RDFPKMM5.js → cloudflare-U3RHJJKK.js} +3 -3
  48. package/dist/{cloudflare-HBBABPK6.js → cloudflare-ZHN7UGPX.js} +3 -3
  49. package/dist/{cnb-FLP3QX46.js → cnb-AMXC5I7D.js} +3 -3
  50. package/dist/{cnb-YAVVEYFB.js → cnb-P3IZ4JTD.js} +3 -3
  51. package/dist/console/index.html +1 -1
  52. package/dist/console/static/css/index.f4563d95.css +1 -0
  53. package/dist/console/static/js/index.020abc71.js +1 -0
  54. package/dist/{coze-E6VGRNLV.js → coze-66RYMKVB.js} +3 -3
  55. package/dist/{coze-C6PMDPBI.js → coze-YE3BINXP.js} +3 -3
  56. package/dist/{dashboard.command-SCAAQ23X.js → dashboard.command-BRPZCZER.js} +1 -1
  57. package/dist/{dashboard.command-H5DIGLUR.js → dashboard.command-GUHSJ2CN.js} +1 -1
  58. package/dist/{dingtalk-JNRNRN7E.js → dingtalk-4RFQG7N2.js} +3 -3
  59. package/dist/{dingtalk-WZGGIAHJ.js → dingtalk-VNFKXD2P.js} +3 -3
  60. package/dist/{douyin-miniprogram-AIJPPIZH.js → douyin-miniprogram-UEALAGOS.js} +3 -3
  61. package/dist/{douyin-miniprogram-HCYZ5NBW.js → douyin-miniprogram-UNB6UO2I.js} +3 -3
  62. package/dist/{figma-2YYNSCDX.js → figma-A264OWU5.js} +3 -3
  63. package/dist/{figma-RYOBMENP.js → figma-Y4TGSDZP.js} +3 -3
  64. package/dist/{firebase-OYSY6HPT.js → firebase-AI3MAGYG.js} +3 -3
  65. package/dist/{firebase-2IJDDBXX.js → firebase-ZGQARUIH.js} +3 -3
  66. package/dist/{firecrawl-2T3SBUW7.js → firecrawl-2JW7DMTH.js} +3 -3
  67. package/dist/{firecrawl-IYYXLAZM.js → firecrawl-UURQ5P5N.js} +3 -3
  68. package/dist/{flyai-QS5Q6FJR.js → flyai-EJGDMYFA.js} +3 -3
  69. package/dist/{flyai-7FJ4TRAG.js → flyai-ZFMZBBHJ.js} +3 -3
  70. package/dist/{gitagent-MWI75OIX.js → gitagent-5SDBYFNA.js} +1 -1
  71. package/dist/{gitagent-YBMWY7NZ.js → gitagent-ODXPCR4X.js} +1 -1
  72. package/dist/{gitee-3N7OFOM7.js → gitee-5UMJ4BC7.js} +3 -3
  73. package/dist/{gitee-KVNK6KLZ.js → gitee-D6NAZTCO.js} +3 -3
  74. package/dist/{github-LUEC2LID.js → github-JBLDKIA3.js} +3 -3
  75. package/dist/{github-XRO5Z3GC.js → github-PZQAVEZP.js} +3 -3
  76. package/dist/{google-ads-VPKWTX67.js → google-ads-BIFQOJ5M.js} +3 -3
  77. package/dist/{google-ads-A3QAJI4D.js → google-ads-QU3LJE4O.js} +3 -3
  78. package/dist/{google-analytics-C4UR5ZR2.js → google-analytics-7VZ6YZVA.js} +3 -3
  79. package/dist/{google-analytics-XDYZA2B7.js → google-analytics-HXMPCL5V.js} +3 -3
  80. package/dist/{google-workspace-YX35SHHX.js → google-workspace-6SEBJ4VA.js} +2 -2
  81. package/dist/{google-workspace-LL3EWVHH.js → google-workspace-L3AMJLCF.js} +2 -2
  82. package/dist/{huawei-xiaoyi-KPWLTSHB.js → huawei-xiaoyi-C6QIJMPM.js} +3 -3
  83. package/dist/{huawei-xiaoyi-6BSMGJHR.js → huawei-xiaoyi-JGLXWU5P.js} +3 -3
  84. package/dist/{hubspot-FTIEMNZO.js → hubspot-LACJGE6D.js} +3 -3
  85. package/dist/{hubspot-DIUHGEDI.js → hubspot-XWPRO4KZ.js} +3 -3
  86. package/dist/{huggingface-UUXK2RHK.js → huggingface-26QQZK4C.js} +3 -3
  87. package/dist/{huggingface-MJCOXA7E.js → huggingface-CQICNA2R.js} +3 -3
  88. package/dist/index.d.ts +1338 -1
  89. package/dist/index.js +1364 -226
  90. package/dist/{inference-ai-image-generation-PXV6IG4U.js → inference-ai-image-generation-5KYIUWT6.js} +3 -3
  91. package/dist/{inference-ai-image-generation-CMI6R5T3.js → inference-ai-image-generation-J2NYDCLZ.js} +3 -3
  92. package/dist/{inference-sh-7AZOLEFI.js → inference-sh-5SWQTK73.js} +3 -3
  93. package/dist/{inference-sh-ABQOD3YF.js → inference-sh-PTQF6T3R.js} +3 -3
  94. package/dist/{init.command-WGKN3GC6.js → init.command-C7UKPK2Y.js} +3 -3
  95. package/dist/{init.command-775GLXTC.js → init.command-UNL66BMR.js} +3 -3
  96. package/dist/{klaviyo-LDPBWBSS.js → klaviyo-4UNPMBFT.js} +3 -3
  97. package/dist/{klaviyo-6K5YEFNH.js → klaviyo-SLYNEULT.js} +3 -3
  98. package/dist/{kuaidi100-HGFM5VK2.js → kuaidi100-HZKV5AIS.js} +3 -3
  99. package/dist/{kuaidi100-UHPFCVXP.js → kuaidi100-ZQUW7GHH.js} +3 -3
  100. package/dist/lark-HQUZNHDI.js +382 -0
  101. package/dist/lark-PAV7XWJS.js +381 -0
  102. package/dist/{linear-T4ORUP7N.js → linear-PYGQ5SLK.js} +3 -3
  103. package/dist/{linear-7QFSFPOD.js → linear-VJLYNTUF.js} +3 -3
  104. package/dist/{lovart-PDUXRUHJ.js → lovart-KC6SVNAJ.js} +3 -3
  105. package/dist/{lovart-QO3SK55T.js → lovart-WVKY4RR4.js} +3 -3
  106. package/dist/{meta-ads-SCNFI45S.js → meta-ads-E6XT33GI.js} +3 -3
  107. package/dist/{meta-ads-V6XPZWX3.js → meta-ads-RJ6DWRYN.js} +3 -3
  108. package/dist/{miclaw-TPPPS2WN.js → miclaw-4BA3A2YN.js} +3 -3
  109. package/dist/{miclaw-5CNTW7VV.js → miclaw-LUV6DCHX.js} +3 -3
  110. package/dist/{model-provider-KFB76XV5.js → model-provider-SYXJZ3JD.js} +1 -1
  111. package/dist/{model-provider-AVSFJSZP.js → model-provider-U7NEYA3Y.js} +1 -1
  112. package/dist/nature-skills-G76ABIWZ.js +143 -0
  113. package/dist/nature-skills-SQHMFXKT.js +142 -0
  114. package/dist/{notion-WFA7KGZZ.js → notion-2JZAKOFP.js} +1 -1
  115. package/dist/{notion-FZK76MN2.js → notion-O3NO5TJH.js} +1 -1
  116. package/dist/{oceanengine-3JZUS3PP.js → oceanengine-D23UZGVB.js} +3 -3
  117. package/dist/{oceanengine-5BRIJVJE.js → oceanengine-WDK2OXX5.js} +3 -3
  118. package/dist/{opencli-PFXHGCS2.js → opencli-36P63YNU.js} +3 -3
  119. package/dist/{opencli-VIGRJTGH.js → opencli-SUHDFR33.js} +3 -3
  120. package/dist/{paypal-Z5JYHIWD.js → paypal-K27SUW3B.js} +3 -3
  121. package/dist/{paypal-33UADIPR.js → paypal-OPZ3KOV5.js} +3 -3
  122. package/dist/{playwright-SQAQ3DZG.js → playwright-2ULT3NIC.js} +3 -3
  123. package/dist/{playwright-MG5WHK47.js → playwright-3Q7LBILG.js} +3 -3
  124. package/dist/{plugins-HZBWK3WQ.js → plugins-2MITZ4ZD.js} +2 -2
  125. package/dist/{plugins-I4GD5SZX.js → plugins-UK2QWD6G.js} +2 -2
  126. package/dist/{posthog-MU5MAJOQ.js → posthog-E3EHXLAN.js} +3 -3
  127. package/dist/{posthog-RJRRKDWB.js → posthog-KPJVLGX6.js} +3 -3
  128. package/dist/{salesforce-34FVIJTG.js → salesforce-FGPNG7FB.js} +3 -3
  129. package/dist/{salesforce-3QZ6OFVO.js → salesforce-TVHISKBC.js} +3 -3
  130. package/dist/{sentry-PIWW46VA.js → sentry-BZ3J3MZM.js} +3 -3
  131. package/dist/{sentry-MCIRMACU.js → sentry-XC57YRAJ.js} +3 -3
  132. package/dist/{seo-suite-WJXMA3S4.js → seo-suite-2MDEDLAB.js} +3 -3
  133. package/dist/{seo-suite-4SQ3I67Q.js → seo-suite-U75O3QP6.js} +3 -3
  134. package/dist/{serve.command-2AHJI665.js → serve.command-G5RVQFUD.js} +3 -3
  135. package/dist/{serve.command-Q46LHQG6.js → serve.command-PYGDG7K3.js} +3 -3
  136. package/dist/{shadowob-PRSMI5MW.js → shadowob-3QZ7DLDW.js} +158 -31
  137. package/dist/{shadowob-JELOWHWX.js → shadowob-CJLOEKFP.js} +158 -31
  138. package/dist/{sherlock-2PKY2E2Y.js → sherlock-CQFUHKDH.js} +3 -3
  139. package/dist/{sherlock-C5ZWPPVT.js → sherlock-DONK2I6E.js} +3 -3
  140. package/dist/{shopify-GL3NFVGE.js → shopify-NO5GI3WD.js} +3 -3
  141. package/dist/{shopify-R4G3UXM6.js → shopify-VW2KLKH5.js} +3 -3
  142. package/dist/{skill-discovery-YPXXV622.js → skill-discovery-6JEPPKKM.js} +3 -3
  143. package/dist/{skill-discovery-7INAUP4D.js → skill-discovery-PWRAVGIS.js} +3 -3
  144. package/dist/skills/shadowob-cli/SKILL.md +33 -22
  145. package/dist/{stripe-LJNPQ3CQ.js → stripe-HCNCKG4C.js} +1 -1
  146. package/dist/{stripe-C22RR4ZS.js → stripe-IU3KTJ4H.js} +1 -1
  147. package/dist/{supabase-IRNQ54FJ.js → supabase-KRL7JW2D.js} +3 -3
  148. package/dist/{supabase-N4ONFJNQ.js → supabase-TYEBTZNO.js} +3 -3
  149. package/dist/{taobao-aipaas-LRR4GMO3.js → taobao-aipaas-PEUIDOYP.js} +3 -3
  150. package/dist/{taobao-aipaas-RVKORSF4.js → taobao-aipaas-SA5E4MZA.js} +3 -3
  151. package/dist/{tapd-TMQRSMFG.js → tapd-6DDIUPVQ.js} +3 -3
  152. package/dist/{tapd-3JPVJ7XH.js → tapd-OTYLSZGY.js} +3 -3
  153. package/dist/{tencent-ads-UHC6OPBV.js → tencent-ads-OW2TAMH5.js} +3 -3
  154. package/dist/{tencent-ads-IGD33LO7.js → tencent-ads-XTQZ27YT.js} +3 -3
  155. package/dist/{tencent-docs-C3A4J3CJ.js → tencent-docs-6D6A2VCO.js} +3 -3
  156. package/dist/{tencent-docs-O2SC4FHL.js → tencent-docs-K3TMUIWD.js} +3 -3
  157. package/dist/{tencent-maps-OQOKHVW2.js → tencent-maps-IZYWITJZ.js} +3 -3
  158. package/dist/{tencent-maps-HMMWMNF4.js → tencent-maps-SWI7CLQY.js} +3 -3
  159. package/dist/text-to-cad-B2UP6PKA.js +192 -0
  160. package/dist/text-to-cad-I4B6VBFV.js +193 -0
  161. package/dist/{vercel-KOXDDTHX.js → vercel-CCKRC76D.js} +3 -3
  162. package/dist/{vercel-OLNVDWMG.js → vercel-SBGEMIJJ.js} +3 -3
  163. package/dist/{webflow-OMJKZM54.js → webflow-C3EHNNSN.js} +3 -3
  164. package/dist/{webflow-FULU5Q2I.js → webflow-ZFBJH4CR.js} +3 -3
  165. package/dist/{wechat-miniprogram-skyline-KYCDMQNW.js → wechat-miniprogram-skyline-VNCRERHX.js} +3 -3
  166. package/dist/{wechat-miniprogram-skyline-VR4FVIQL.js → wechat-miniprogram-skyline-Z5JQUV5Q.js} +3 -3
  167. package/dist/{wechat-pay-BCMAJ6UW.js → wechat-pay-RPDKPUEB.js} +3 -3
  168. package/dist/{wechat-pay-YQQKXVUI.js → wechat-pay-XVDGJRF2.js} +3 -3
  169. package/dist/{wonda-NGWIORYN.js → wonda-EL2P44S7.js} +3 -3
  170. package/dist/{wonda-RBABXFNM.js → wonda-XK5JK4X3.js} +3 -3
  171. package/dist/{wordpress-woocommerce-RNA5HB3N.js → wordpress-woocommerce-4MEE5A2M.js} +3 -3
  172. package/dist/{wordpress-woocommerce-RDIUTHYT.js → wordpress-woocommerce-6ECNM2QU.js} +3 -3
  173. package/dist/{wps-LUWHMZQQ.js → wps-E4OZEMOF.js} +3 -3
  174. package/dist/{wps-DAEFQHDE.js → wps-JNQUC4JS.js} +3 -3
  175. package/dist/{yuque-HCHTJWNI.js → yuque-GLAAOS7X.js} +3 -3
  176. package/dist/{yuque-KRH5O74J.js → yuque-MEF6VFLJ.js} +3 -3
  177. package/images/RUNNERS.md +15 -0
  178. package/images/cc-connect-runner/entrypoint.mjs +228 -0
  179. package/images/claude-runner/RUNNER.md +5 -3
  180. package/images/codex-runner/RUNNER.md +5 -3
  181. package/images/gemini-runner/RUNNER.md +5 -2
  182. package/images/hermes-runner/RUNNER.md +4 -2
  183. package/images/hermes-runner/entrypoint.mjs +269 -1
  184. package/images/openclaw-runner/Dockerfile +1 -0
  185. package/images/openclaw-runner/RUNNER.md +3 -0
  186. package/images/openclaw-runner/entrypoint.mjs +249 -1
  187. package/images/openclaw-runner/warm-runtime-deps.mjs +1 -3
  188. package/images/opencode-runner/RUNNER.md +5 -3
  189. package/package.json +3 -3
  190. package/templates/agent-marketplace-buddy.template.json +4 -1
  191. package/templates/bmad-method-buddy.template.json +4 -1
  192. package/templates/code-trainer.template.json +331 -0
  193. package/templates/gstack-buddy.template.json +4 -1
  194. package/templates/little-match-girl.template.json +10 -3
  195. package/dist/console/static/css/index.7f91f806.css +0 -1
  196. package/dist/console/static/js/index.4487e1ff.js +0 -1
  197. package/dist/lark-6LNA3LUQ.js +0 -103
  198. package/dist/lark-URVBZNS4.js +0 -102
@@ -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: OpenCode does not provide the same native cron surface as
149
- OpenClaw or Hermes in the researched docs; Cloud should own scheduling for
150
- phase 1.
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.6",
3
+ "version": "1.1.8",
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.6",
62
- "@shadowob/shared": "1.1.6"
61
+ "@shadowob/sdk": "1.1.8",
62
+ "@shadowob/shared": "1.1.8"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@playwright/test": "^1.52.0",
@@ -49,7 +49,10 @@
49
49
  "channels": ["choose", "build", "review"],
50
50
  "agentId": "agent-marketplace-buddy"
51
51
  }
52
- ]
52
+ ],
53
+ "greeting": {
54
+ "entryChannelId": "choose"
55
+ }
53
56
  }
54
57
  }
55
58
  ],
@@ -49,7 +49,10 @@
49
49
  "channels": ["analysis", "planning", "delivery"],
50
50
  "agentId": "bmad-buddy"
51
51
  }
52
- ]
52
+ ],
53
+ "greeting": {
54
+ "entryChannelId": "delivery"
55
+ }
53
56
  }
54
57
  }
55
58
  ],
@@ -0,0 +1,331 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "name": "code-trainer",
4
+ "title": "${i18n:title}",
5
+ "description": "${i18n:description}",
6
+ "environment": "production",
7
+ "routines": [
8
+ {
9
+ "id": "sync-submissions",
10
+ "agentId": "code-trainer-buddy",
11
+ "title": "Pull learner submissions",
12
+ "description": "Check for submitted code that needs review.",
13
+ "schedule": {
14
+ "interval": "15m"
15
+ },
16
+ "prompt": "Open Code Trainer through the Shadow Server App CLI. Treat this run as #代码复盘. Fetch pending submissions, claim any Inbox task assigned to you, post a one-line acknowledgement before running cases, run sandbox review for assigned work, write analysis with shadow-trainer submissions.analyze, then read shadow-trainer learning.overview to confirm skills, wrong problems, recommendations, plan, and report state updated. Post only a short status summary to the channel."
17
+ },
18
+ {
19
+ "id": "recommend-next-problem",
20
+ "agentId": "code-trainer-buddy",
21
+ "title": "Recommend next problem",
22
+ "description": "Pick the next suitable training problem after recent progress.",
23
+ "schedule": {
24
+ "cron": "0 18 * * *",
25
+ "timezone": "Asia/Shanghai"
26
+ },
27
+ "prompt": "Treat this run as #题目推荐. Call shadow-trainer learning.overview, inspect skills, wrong problems, recent submissions, current plan, and difficulty settings. Create a recommendation with shadow-trainer recommendations.create when a concrete next problem is clear. Post the recommendation reason, predicted ACK rate if available, and one lighter fallback to the channel."
28
+ },
29
+ {
30
+ "id": "refresh-learning-plan",
31
+ "agentId": "code-trainer-buddy",
32
+ "title": "Refresh learning plan",
33
+ "description": "Update daily and weekly learning tasks.",
34
+ "schedule": {
35
+ "cron": "0 8 * * *",
36
+ "timezone": "Asia/Shanghai"
37
+ },
38
+ "prompt": "Treat this run as #学习规划. Call shadow-trainer learning.overview, then create or adjust today's learning list with shadow-trainer learning.plan.upsert. If the learner has no plan yet, create a starter list with one main problem, one review task, and one small check. Use recent reviews, weak tags, wrong problems, and the current training stage. Post only the first concrete action."
39
+ },
40
+ {
41
+ "id": "push-algorithm-tip",
42
+ "agentId": "code-trainer-buddy",
43
+ "title": "Push algorithm tip",
44
+ "description": "Send a concise technique card for the current stage.",
45
+ "schedule": {
46
+ "cron": "30 8 * * *",
47
+ "timezone": "Asia/Shanghai"
48
+ },
49
+ "prompt": "Treat this run as #算法小技巧. Call shadow-trainer learning.overview, choose one weak or active tag, then create a tip with shadow-trainer tips.create and a short check with shadow-trainer checks.create. Include the pattern, one pitfall, and a short complexity reminder."
50
+ },
51
+ {
52
+ "id": "wrong-problem-review",
53
+ "agentId": "code-trainer-buddy",
54
+ "title": "Schedule wrong problem review",
55
+ "description": "Select wrong problems that should be retried.",
56
+ "schedule": {
57
+ "cron": "0 20 * * *",
58
+ "timezone": "Asia/Shanghai"
59
+ },
60
+ "prompt": "Treat this run as #错题回炉. Call shadow-trainer learning.overview. If a recent failed or incomplete review exists, schedule it with shadow-trainer wrongProblems.schedule and post the retry reason plus one similar variation. If nothing is due, stay quiet unless a due review exists."
61
+ },
62
+ {
63
+ "id": "generate-progress-report",
64
+ "agentId": "code-trainer-buddy",
65
+ "title": "Generate learning report",
66
+ "description": "Summarize completion, error distribution, skill movement, and plan risk.",
67
+ "schedule": {
68
+ "cron": "0 21 * * *",
69
+ "timezone": "Asia/Shanghai"
70
+ },
71
+ "prompt": "Treat this run as #助教资讯. Call shadow-trainer learning.overview. Create a daily report with shadow-trainer reports.create including completion signals, accepted-rate trend, weak tags, repeated error types, and one concrete next action. Post the same concise summary to 助教资讯."
72
+ },
73
+ {
74
+ "id": "detect-learning-risk",
75
+ "agentId": "code-trainer-buddy",
76
+ "title": "Detect learning risk",
77
+ "description": "Identify inactivity, repeated failures, and plan drift that need intervention.",
78
+ "schedule": {
79
+ "cron": "30 21 * * *",
80
+ "timezone": "Asia/Shanghai"
81
+ },
82
+ "prompt": "Treat this run as #助教资讯. Call shadow-trainer learning.overview. If the learner is inactive, repeatedly failing the same tag, or drifting from the active plan, create a risk-focused report with shadow-trainer reports.create and post a short intervention note. If there is no risk, only post a one-line healthy status."
83
+ },
84
+ {
85
+ "id": "weekly-problem-quality-check",
86
+ "agentId": "code-trainer-buddy",
87
+ "title": "Check problem bank quality",
88
+ "description": "Review tags, difficulty, examples, and test coverage.",
89
+ "schedule": {
90
+ "cron": "0 10 * * 1",
91
+ "timezone": "Asia/Shanghai"
92
+ },
93
+ "prompt": "Treat this run as #算法小技巧. Audit Code Trainer problems that produced confusing reviews. Suggest tag fixes, missing tests, or difficulty calibration. Use shadow-trainer challenges.upsert only when the owner explicitly asks you to apply a change."
94
+ }
95
+ ],
96
+ "use": [
97
+ {
98
+ "plugin": "model-provider",
99
+ "options": {
100
+ "model": "custom/default"
101
+ }
102
+ },
103
+ {
104
+ "plugin": "shadowob",
105
+ "options": {
106
+ "servers": [
107
+ {
108
+ "id": "code-trainer-server",
109
+ "name": "Code Trainer",
110
+ "slug": "code-trainer",
111
+ "description": "Algorithm learning and practice workspace.",
112
+ "channels": [
113
+ {
114
+ "id": "assistant-news",
115
+ "title": "助教资讯",
116
+ "type": "text",
117
+ "description": "Review summaries, learning reminders, stage feedback, and system notices."
118
+ },
119
+ {
120
+ "id": "problem-recommendations",
121
+ "title": "题目推荐",
122
+ "type": "text",
123
+ "description": "Daily recommendations, next problem suggestions, wrong problem variants, and drills."
124
+ },
125
+ {
126
+ "id": "learning-plan",
127
+ "title": "学习规划",
128
+ "type": "text",
129
+ "description": "Daily tasks, weekly lists, stage goals, and plan adjustments."
130
+ },
131
+ {
132
+ "id": "code-review",
133
+ "title": "代码复盘",
134
+ "type": "text",
135
+ "description": "Post-submission causes, fixes, and review questions."
136
+ },
137
+ {
138
+ "id": "wrong-problems",
139
+ "title": "错题回炉",
140
+ "type": "text",
141
+ "description": "Second-pass problems, similar problems, forgetting reminders, and review tasks."
142
+ },
143
+ {
144
+ "id": "algorithm-tips",
145
+ "title": "算法小技巧",
146
+ "type": "text",
147
+ "description": "Stage-specific templates, tips, pitfalls, and complexity reminders."
148
+ }
149
+ ]
150
+ }
151
+ ],
152
+ "buddies": [
153
+ {
154
+ "id": "code-trainer-buddy",
155
+ "name": "算法教练",
156
+ "description": "一个 Buddy 负责提交复盘、题目推荐、学习规划、算法技巧、错题回炉和进度摘要;频道用于区分工作语境。"
157
+ }
158
+ ],
159
+ "bindings": [
160
+ {
161
+ "targetId": "code-trainer-buddy",
162
+ "targetType": "buddy",
163
+ "servers": ["code-trainer-server"],
164
+ "channels": [
165
+ "assistant-news",
166
+ "problem-recommendations",
167
+ "learning-plan",
168
+ "code-review",
169
+ "wrong-problems",
170
+ "algorithm-tips"
171
+ ],
172
+ "agentId": "code-trainer-buddy",
173
+ "replyPolicy": {
174
+ "mode": "mentionOnly"
175
+ }
176
+ }
177
+ ],
178
+ "greeting": {
179
+ "entryChannelId": "learning-plan",
180
+ "messages": [
181
+ {
182
+ "id": "assistant-news-welcome",
183
+ "channelId": "assistant-news",
184
+ "buddyId": "code-trainer-buddy",
185
+ "content": "这里是训练动态和日报摘要。我会把复盘写回、计划偏离、风险提醒和阶段变化放在这里,默认只发关键变化。"
186
+ },
187
+ {
188
+ "id": "problem-recommendations-welcome",
189
+ "channelId": "problem-recommendations",
190
+ "buddyId": "code-trainer-buddy",
191
+ "content": "这里是题目推荐区。我会根据提交结果、薄弱标签、复习节奏和难度系数推荐下一题,也会给出变式或降阶备选。"
192
+ },
193
+ {
194
+ "id": "learning-plan-welcome",
195
+ "channelId": "learning-plan",
196
+ "buddyId": "code-trainer-buddy",
197
+ "content": "{userName},欢迎来到 Code Trainer。我是算法教练,一个 Buddy 会在不同频道里处理复盘、推荐、计划、技巧和错题。你可以先告诉我目标、语言和截止时间;也可以直接打开 Code Trainer 提交代码。"
198
+ },
199
+ {
200
+ "id": "code-review-welcome",
201
+ "channelId": "code-review",
202
+ "buddyId": "code-trainer-buddy",
203
+ "content": "这里用来复盘提交。你在 Code Trainer 里提交后,我会先确认收到,再运行示例、隐藏用例和边界用例,最后写回结论、分数、复杂度和下一步。"
204
+ },
205
+ {
206
+ "id": "wrong-problems-welcome",
207
+ "channelId": "wrong-problems",
208
+ "buddyId": "code-trainer-buddy",
209
+ "content": "这里会沉淀错题和二刷安排。我会把重复错误、遗忘点和相似变体拆出来,按间隔复习。"
210
+ },
211
+ {
212
+ "id": "algorithm-tips-welcome",
213
+ "channelId": "algorithm-tips",
214
+ "buddyId": "code-trainer-buddy",
215
+ "content": "这里是算法技巧和短检查区。我会把当前阶段最该掌握的模板、坑点和复杂度判断压缩成可练的一小块。"
216
+ }
217
+ ]
218
+ },
219
+ "serverApps": [
220
+ {
221
+ "id": "code-trainer-app",
222
+ "serverId": "code-trainer-server",
223
+ "manifestUrl": "${env:CODE_TRAINER_MANIFEST_URL}",
224
+ "grants": [
225
+ {
226
+ "buddyId": "code-trainer-buddy",
227
+ "permissions": [
228
+ "trainer.challenges:read",
229
+ "trainer.challenges:write",
230
+ "trainer.submissions:read",
231
+ "trainer.submissions:analyze",
232
+ "trainer.learning:read",
233
+ "trainer.learning:write"
234
+ ],
235
+ "approvalMode": "none"
236
+ }
237
+ ]
238
+ }
239
+ ],
240
+ "routines": [
241
+ {
242
+ "routineId": "sync-submissions",
243
+ "serverId": "code-trainer-server",
244
+ "channelId": "code-review",
245
+ "accountId": "code-trainer-buddy"
246
+ },
247
+ {
248
+ "routineId": "recommend-next-problem",
249
+ "serverId": "code-trainer-server",
250
+ "channelId": "problem-recommendations",
251
+ "accountId": "code-trainer-buddy"
252
+ },
253
+ {
254
+ "routineId": "refresh-learning-plan",
255
+ "serverId": "code-trainer-server",
256
+ "channelId": "learning-plan",
257
+ "accountId": "code-trainer-buddy"
258
+ },
259
+ {
260
+ "routineId": "push-algorithm-tip",
261
+ "serverId": "code-trainer-server",
262
+ "channelId": "algorithm-tips",
263
+ "accountId": "code-trainer-buddy"
264
+ },
265
+ {
266
+ "routineId": "wrong-problem-review",
267
+ "serverId": "code-trainer-server",
268
+ "channelId": "wrong-problems",
269
+ "accountId": "code-trainer-buddy"
270
+ },
271
+ {
272
+ "routineId": "generate-progress-report",
273
+ "serverId": "code-trainer-server",
274
+ "channelId": "assistant-news",
275
+ "accountId": "code-trainer-buddy"
276
+ },
277
+ {
278
+ "routineId": "detect-learning-risk",
279
+ "serverId": "code-trainer-server",
280
+ "channelId": "assistant-news",
281
+ "accountId": "code-trainer-buddy"
282
+ },
283
+ {
284
+ "routineId": "weekly-problem-quality-check",
285
+ "serverId": "code-trainer-server",
286
+ "channelId": "algorithm-tips",
287
+ "accountId": "code-trainer-buddy"
288
+ }
289
+ ]
290
+ }
291
+ }
292
+ ],
293
+ "deployments": {
294
+ "namespace": "code-trainer",
295
+ "backend": "deployment",
296
+ "backendPolicy": "deployment-only",
297
+ "agents": [
298
+ {
299
+ "id": "code-trainer-buddy",
300
+ "runtime": "openclaw",
301
+ "description": "单一 Code Trainer 算法教练,按频道承担复盘、推荐、计划、技巧、错题和进度摘要。",
302
+ "identity": {
303
+ "name": "算法教练",
304
+ "personality": "You are structured, direct, patient, and proactive. You keep one coherent coaching memory across all Code Trainer channels while respecting each channel purpose.",
305
+ "systemPrompt": "You are the single Code Trainer Buddy. Use the mounted shadowob CLI skill as the source of truth: run `shadowob app discover --server \"$SHADOW_SERVER_APP_SERVER_CODE_TRAINER_APP\" --json`, then call `shadowob app call \"$SHADOW_SERVER_APP_KEY_CODE_TRAINER_APP\" <command> --server \"$SHADOW_SERVER_APP_SERVER_CODE_TRAINER_APP\" --json-input '<raw-command-input-json>' --json`.\n\nUse channels as modes, not separate identities:\n- #代码复盘: claim Inbox tasks immediately, acknowledge within 60 seconds, run visible examples plus hidden/edge cases, then write submissions.analyze with verdict, score, complexity, diagnosis, and next steps. If review takes more than 3 minutes, post one brief progress note.\n- #题目推荐: recommend the next problem from learning.overview using reinforcement, diversity, review, and popular coverage. Include predicted ACK rate when available and offer a lighter fallback.\n- #学习规划: maintain daily/weekly lists, deadline target, and first concrete action.\n- #算法小技巧: post compact templates, pitfalls, complexity reminders, and short checks tied to current weak or active tags.\n- #错题回炉: schedule spaced review for wrong or nearly forgotten problems and suggest one similar variation.\n- #助教资讯: post concise status summaries, daily reports, and risk alerts.\n\nKeep outputs concise. Prefer Chinese when the learner uses Chinese. Be proactive after submissions, but avoid noisy duplicate reports. Do not paste a full final solution unless explicitly asked by the owner."
306
+ },
307
+ "resources": {
308
+ "requests": {
309
+ "cpu": "150m",
310
+ "memory": "384Mi"
311
+ },
312
+ "limits": {
313
+ "cpu": "1500m",
314
+ "memory": "1Gi"
315
+ }
316
+ },
317
+ "configuration": {}
318
+ }
319
+ ]
320
+ },
321
+ "i18n": {
322
+ "en": {
323
+ "title": "Code Trainer",
324
+ "description": "A single-Buddy algorithm training template with Code Trainer Server App authorization, learning channels, scheduled reviews, recommendations, plans, tips, wrong-problem review, and progress reports."
325
+ },
326
+ "zh-CN": {
327
+ "title": "Code Trainer 算法训练",
328
+ "description": "基于 Code Trainer Server App 授权的单 Buddy 算法训练模板,用多个频道承载自动复盘、题目推荐、学习计划、技巧推送、错题回炉和进度报告。"
329
+ }
330
+ }
331
+ }