jishushell 0.6.5 → 0.7.3

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 (1207) hide show
  1. package/apps/anythingllm-container.yaml +16 -170
  2. package/apps/browserless-chromium-container.yaml +16 -10
  3. package/apps/filebrowser-container.yaml +15 -9
  4. package/apps/hermes-container.yaml +20 -5
  5. package/apps/immich-container-lite.yaml +337 -0
  6. package/apps/immich-container.yaml +371 -0
  7. package/apps/jishu-kb-container.yaml +50 -177
  8. package/apps/ollama-binary.yaml +33 -28
  9. package/apps/ollama-cpu-container.yaml +6 -0
  10. package/apps/ollama-with-hollama-binary.yaml +35 -28
  11. package/apps/openclaw-binary.yaml +35 -15
  12. package/apps/openclaw-container.yaml +29 -11
  13. package/apps/openclaw-with-ollama-container.yaml +9 -2
  14. package/apps/openclaw-with-searxng-container.yaml +38 -6
  15. package/apps/searxng-container.yaml +31 -6
  16. package/apps/weknora-container.yaml +26 -21
  17. package/dependencies/jishushell-panel-0.7.3.tgz +0 -0
  18. package/dist/cli/app.js +244 -213
  19. package/dist/cli/app.js.map +1 -1
  20. package/dist/cli/backup.js +15 -12
  21. package/dist/cli/backup.js.map +1 -1
  22. package/dist/cli/core.d.ts +4 -3
  23. package/dist/cli/core.js +392 -227
  24. package/dist/cli/core.js.map +1 -1
  25. package/dist/cli/doctor.d.ts +1 -1
  26. package/dist/cli/doctor.js +113 -10
  27. package/dist/cli/doctor.js.map +1 -1
  28. package/dist/cli/job.js +62 -14
  29. package/dist/cli/job.js.map +1 -1
  30. package/dist/cli/llm.js +80 -11
  31. package/dist/cli/llm.js.map +1 -1
  32. package/dist/cli/managed-list.d.ts +1 -3
  33. package/dist/cli/managed-list.js +18 -16
  34. package/dist/cli/managed-list.js.map +1 -1
  35. package/dist/cli/migrate.d.ts +2 -0
  36. package/dist/cli/migrate.js +160 -0
  37. package/dist/cli/migrate.js.map +1 -0
  38. package/dist/cli.js +1 -0
  39. package/dist/cli.js.map +1 -1
  40. package/dist/config.d.ts +32 -20
  41. package/dist/config.js +132 -51
  42. package/dist/config.js.map +1 -1
  43. package/dist/control.d.ts +6 -6
  44. package/dist/control.js +31 -23
  45. package/dist/control.js.map +1 -1
  46. package/dist/core.d.ts +5 -5
  47. package/dist/core.js +5 -5
  48. package/dist/core.js.map +1 -1
  49. package/dist/install.d.ts +2 -2
  50. package/dist/install.js +78 -37
  51. package/dist/install.js.map +1 -1
  52. package/dist/routes/admin.d.ts +2 -0
  53. package/dist/routes/admin.js +72 -0
  54. package/dist/routes/admin.js.map +1 -0
  55. package/dist/routes/apps.d.ts +1 -1
  56. package/dist/routes/apps.js +101 -193
  57. package/dist/routes/apps.js.map +1 -1
  58. package/dist/routes/auth.js +1 -1
  59. package/dist/routes/auth.js.map +1 -1
  60. package/dist/routes/backup.js +1 -1
  61. package/dist/routes/backup.js.map +1 -1
  62. package/dist/routes/docker.d.ts +2 -0
  63. package/dist/routes/docker.js +58 -0
  64. package/dist/routes/docker.js.map +1 -0
  65. package/dist/routes/external-mounts.d.ts +1 -1
  66. package/dist/routes/external-mounts.js +1 -1
  67. package/dist/routes/external-mounts.js.map +1 -1
  68. package/dist/routes/file-mounts.d.ts +4 -3
  69. package/dist/routes/file-mounts.js +49 -31
  70. package/dist/routes/file-mounts.js.map +1 -1
  71. package/dist/routes/files-organize.d.ts +2 -2
  72. package/dist/routes/files-organize.js +5 -5
  73. package/dist/routes/files-organize.js.map +1 -1
  74. package/dist/routes/files.d.ts +1 -1
  75. package/dist/routes/files.js +1 -1
  76. package/dist/routes/files.js.map +1 -1
  77. package/dist/routes/instances.d.ts +0 -8
  78. package/dist/routes/instances.js +202 -1560
  79. package/dist/routes/instances.js.map +1 -1
  80. package/dist/routes/integration-apps.d.ts +14 -0
  81. package/dist/routes/integration-apps.js +81 -0
  82. package/dist/routes/integration-apps.js.map +1 -0
  83. package/dist/routes/integrations.d.ts +9 -0
  84. package/dist/routes/integrations.js +12 -0
  85. package/dist/routes/integrations.js.map +1 -0
  86. package/dist/routes/llm-proxy.js +26 -3
  87. package/dist/routes/llm-proxy.js.map +1 -1
  88. package/dist/routes/setup.js +53 -38
  89. package/dist/routes/setup.js.map +1 -1
  90. package/dist/routes/system.js +108 -68
  91. package/dist/routes/system.js.map +1 -1
  92. package/dist/routes/webdav.d.ts +1 -1
  93. package/dist/routes/webdav.js +2 -2
  94. package/dist/routes/webdav.js.map +1 -1
  95. package/dist/server.d.ts +6 -0
  96. package/dist/server.js +368 -233
  97. package/dist/server.js.map +1 -1
  98. package/dist/services/app-common/app-compiler.js +186 -0
  99. package/dist/services/app-common/app-compiler.js.map +1 -0
  100. package/dist/services/app-common/app-shared.d.ts +15 -0
  101. package/dist/services/app-common/app-shared.js +64 -0
  102. package/dist/services/app-common/app-shared.js.map +1 -0
  103. package/dist/services/app-common/capability-service.d.ts +45 -0
  104. package/dist/services/app-common/capability-service.js +331 -0
  105. package/dist/services/app-common/capability-service.js.map +1 -0
  106. package/dist/services/app-common/catalog-service.d.ts +59 -0
  107. package/dist/services/app-common/catalog-service.js +318 -0
  108. package/dist/services/app-common/catalog-service.js.map +1 -0
  109. package/dist/services/app-common/create-pipeline.d.ts +26 -0
  110. package/dist/services/app-common/create-pipeline.js +298 -0
  111. package/dist/services/app-common/create-pipeline.js.map +1 -0
  112. package/dist/services/app-common/delete-service.d.ts +5 -0
  113. package/dist/services/app-common/delete-service.js +109 -0
  114. package/dist/services/app-common/delete-service.js.map +1 -0
  115. package/dist/services/app-common/execution-owner.d.ts +23 -0
  116. package/dist/services/app-common/execution-owner.js +124 -0
  117. package/dist/services/app-common/execution-owner.js.map +1 -0
  118. package/dist/services/app-common/execution-service.d.ts +23 -0
  119. package/dist/services/app-common/execution-service.js +105 -0
  120. package/dist/services/app-common/execution-service.js.map +1 -0
  121. package/dist/services/app-common/id-normalizer.d.ts +31 -0
  122. package/dist/services/app-common/id-normalizer.js +83 -0
  123. package/dist/services/app-common/id-normalizer.js.map +1 -0
  124. package/dist/services/app-common/install-store.d.ts +34 -0
  125. package/dist/services/app-common/install-store.js +261 -0
  126. package/dist/services/app-common/install-store.js.map +1 -0
  127. package/dist/services/app-common/instance-store.d.ts +78 -0
  128. package/dist/services/app-common/instance-store.js +498 -0
  129. package/dist/services/app-common/instance-store.js.map +1 -0
  130. package/dist/services/app-common/integration-refs.d.ts +17 -0
  131. package/dist/services/app-common/integration-refs.js +47 -0
  132. package/dist/services/app-common/integration-refs.js.map +1 -0
  133. package/dist/services/app-common/lifecycle-pipeline.d.ts +62 -0
  134. package/dist/services/app-common/lifecycle-pipeline.js +317 -0
  135. package/dist/services/app-common/lifecycle-pipeline.js.map +1 -0
  136. package/dist/services/app-common/lifecycle-scripts.d.ts +38 -0
  137. package/dist/services/app-common/lifecycle-scripts.js +935 -0
  138. package/dist/services/app-common/lifecycle-scripts.js.map +1 -0
  139. package/dist/services/app-common/lifecycle-service.d.ts +68 -0
  140. package/dist/services/app-common/lifecycle-service.js +475 -0
  141. package/dist/services/app-common/lifecycle-service.js.map +1 -0
  142. package/dist/services/app-common/ownership.d.ts +3 -0
  143. package/dist/services/app-common/ownership.js +11 -0
  144. package/dist/services/app-common/ownership.js.map +1 -0
  145. package/dist/services/app-common/paths.d.ts +29 -0
  146. package/dist/services/app-common/paths.js +34 -0
  147. package/dist/services/app-common/paths.js.map +1 -0
  148. package/dist/services/app-common/platform-transform.d.ts +32 -0
  149. package/dist/services/app-common/platform-transform.js +65 -0
  150. package/dist/services/app-common/platform-transform.js.map +1 -0
  151. package/dist/services/app-common/provide-resolver.d.ts +29 -0
  152. package/dist/services/app-common/provide-resolver.js +129 -0
  153. package/dist/services/app-common/provide-resolver.js.map +1 -0
  154. package/dist/services/app-common/remote-spec.d.ts +14 -0
  155. package/dist/services/app-common/remote-spec.js +58 -0
  156. package/dist/services/app-common/remote-spec.js.map +1 -0
  157. package/dist/services/app-common/runtime-builder.d.ts +1 -0
  158. package/dist/services/app-common/runtime-builder.js +2 -0
  159. package/dist/services/app-common/runtime-builder.js.map +1 -0
  160. package/dist/services/app-common/runtime-facts.d.ts +19 -0
  161. package/dist/services/app-common/runtime-facts.js +128 -0
  162. package/dist/services/app-common/runtime-facts.js.map +1 -0
  163. package/dist/services/app-common/service.d.ts +9 -0
  164. package/dist/services/app-common/service.js +10 -0
  165. package/dist/services/app-common/service.js.map +1 -0
  166. package/dist/services/app-common/spec-materializer.d.ts +8 -0
  167. package/dist/services/app-common/spec-materializer.js +295 -0
  168. package/dist/services/app-common/spec-materializer.js.map +1 -0
  169. package/dist/services/app-common/status-refresh.d.ts +33 -0
  170. package/dist/services/app-common/status-refresh.js +771 -0
  171. package/dist/services/app-common/status-refresh.js.map +1 -0
  172. package/dist/services/app-common/task-service.d.ts +29 -0
  173. package/dist/services/app-common/task-service.js +93 -0
  174. package/dist/services/app-common/task-service.js.map +1 -0
  175. package/dist/services/app-common/terminal-session-manager.js +157 -0
  176. package/dist/services/app-common/terminal-session-manager.js.map +1 -0
  177. package/dist/services/app-modules/browserless/routes.d.ts +9 -0
  178. package/dist/services/app-modules/browserless/routes.js +519 -0
  179. package/dist/services/app-modules/browserless/routes.js.map +1 -0
  180. package/dist/services/app-modules/routes.d.ts +2 -0
  181. package/dist/services/app-modules/routes.js +5 -0
  182. package/dist/services/app-modules/routes.js.map +1 -0
  183. package/dist/services/backup/backup-admin.d.ts +95 -0
  184. package/dist/services/backup/backup-admin.js +246 -0
  185. package/dist/services/backup/backup-admin.js.map +1 -0
  186. package/dist/services/backup/backup-manager.d.ts +264 -0
  187. package/dist/services/backup/backup-manager.js +2318 -0
  188. package/dist/services/backup/backup-manager.js.map +1 -0
  189. package/dist/services/backup/backup-verify.js +240 -0
  190. package/dist/services/backup/backup-verify.js.map +1 -0
  191. package/dist/services/capabilities/browser-policy.d.ts +14 -0
  192. package/dist/services/capabilities/browser-policy.js +141 -0
  193. package/dist/services/capabilities/browser-policy.js.map +1 -0
  194. package/dist/services/capabilities/contract.d.ts +49 -0
  195. package/dist/services/capabilities/contract.js +119 -0
  196. package/dist/services/capabilities/contract.js.map +1 -0
  197. package/dist/services/capabilities/endpoint-validator.d.ts +42 -0
  198. package/dist/services/capabilities/endpoint-validator.js +113 -0
  199. package/dist/services/capabilities/endpoint-validator.js.map +1 -0
  200. package/dist/services/capabilities/health.d.ts +16 -0
  201. package/dist/services/capabilities/health.js +121 -0
  202. package/dist/services/capabilities/health.js.map +1 -0
  203. package/dist/services/capabilities/registry.d.ts +56 -0
  204. package/dist/services/capabilities/registry.js +222 -0
  205. package/dist/services/capabilities/registry.js.map +1 -0
  206. package/dist/services/capabilities/sync.d.ts +7 -0
  207. package/dist/services/capabilities/sync.js +223 -0
  208. package/dist/services/capabilities/sync.js.map +1 -0
  209. package/dist/services/capability-proxy/html-rewriters/browserless.d.ts +1 -0
  210. package/dist/services/capability-proxy/html-rewriters/browserless.js +83 -0
  211. package/dist/services/capability-proxy/html-rewriters/browserless.js.map +1 -0
  212. package/dist/services/capability-proxy/html-rewriters/index.d.ts +12 -0
  213. package/dist/services/capability-proxy/html-rewriters/index.js +25 -0
  214. package/dist/services/capability-proxy/html-rewriters/index.js.map +1 -0
  215. package/dist/services/capability-proxy/html-rewriters/jishukb.d.ts +1 -0
  216. package/dist/services/capability-proxy/html-rewriters/jishukb.js +161 -0
  217. package/dist/services/capability-proxy/html-rewriters/jishukb.js.map +1 -0
  218. package/dist/services/capability-proxy/http.d.ts +7 -0
  219. package/dist/services/capability-proxy/http.js +555 -0
  220. package/dist/services/capability-proxy/http.js.map +1 -0
  221. package/dist/services/capability-proxy/terminal.d.ts +4 -0
  222. package/dist/services/capability-proxy/terminal.js +179 -0
  223. package/dist/services/capability-proxy/terminal.js.map +1 -0
  224. package/dist/services/connections/admin.d.ts +80 -0
  225. package/dist/services/connections/admin.js +337 -0
  226. package/dist/services/connections/admin.js.map +1 -0
  227. package/dist/services/connections/apply.d.ts +104 -0
  228. package/dist/services/connections/apply.js +415 -0
  229. package/dist/services/connections/apply.js.map +1 -0
  230. package/dist/services/connections/resolver.d.ts +82 -0
  231. package/dist/services/connections/resolver.js +289 -0
  232. package/dist/services/connections/resolver.js.map +1 -0
  233. package/dist/services/connections/transactor.d.ts +39 -0
  234. package/dist/services/connections/transactor.js +307 -0
  235. package/dist/services/connections/transactor.js.map +1 -0
  236. package/dist/services/files/bootstrap.d.ts +7 -0
  237. package/dist/services/files/bootstrap.js +16 -0
  238. package/dist/services/files/bootstrap.js.map +1 -0
  239. package/dist/services/files/external-mounts.js +187 -0
  240. package/dist/services/files/external-mounts.js.map +1 -0
  241. package/dist/services/files/files-manager.d.ts +265 -0
  242. package/dist/services/files/files-manager.js +1189 -0
  243. package/dist/services/files/files-manager.js.map +1 -0
  244. package/dist/services/files/files-mounts.d.ts +42 -0
  245. package/dist/services/files/files-mounts.js +207 -0
  246. package/dist/services/files/files-mounts.js.map +1 -0
  247. package/dist/services/files/organize/applier.js +218 -0
  248. package/dist/services/files/organize/applier.js.map +1 -0
  249. package/dist/services/files/organize/rules.js +286 -0
  250. package/dist/services/files/organize/rules.js.map +1 -0
  251. package/dist/services/files/organize/scanner.js +366 -0
  252. package/dist/services/files/organize/scanner.js.map +1 -0
  253. package/dist/services/files/organize/store.js +82 -0
  254. package/dist/services/files/organize/store.js.map +1 -0
  255. package/dist/services/files/photos/upload-page.d.ts +2 -0
  256. package/dist/services/files/photos/upload-page.js +248 -0
  257. package/dist/services/files/photos/upload-page.js.map +1 -0
  258. package/dist/services/files/photos/upload-store.d.ts +74 -0
  259. package/dist/services/files/photos/upload-store.js +432 -0
  260. package/dist/services/files/photos/upload-store.js.map +1 -0
  261. package/dist/services/files/webdav/server.d.ts +47 -0
  262. package/dist/services/files/webdav/server.js +329 -0
  263. package/dist/services/files/webdav/server.js.map +1 -0
  264. package/dist/services/files/webdav/xml-builder.js.map +1 -0
  265. package/dist/services/http/proxy-utils.d.ts +7 -0
  266. package/dist/services/http/proxy-utils.js +29 -0
  267. package/dist/services/http/proxy-utils.js.map +1 -0
  268. package/dist/services/http/request-utils.d.ts +3 -0
  269. package/dist/services/http/request-utils.js +23 -0
  270. package/dist/services/http/request-utils.js.map +1 -0
  271. package/dist/services/instances/admin.d.ts +23 -0
  272. package/dist/services/instances/admin.js +218 -0
  273. package/dist/services/instances/admin.js.map +1 -0
  274. package/dist/services/instances/clone.d.ts +26 -0
  275. package/dist/services/instances/clone.js +78 -0
  276. package/dist/services/instances/clone.js.map +1 -0
  277. package/dist/services/instances/config-admin.d.ts +17 -0
  278. package/dist/services/instances/config-admin.js +181 -0
  279. package/dist/services/instances/config-admin.js.map +1 -0
  280. package/dist/services/instances/manager.d.ts +232 -0
  281. package/dist/services/instances/manager.js +1342 -0
  282. package/dist/services/instances/manager.js.map +1 -0
  283. package/dist/services/instances/pairing.d.ts +17 -0
  284. package/dist/services/instances/pairing.js +53 -0
  285. package/dist/services/instances/pairing.js.map +1 -0
  286. package/dist/services/instances/passwords.js +173 -0
  287. package/dist/services/instances/passwords.js.map +1 -0
  288. package/dist/services/instances/status.d.ts +2 -0
  289. package/dist/services/instances/status.js +11 -0
  290. package/dist/services/instances/status.js.map +1 -0
  291. package/dist/services/instances/types.d.ts +21 -0
  292. package/dist/services/instances/types.js +2 -0
  293. package/dist/services/instances/types.js.map +1 -0
  294. package/dist/services/integrations/anythingllm/integration.d.ts +25 -0
  295. package/dist/services/integrations/anythingllm/integration.js +251 -0
  296. package/dist/services/integrations/anythingllm/integration.js.map +1 -0
  297. package/dist/services/integrations/catalog.d.ts +3 -0
  298. package/dist/services/integrations/catalog.js +73 -0
  299. package/dist/services/integrations/catalog.js.map +1 -0
  300. package/dist/services/integrations/custom/integration.d.ts +28 -0
  301. package/dist/services/integrations/custom/integration.js +179 -0
  302. package/dist/services/integrations/custom/integration.js.map +1 -0
  303. package/dist/services/integrations/hermes/integration.d.ts +194 -0
  304. package/dist/services/integrations/hermes/integration.js +1668 -0
  305. package/dist/services/integrations/hermes/integration.js.map +1 -0
  306. package/dist/services/integrations/immich/client.d.ts +93 -0
  307. package/dist/services/integrations/immich/client.js +458 -0
  308. package/dist/services/integrations/immich/client.js.map +1 -0
  309. package/dist/services/integrations/immich/config.d.ts +15 -0
  310. package/dist/services/integrations/immich/config.js +178 -0
  311. package/dist/services/integrations/immich/config.js.map +1 -0
  312. package/dist/services/integrations/immich/discovery.d.ts +9 -0
  313. package/dist/services/integrations/immich/discovery.js +101 -0
  314. package/dist/services/integrations/immich/discovery.js.map +1 -0
  315. package/dist/services/integrations/immich/gallery-renderer.d.ts +5 -0
  316. package/dist/services/integrations/immich/gallery-renderer.js +150 -0
  317. package/dist/services/integrations/immich/gallery-renderer.js.map +1 -0
  318. package/dist/services/integrations/immich/immich-shim.d.ts +11 -0
  319. package/dist/services/integrations/immich/immich-shim.js +439 -0
  320. package/dist/services/integrations/immich/immich-shim.js.map +1 -0
  321. package/dist/services/integrations/immich/integration.d.ts +18 -0
  322. package/dist/services/integrations/immich/integration.js +64 -0
  323. package/dist/services/integrations/immich/integration.js.map +1 -0
  324. package/dist/services/integrations/immich/photo-library.d.ts +4 -0
  325. package/dist/services/integrations/immich/photo-library.js +63 -0
  326. package/dist/services/integrations/immich/photo-library.js.map +1 -0
  327. package/dist/services/integrations/immich/review-executor.d.ts +3 -0
  328. package/dist/services/integrations/immich/review-executor.js +41 -0
  329. package/dist/services/integrations/immich/review-executor.js.map +1 -0
  330. package/dist/services/integrations/immich/review-session-service.d.ts +27 -0
  331. package/dist/services/integrations/immich/review-session-service.js +206 -0
  332. package/dist/services/integrations/immich/review-session-service.js.map +1 -0
  333. package/dist/services/integrations/immich/review-store.d.ts +47 -0
  334. package/dist/services/integrations/immich/review-store.js +347 -0
  335. package/dist/services/integrations/immich/review-store.js.map +1 -0
  336. package/dist/services/integrations/immich/routes.d.ts +7 -0
  337. package/dist/services/integrations/immich/routes.js +363 -0
  338. package/dist/services/integrations/immich/routes.js.map +1 -0
  339. package/dist/services/integrations/immich/types.d.ts +186 -0
  340. package/dist/services/integrations/immich/types.js +2 -0
  341. package/dist/services/integrations/immich/types.js.map +1 -0
  342. package/dist/services/integrations/index.d.ts +41 -0
  343. package/dist/services/integrations/index.js +60 -0
  344. package/dist/services/integrations/index.js.map +1 -0
  345. package/dist/services/integrations/installable/catalog.d.ts +33 -0
  346. package/dist/services/integrations/installable/catalog.js +88 -0
  347. package/dist/services/integrations/installable/catalog.js.map +1 -0
  348. package/dist/services/integrations/installable/index.d.ts +35 -0
  349. package/dist/services/integrations/installable/index.js +170 -0
  350. package/dist/services/integrations/installable/index.js.map +1 -0
  351. package/dist/services/integrations/installable/installers/integration-probes.d.ts +50 -0
  352. package/dist/services/integrations/installable/installers/integration-probes.js +231 -0
  353. package/dist/services/integrations/installable/installers/integration-probes.js.map +1 -0
  354. package/dist/services/integrations/installable/installers/integration.d.ts +30 -0
  355. package/dist/services/integrations/installable/installers/integration.js +283 -0
  356. package/dist/services/integrations/installable/installers/integration.js.map +1 -0
  357. package/dist/services/integrations/installable/installers/registry-probe.js.map +1 -0
  358. package/dist/services/integrations/installable/installers/shell-script.d.ts +46 -0
  359. package/dist/services/integrations/installable/installers/shell-script.js +487 -0
  360. package/dist/services/integrations/installable/installers/shell-script.js.map +1 -0
  361. package/dist/services/integrations/installable/types.d.ts +130 -0
  362. package/dist/services/integrations/installable/types.js +19 -0
  363. package/dist/services/integrations/installable/types.js.map +1 -0
  364. package/dist/services/integrations/jishukb/integration.d.ts +24 -0
  365. package/dist/services/integrations/jishukb/integration.js +300 -0
  366. package/dist/services/integrations/jishukb/integration.js.map +1 -0
  367. package/dist/services/integrations/openclaw/anythingllm-shim.d.ts +46 -0
  368. package/dist/services/integrations/openclaw/anythingllm-shim.js +281 -0
  369. package/dist/services/integrations/openclaw/anythingllm-shim.js.map +1 -0
  370. package/dist/services/integrations/openclaw/drive-shim.js +490 -0
  371. package/dist/services/integrations/openclaw/drive-shim.js.map +1 -0
  372. package/dist/services/integrations/openclaw/integration.d.ts +438 -0
  373. package/dist/services/integrations/openclaw/integration.js +4629 -0
  374. package/dist/services/integrations/openclaw/integration.js.map +1 -0
  375. package/dist/services/integrations/openclaw/jishukb-native-mcp.d.ts +58 -0
  376. package/dist/services/integrations/openclaw/jishukb-native-mcp.js +373 -0
  377. package/dist/services/integrations/openclaw/jishukb-native-mcp.js.map +1 -0
  378. package/dist/services/integrations/openclaw/jishukb-shim.d.ts +52 -0
  379. package/dist/services/integrations/openclaw/jishukb-shim.js +1357 -0
  380. package/dist/services/integrations/openclaw/jishukb-shim.js.map +1 -0
  381. package/dist/services/integrations/openclaw/mcporter-lite.js +276 -0
  382. package/dist/services/integrations/openclaw/mcporter-lite.js.map +1 -0
  383. package/dist/services/integrations/openclaw/mcporter.d.ts +59 -0
  384. package/dist/services/integrations/openclaw/mcporter.js +143 -0
  385. package/dist/services/integrations/openclaw/mcporter.js.map +1 -0
  386. package/dist/services/integrations/openclaw/native-mcp.d.ts +48 -0
  387. package/dist/services/integrations/openclaw/native-mcp.js +125 -0
  388. package/dist/services/integrations/openclaw/native-mcp.js.map +1 -0
  389. package/dist/services/integrations/openclaw/routes.d.ts +21 -0
  390. package/dist/services/integrations/openclaw/routes.js +1194 -0
  391. package/dist/services/integrations/openclaw/routes.js.map +1 -0
  392. package/dist/services/integrations/registry.d.ts +17 -0
  393. package/dist/services/integrations/registry.js +36 -0
  394. package/dist/services/integrations/registry.js.map +1 -0
  395. package/dist/services/integrations/routes.d.ts +2 -0
  396. package/dist/services/integrations/routes.js +9 -0
  397. package/dist/services/integrations/routes.js.map +1 -0
  398. package/dist/services/integrations/types.d.ts +457 -0
  399. package/dist/services/integrations/types.js +2 -0
  400. package/dist/services/integrations/types.js.map +1 -0
  401. package/dist/services/legacy-migrator/classifier.d.ts +44 -0
  402. package/dist/services/legacy-migrator/classifier.js +309 -0
  403. package/dist/services/legacy-migrator/classifier.js.map +1 -0
  404. package/dist/services/legacy-migrator/executor.d.ts +42 -0
  405. package/dist/services/legacy-migrator/executor.js +637 -0
  406. package/dist/services/legacy-migrator/executor.js.map +1 -0
  407. package/dist/services/legacy-migrator/index.d.ts +31 -0
  408. package/dist/services/legacy-migrator/index.js +34 -0
  409. package/dist/services/legacy-migrator/index.js.map +1 -0
  410. package/dist/services/legacy-migrator/planner.d.ts +8 -0
  411. package/dist/services/legacy-migrator/planner.js +154 -0
  412. package/dist/services/legacy-migrator/planner.js.map +1 -0
  413. package/dist/services/legacy-migrator/provider-settings.d.ts +6 -0
  414. package/dist/services/legacy-migrator/provider-settings.js +72 -0
  415. package/dist/services/legacy-migrator/provider-settings.js.map +1 -0
  416. package/dist/services/legacy-migrator/report.d.ts +9 -0
  417. package/dist/services/legacy-migrator/report.js +99 -0
  418. package/dist/services/legacy-migrator/report.js.map +1 -0
  419. package/dist/services/legacy-migrator/scanner.d.ts +13 -0
  420. package/dist/services/legacy-migrator/scanner.js +157 -0
  421. package/dist/services/legacy-migrator/scanner.js.map +1 -0
  422. package/dist/services/legacy-migrator/types.d.ts +97 -0
  423. package/dist/services/legacy-migrator/types.js +23 -0
  424. package/dist/services/legacy-migrator/types.js.map +1 -0
  425. package/dist/services/llm-proxy/instance-proxy.d.ts +17 -1
  426. package/dist/services/llm-proxy/instance-proxy.js +171 -44
  427. package/dist/services/llm-proxy/instance-proxy.js.map +1 -1
  428. package/dist/services/llm-proxy/probe.js +5 -14
  429. package/dist/services/llm-proxy/probe.js.map +1 -1
  430. package/dist/services/llm-proxy/providers.js +23 -31
  431. package/dist/services/llm-proxy/providers.js.map +1 -1
  432. package/dist/services/llm-proxy/ssrf.d.ts +11 -4
  433. package/dist/services/llm-proxy/ssrf.js +45 -7
  434. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  435. package/dist/services/llm-proxy/validate-key.js +16 -37
  436. package/dist/services/llm-proxy/validate-key.js.map +1 -1
  437. package/dist/services/repair/runtime-repair.d.ts +22 -0
  438. package/dist/services/repair/runtime-repair.js +374 -0
  439. package/dist/services/repair/runtime-repair.js.map +1 -0
  440. package/dist/services/runtime/docker-network.d.ts +8 -0
  441. package/dist/services/runtime/docker-network.js +123 -0
  442. package/dist/services/runtime/docker-network.js.map +1 -0
  443. package/dist/services/runtime/driver-registry.d.ts +25 -0
  444. package/dist/services/runtime/driver-registry.js +22 -0
  445. package/dist/services/runtime/driver-registry.js.map +1 -0
  446. package/dist/services/runtime/drivers/nomad.d.ts +261 -0
  447. package/dist/services/runtime/drivers/nomad.js +3122 -0
  448. package/dist/services/runtime/drivers/nomad.js.map +1 -0
  449. package/dist/services/runtime/errors.d.ts +3 -3
  450. package/dist/services/runtime/errors.js +3 -3
  451. package/dist/services/runtime/instance.d.ts +14 -16
  452. package/dist/services/runtime/instance.js +93 -123
  453. package/dist/services/runtime/instance.js.map +1 -1
  454. package/dist/services/runtime/job-id.d.ts +1 -1
  455. package/dist/services/runtime/job-id.js +1 -1
  456. package/dist/services/runtime/mcp-shims/firewall.d.ts +2 -2
  457. package/dist/services/runtime/mcp-shims/firewall.js +2 -2
  458. package/dist/services/runtime/mcp-shims/searxng-shim.d.ts +3 -5
  459. package/dist/services/runtime/mcp-shims/searxng-shim.js +3 -5
  460. package/dist/services/runtime/mcp-shims/searxng-shim.js.map +1 -1
  461. package/dist/services/runtime/mcp-shims/write-mcp-entry.d.ts +20 -20
  462. package/dist/services/runtime/mcp-shims/write-mcp-entry.js +16 -16
  463. package/dist/services/runtime/mcp-shims/write-mcp-entry.js.map +1 -1
  464. package/dist/services/runtime/ownership-marker.d.ts +83 -0
  465. package/dist/services/runtime/ownership-marker.js +109 -0
  466. package/dist/services/runtime/ownership-marker.js.map +1 -0
  467. package/dist/services/runtime/service-manager.d.ts +2 -0
  468. package/dist/services/runtime/service-manager.js +18 -0
  469. package/dist/services/runtime/service-manager.js.map +1 -0
  470. package/dist/services/runtime/types.d.ts +23 -501
  471. package/dist/services/runtime/types.js +0 -12
  472. package/dist/services/runtime/types.js.map +1 -1
  473. package/dist/services/runtime/workload-compiler.d.ts +17 -0
  474. package/dist/services/runtime/workload-compiler.js +550 -0
  475. package/dist/services/runtime/workload-compiler.js.map +1 -0
  476. package/dist/services/runtime/workload-types.d.ts +11 -0
  477. package/dist/services/runtime/workload-types.js +2 -0
  478. package/dist/services/runtime/workload-types.js.map +1 -0
  479. package/dist/services/setup/core-manager.d.ts +50 -0
  480. package/dist/services/setup/core-manager.js +456 -0
  481. package/dist/services/setup/core-manager.js.map +1 -0
  482. package/dist/services/setup/plugin-installer.js +136 -0
  483. package/dist/services/setup/plugin-installer.js.map +1 -0
  484. package/dist/services/setup/setup-manager.d.ts +158 -0
  485. package/dist/services/setup/setup-manager.js +2724 -0
  486. package/dist/services/setup/setup-manager.js.map +1 -0
  487. package/dist/services/system/cli-command.d.ts +5 -0
  488. package/dist/services/system/cli-command.js +18 -0
  489. package/dist/services/system/cli-command.js.map +1 -0
  490. package/dist/services/system/macos-launchd.js +312 -0
  491. package/dist/services/system/macos-launchd.js.map +1 -0
  492. package/dist/services/system/repair-orchestrator.d.ts +71 -0
  493. package/dist/services/system/repair-orchestrator.js +412 -0
  494. package/dist/services/system/repair-orchestrator.js.map +1 -0
  495. package/dist/services/system/runtime-ownership.d.ts +36 -0
  496. package/dist/services/system/runtime-ownership.js +250 -0
  497. package/dist/services/system/runtime-ownership.js.map +1 -0
  498. package/dist/services/system/system-monitor.js +96 -0
  499. package/dist/services/system/system-monitor.js.map +1 -0
  500. package/dist/services/system/system-ollama-provider.d.ts +14 -0
  501. package/dist/services/system/system-ollama-provider.js +129 -0
  502. package/dist/services/system/system-ollama-provider.js.map +1 -0
  503. package/dist/services/system/system-reconciler.d.ts +59 -0
  504. package/dist/services/system/system-reconciler.js +763 -0
  505. package/dist/services/system/system-reconciler.js.map +1 -0
  506. package/dist/services/system/update-manager.d.ts +43 -0
  507. package/dist/services/system/update-manager.js +315 -0
  508. package/dist/services/system/update-manager.js.map +1 -0
  509. package/dist/services/system/upgrade-finalize.d.ts +80 -0
  510. package/dist/services/system/upgrade-finalize.js +507 -0
  511. package/dist/services/system/upgrade-finalize.js.map +1 -0
  512. package/dist/services/tasks/registry.d.ts +44 -0
  513. package/dist/services/tasks/registry.js +90 -0
  514. package/dist/services/tasks/registry.js.map +1 -0
  515. package/dist/services/telemetry/activation.d.ts +6 -2
  516. package/dist/services/telemetry/activation.js +6 -2
  517. package/dist/services/telemetry/activation.js.map +1 -1
  518. package/dist/services/telemetry/heartbeat.d.ts +6 -2
  519. package/dist/services/telemetry/heartbeat.js +6 -2
  520. package/dist/services/telemetry/heartbeat.js.map +1 -1
  521. package/dist/services/workspaces/builder.d.ts +29 -0
  522. package/dist/services/workspaces/builder.js +186 -0
  523. package/dist/services/workspaces/builder.js.map +1 -0
  524. package/dist/types.d.ts +350 -48
  525. package/dist/utils/instance-lock.d.ts +2 -2
  526. package/dist/utils/instance-lock.js +2 -2
  527. package/dist/utils/path-safety.js +1 -1
  528. package/dist/utils/service-user.d.ts +13 -0
  529. package/dist/utils/service-user.js +129 -0
  530. package/dist/utils/service-user.js.map +1 -0
  531. package/install/jishu-install.sh +107 -27
  532. package/install/jishu-uninstall.sh +8 -0
  533. package/install/post-install.sh +162 -185
  534. package/install/post-uninstall.sh +6 -0
  535. package/node_modules/@fastify/static/.github/workflows/ci.yml +1 -1
  536. package/node_modules/@fastify/static/.github/workflows/lock-threads.yml +19 -0
  537. package/node_modules/@fastify/static/LICENSE +1 -3
  538. package/node_modules/@fastify/static/example/server-benchmark.js +39 -0
  539. package/node_modules/@fastify/static/index.js +169 -23
  540. package/node_modules/@fastify/static/lib/dirList.js +20 -6
  541. package/node_modules/@fastify/static/package.json +10 -8
  542. package/node_modules/@fastify/static/test/dir-list.test.js +82 -0
  543. package/node_modules/@fastify/static/test/static.test.js +326 -4
  544. package/node_modules/@fastify/static/types/index.d.ts +0 -4
  545. package/node_modules/@fastify/static/types/index.test-d.ts +1 -1
  546. package/node_modules/brace-expansion/dist/commonjs/index.js +24 -14
  547. package/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -1
  548. package/node_modules/brace-expansion/dist/esm/index.js +24 -14
  549. package/node_modules/brace-expansion/dist/esm/index.js.map +1 -1
  550. package/node_modules/brace-expansion/package.json +2 -2
  551. package/node_modules/content-disposition/README.md +21 -22
  552. package/node_modules/content-disposition/index.js +122 -44
  553. package/node_modules/content-disposition/package.json +16 -20
  554. package/node_modules/fast-uri/index.js +1 -1
  555. package/node_modules/fast-uri/package.json +1 -1
  556. package/node_modules/fast-uri/test/security.test.js +28 -0
  557. package/node_modules/fastify/SECURITY.md +1 -1
  558. package/node_modules/fastify/SPONSORS.md +6 -4
  559. package/node_modules/fastify/docs/Guides/Database.md +0 -28
  560. package/node_modules/fastify/docs/Guides/Ecosystem.md +13 -2
  561. package/node_modules/fastify/docs/Guides/Serverless.md +2 -2
  562. package/node_modules/fastify/docs/Guides/Write-Plugin.md +1 -1
  563. package/node_modules/fastify/docs/Reference/Encapsulation.md +27 -26
  564. package/node_modules/fastify/docs/Reference/Errors.md +10 -4
  565. package/node_modules/fastify/docs/Reference/HTTP2.md +10 -10
  566. package/node_modules/fastify/docs/Reference/Hooks.md +4 -4
  567. package/node_modules/fastify/docs/Reference/Index.md +14 -16
  568. package/node_modules/fastify/docs/Reference/LTS.md +12 -13
  569. package/node_modules/fastify/docs/Reference/Lifecycle.md +9 -8
  570. package/node_modules/fastify/docs/Reference/Logging.md +44 -39
  571. package/node_modules/fastify/docs/Reference/Middleware.md +21 -25
  572. package/node_modules/fastify/docs/Reference/Principles.md +2 -2
  573. package/node_modules/fastify/docs/Reference/Reply.md +6 -1
  574. package/node_modules/fastify/docs/Reference/Request.md +27 -16
  575. package/node_modules/fastify/docs/Reference/Routes.md +5 -2
  576. package/node_modules/fastify/docs/Reference/Server.md +31 -3
  577. package/node_modules/fastify/docs/Reference/Type-Providers.md +29 -5
  578. package/node_modules/fastify/docs/Reference/Validation-and-Serialization.md +15 -2
  579. package/node_modules/fastify/docs/Reference/Warnings.md +7 -6
  580. package/node_modules/fastify/eslint.config.js +7 -2
  581. package/node_modules/fastify/fastify.d.ts +8 -3
  582. package/node_modules/fastify/fastify.js +43 -14
  583. package/node_modules/fastify/lib/content-type-parser.js +13 -1
  584. package/node_modules/fastify/lib/decorate.js +11 -3
  585. package/node_modules/fastify/lib/error-handler.js +4 -3
  586. package/node_modules/fastify/lib/error-serializer.js +59 -59
  587. package/node_modules/fastify/lib/errors.js +16 -1
  588. package/node_modules/fastify/lib/four-oh-four.js +14 -9
  589. package/node_modules/fastify/lib/handle-request.js +11 -5
  590. package/node_modules/fastify/lib/plugin-override.js +2 -1
  591. package/node_modules/fastify/lib/plugin-utils.js +5 -5
  592. package/node_modules/fastify/lib/reply.js +63 -8
  593. package/node_modules/fastify/lib/request.js +14 -4
  594. package/node_modules/fastify/lib/route.js +20 -6
  595. package/node_modules/fastify/lib/schema-controller.js +1 -1
  596. package/node_modules/fastify/lib/schemas.js +37 -30
  597. package/node_modules/fastify/lib/symbols.js +3 -1
  598. package/node_modules/fastify/lib/validation.js +1 -13
  599. package/node_modules/fastify/lib/warnings.js +3 -3
  600. package/node_modules/fastify/package.json +13 -15
  601. package/node_modules/fastify/scripts/validate-ecosystem-links.js +1 -0
  602. package/node_modules/fastify/test/bundler/esbuild/package.json +1 -1
  603. package/node_modules/fastify/test/close-pipelining.test.js +1 -2
  604. package/node_modules/fastify/test/custom-http-server.test.js +38 -0
  605. package/node_modules/fastify/test/decorator-instance-properties.test.js +63 -0
  606. package/node_modules/fastify/test/diagnostics-channel/async-error-handler.test.js +74 -0
  607. package/node_modules/fastify/test/hooks.test.js +23 -0
  608. package/node_modules/fastify/test/http-methods/get.test.js +1 -1
  609. package/node_modules/fastify/test/http2/plain.test.js +135 -0
  610. package/node_modules/fastify/test/http2/secure-with-fallback.test.js +1 -1
  611. package/node_modules/fastify/test/https/https.test.js +1 -2
  612. package/node_modules/fastify/test/internals/errors.test.js +31 -1
  613. package/node_modules/fastify/test/internals/plugin.test.js +3 -1
  614. package/node_modules/fastify/test/internals/request.test.js +27 -3
  615. package/node_modules/fastify/test/internals/schema-controller-perf.test.js +33 -0
  616. package/node_modules/fastify/test/logger/logging.test.js +18 -1
  617. package/node_modules/fastify/test/logger/options.test.js +38 -1
  618. package/node_modules/fastify/test/reply-error.test.js +1 -1
  619. package/node_modules/fastify/test/reply-trailers.test.js +70 -0
  620. package/node_modules/fastify/test/request-media-type.test.js +105 -0
  621. package/node_modules/fastify/test/route-prefix.test.js +34 -0
  622. package/node_modules/fastify/test/router-options.test.js +222 -11
  623. package/node_modules/fastify/test/schema-serialization.test.js +108 -0
  624. package/node_modules/fastify/test/schema-validation.test.js +24 -0
  625. package/node_modules/fastify/test/scripts/validate-ecosystem-links.test.js +40 -57
  626. package/node_modules/fastify/test/throw.test.js +14 -0
  627. package/node_modules/fastify/test/trust-proxy.test.js +21 -0
  628. package/node_modules/fastify/test/types/content-type-parser.tst.ts +70 -0
  629. package/node_modules/fastify/test/types/decorate-request-reply.tst.ts +18 -0
  630. package/node_modules/fastify/test/types/dummy-plugin.mts +9 -0
  631. package/node_modules/fastify/test/types/errors.tst.ts +91 -0
  632. package/node_modules/fastify/test/types/fastify.tst.ts +351 -0
  633. package/node_modules/fastify/test/types/hooks.tst.ts +578 -0
  634. package/node_modules/fastify/test/types/instance.tst.ts +597 -0
  635. package/node_modules/fastify/test/types/logger.tst.ts +276 -0
  636. package/node_modules/fastify/test/types/plugin.tst.ts +96 -0
  637. package/node_modules/fastify/test/types/register.tst.ts +245 -0
  638. package/node_modules/fastify/test/types/reply.tst.ts +297 -0
  639. package/node_modules/fastify/test/types/request.tst.ts +199 -0
  640. package/node_modules/fastify/test/types/route.tst.ts +576 -0
  641. package/node_modules/fastify/test/types/schema.tst.ts +135 -0
  642. package/node_modules/fastify/test/types/serverFactory.tst.ts +37 -0
  643. package/node_modules/fastify/test/types/tsconfig.json +9 -0
  644. package/node_modules/fastify/test/types/type-provider.tst.ts +1219 -0
  645. package/node_modules/fastify/test/types/using.tst.ts +14 -0
  646. package/node_modules/fastify/types/errors.d.ts +3 -0
  647. package/node_modules/fastify/types/request.d.ts +23 -2
  648. package/node_modules/glob/README.md +39 -130
  649. package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
  650. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
  651. package/node_modules/glob/dist/commonjs/glob.js +2 -1
  652. package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
  653. package/node_modules/glob/dist/commonjs/index.min.js +4 -0
  654. package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
  655. package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
  656. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
  657. package/node_modules/glob/dist/commonjs/pattern.js +4 -0
  658. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
  659. package/node_modules/glob/dist/esm/glob.d.ts +8 -0
  660. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
  661. package/node_modules/glob/dist/esm/glob.js +2 -1
  662. package/node_modules/glob/dist/esm/glob.js.map +1 -1
  663. package/node_modules/glob/dist/esm/index.min.js +4 -0
  664. package/node_modules/glob/dist/esm/index.min.js.map +7 -0
  665. package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
  666. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
  667. package/node_modules/glob/dist/esm/pattern.js +4 -0
  668. package/node_modules/glob/dist/esm/pattern.js.map +1 -1
  669. package/node_modules/glob/package.json +38 -37
  670. package/node_modules/jishushell-panel/README.md +4 -4
  671. package/node_modules/jishushell-panel/output/dist/server.js +17 -6
  672. package/node_modules/jishushell-panel/output/dist/server.js.map +1 -1
  673. package/node_modules/jishushell-panel/output/public/assets/ApiKeyField-Ce5d1xna.js +1 -0
  674. package/node_modules/jishushell-panel/output/public/assets/Dashboard-BXame3yg.js +1 -0
  675. package/node_modules/jishushell-panel/output/public/assets/HermesChatPanel-BHZtPCJd.js +1 -0
  676. package/node_modules/jishushell-panel/output/public/assets/HermesConfigForm-CB3GbNX9.js +4 -0
  677. package/node_modules/jishushell-panel/output/public/assets/InitPassword-Boab9F6g.js +1 -0
  678. package/node_modules/jishushell-panel/output/public/assets/InstanceDetail-DrIWCqo-.js +14 -0
  679. package/node_modules/jishushell-panel/output/public/assets/Login-CzpOkNau.js +1 -0
  680. package/node_modules/jishushell-panel/output/public/assets/NewInstance-CANXyCcL.js +1 -0
  681. package/node_modules/jishushell-panel/output/public/assets/ProviderRecommendations-BABo9VOC.js +1 -0
  682. package/node_modules/jishushell-panel/output/public/assets/Settings-CKp5XxFh.js +1 -0
  683. package/node_modules/jishushell-panel/output/public/assets/Setup-C7xVDPow.js +1 -0
  684. package/node_modules/jishushell-panel/output/public/assets/WeixinLoginPanel-B765Xz4C.js +1 -0
  685. package/node_modules/jishushell-panel/output/public/assets/api-C70Gt678.js +4 -0
  686. package/node_modules/jishushell-panel/output/public/assets/index-Bs6DSbiR.js +23 -0
  687. package/node_modules/jishushell-panel/output/public/assets/index-DnnqTf7s.css +1 -0
  688. package/node_modules/jishushell-panel/output/public/assets/registry-sWIZsIEF.js +2 -0
  689. package/node_modules/jishushell-panel/output/public/assets/rolldown-runtime-QTnfLwEv.js +1 -0
  690. package/node_modules/jishushell-panel/output/public/assets/setup-task-q21GnI0E.js +1 -0
  691. package/node_modules/jishushell-panel/output/public/assets/usePolling-D4IDOQd_.js +1 -0
  692. package/node_modules/jishushell-panel/output/public/assets/vendor-i18n-Df8aUdv8.js +1 -0
  693. package/node_modules/jishushell-panel/output/public/assets/vendor-react-0L0rjmYG.js +8 -0
  694. package/node_modules/jishushell-panel/output/public/index.html +6 -4
  695. package/node_modules/jishushell-panel/package.json +2 -2
  696. package/node_modules/semver/classes/range.js +17 -4
  697. package/node_modules/semver/package.json +2 -2
  698. package/package.json +12 -64
  699. package/scripts/check-app-path-boundaries.mjs +121 -0
  700. package/scripts/check-app-spec.mjs +123 -29
  701. package/scripts/check-architecture-boundaries.mjs +178 -0
  702. package/scripts/check-colima-launchd.mjs +10 -8
  703. package/scripts/check-integration-isolation.ts +541 -0
  704. package/scripts/check-new-file-tests.mjs +11 -3
  705. package/scripts/check-open-core-boundaries.mjs +60 -10
  706. package/scripts/check-test-layering.sh +1 -1
  707. package/scripts/fixtures/instances/hermes-sample/instance.json +3 -2
  708. package/scripts/fixtures/instances/legacy-openclaw-sample/instance.json +1 -1
  709. package/scripts/local-web-upgrade-test.README +4 -3
  710. package/scripts/local-web-upgrade-test.example.env +2 -2
  711. package/scripts/local-web-upgrade-test.sh +14 -1
  712. package/scripts/pack-gui-and-send-pi.sh +43 -0
  713. package/scripts/perf/instances.js +1 -1
  714. package/scripts/prune-open-core-dist.mjs +89 -2
  715. package/scripts/smoke/hermes-bootstrap.sh +5 -5
  716. package/templates/hermes-entrypoint.sh +19 -29
  717. package/apps/openwebui-container.yaml +0 -97
  718. package/apps/playwright-container.yaml +0 -126
  719. package/dependencies/jishushell-panel-0.6.5.tgz +0 -0
  720. package/dist/crypto-shim.d.ts +0 -1
  721. package/dist/crypto-shim.js +0 -2
  722. package/dist/crypto-shim.js.map +0 -1
  723. package/dist/routes/agent-apps.d.ts +0 -14
  724. package/dist/routes/agent-apps.js +0 -77
  725. package/dist/routes/agent-apps.js.map +0 -1
  726. package/dist/routes/internal.d.ts +0 -2
  727. package/dist/routes/internal.js +0 -55
  728. package/dist/routes/internal.js.map +0 -1
  729. package/dist/routes/openclaw-routes.d.ts +0 -22
  730. package/dist/routes/openclaw-routes.js +0 -1020
  731. package/dist/routes/openclaw-routes.js.map +0 -1
  732. package/dist/routes/runtime.d.ts +0 -15
  733. package/dist/routes/runtime.js +0 -76
  734. package/dist/routes/runtime.js.map +0 -1
  735. package/dist/services/agent-apps/catalog.d.ts +0 -33
  736. package/dist/services/agent-apps/catalog.js +0 -88
  737. package/dist/services/agent-apps/catalog.js.map +0 -1
  738. package/dist/services/agent-apps/index.d.ts +0 -36
  739. package/dist/services/agent-apps/index.js +0 -171
  740. package/dist/services/agent-apps/index.js.map +0 -1
  741. package/dist/services/agent-apps/installers/adapter-probes.d.ts +0 -49
  742. package/dist/services/agent-apps/installers/adapter-probes.js +0 -230
  743. package/dist/services/agent-apps/installers/adapter-probes.js.map +0 -1
  744. package/dist/services/agent-apps/installers/adapter.d.ts +0 -30
  745. package/dist/services/agent-apps/installers/adapter.js +0 -171
  746. package/dist/services/agent-apps/installers/adapter.js.map +0 -1
  747. package/dist/services/agent-apps/installers/registry-probe.js.map +0 -1
  748. package/dist/services/agent-apps/installers/shell-script.d.ts +0 -47
  749. package/dist/services/agent-apps/installers/shell-script.js +0 -488
  750. package/dist/services/agent-apps/installers/shell-script.js.map +0 -1
  751. package/dist/services/agent-apps/types.d.ts +0 -128
  752. package/dist/services/agent-apps/types.js +0 -17
  753. package/dist/services/agent-apps/types.js.map +0 -1
  754. package/dist/services/app/app-compiler.js +0 -185
  755. package/dist/services/app/app-compiler.js.map +0 -1
  756. package/dist/services/app/app-manager.d.ts +0 -184
  757. package/dist/services/app/app-manager.js +0 -2933
  758. package/dist/services/app/app-manager.js.map +0 -1
  759. package/dist/services/app/custom-manager.d.ts +0 -27
  760. package/dist/services/app/custom-manager.js +0 -382
  761. package/dist/services/app/custom-manager.js.map +0 -1
  762. package/dist/services/app/hermes-agent-manager.d.ts +0 -20
  763. package/dist/services/app/hermes-agent-manager.js +0 -299
  764. package/dist/services/app/hermes-agent-manager.js.map +0 -1
  765. package/dist/services/app/id-normalizer.d.ts +0 -27
  766. package/dist/services/app/id-normalizer.js +0 -77
  767. package/dist/services/app/id-normalizer.js.map +0 -1
  768. package/dist/services/app/ollama-manager.d.ts +0 -18
  769. package/dist/services/app/ollama-manager.js +0 -224
  770. package/dist/services/app/ollama-manager.js.map +0 -1
  771. package/dist/services/app/openclaw-manager.d.ts +0 -63
  772. package/dist/services/app/openclaw-manager.js +0 -1215
  773. package/dist/services/app/openclaw-manager.js.map +0 -1
  774. package/dist/services/app/paths.d.ts +0 -27
  775. package/dist/services/app/paths.js +0 -40
  776. package/dist/services/app/paths.js.map +0 -1
  777. package/dist/services/app/platform-transform.d.ts +0 -32
  778. package/dist/services/app/platform-transform.js +0 -65
  779. package/dist/services/app/platform-transform.js.map +0 -1
  780. package/dist/services/app/provide-resolver.d.ts +0 -29
  781. package/dist/services/app/provide-resolver.js +0 -135
  782. package/dist/services/app/provide-resolver.js.map +0 -1
  783. package/dist/services/app/registry.d.ts +0 -17
  784. package/dist/services/app/registry.js +0 -31
  785. package/dist/services/app/registry.js.map +0 -1
  786. package/dist/services/app/remote-spec.d.ts +0 -14
  787. package/dist/services/app/remote-spec.js +0 -58
  788. package/dist/services/app/remote-spec.js.map +0 -1
  789. package/dist/services/app/terminal-session-manager.js +0 -157
  790. package/dist/services/app/terminal-session-manager.js.map +0 -1
  791. package/dist/services/app/types.d.ts +0 -74
  792. package/dist/services/app/types.js +0 -16
  793. package/dist/services/app/types.js.map +0 -1
  794. package/dist/services/app-config-admin.d.ts +0 -17
  795. package/dist/services/app-config-admin.js +0 -177
  796. package/dist/services/app-config-admin.js.map +0 -1
  797. package/dist/services/app-create-from-installed.d.ts +0 -23
  798. package/dist/services/app-create-from-installed.js +0 -75
  799. package/dist/services/app-create-from-installed.js.map +0 -1
  800. package/dist/services/app-passwords.js +0 -173
  801. package/dist/services/app-passwords.js.map +0 -1
  802. package/dist/services/backup-admin.d.ts +0 -101
  803. package/dist/services/backup-admin.js +0 -259
  804. package/dist/services/backup-admin.js.map +0 -1
  805. package/dist/services/backup-manager.d.ts +0 -264
  806. package/dist/services/backup-manager.js +0 -2263
  807. package/dist/services/backup-manager.js.map +0 -1
  808. package/dist/services/backup-verify.js +0 -240
  809. package/dist/services/backup-verify.js.map +0 -1
  810. package/dist/services/capability-endpoint-validator.d.ts +0 -41
  811. package/dist/services/capability-endpoint-validator.js +0 -114
  812. package/dist/services/capability-endpoint-validator.js.map +0 -1
  813. package/dist/services/capability-health.d.ts +0 -16
  814. package/dist/services/capability-health.js +0 -121
  815. package/dist/services/capability-health.js.map +0 -1
  816. package/dist/services/capability-registry.d.ts +0 -29
  817. package/dist/services/capability-registry.js +0 -176
  818. package/dist/services/capability-registry.js.map +0 -1
  819. package/dist/services/capability-sync.d.ts +0 -4
  820. package/dist/services/capability-sync.js +0 -220
  821. package/dist/services/capability-sync.js.map +0 -1
  822. package/dist/services/connection-admin.d.ts +0 -74
  823. package/dist/services/connection-admin.js +0 -287
  824. package/dist/services/connection-admin.js.map +0 -1
  825. package/dist/services/connection-apply.d.ts +0 -91
  826. package/dist/services/connection-apply.js +0 -471
  827. package/dist/services/connection-apply.js.map +0 -1
  828. package/dist/services/connection-resolver.d.ts +0 -65
  829. package/dist/services/connection-resolver.js +0 -281
  830. package/dist/services/connection-resolver.js.map +0 -1
  831. package/dist/services/connection-transactor.d.ts +0 -39
  832. package/dist/services/connection-transactor.js +0 -354
  833. package/dist/services/connection-transactor.js.map +0 -1
  834. package/dist/services/core-manager.d.ts +0 -50
  835. package/dist/services/core-manager.js +0 -411
  836. package/dist/services/core-manager.js.map +0 -1
  837. package/dist/services/external-mounts.js +0 -187
  838. package/dist/services/external-mounts.js.map +0 -1
  839. package/dist/services/files-manager.d.ts +0 -252
  840. package/dist/services/files-manager.js +0 -1156
  841. package/dist/services/files-manager.js.map +0 -1
  842. package/dist/services/files-mounts.d.ts +0 -42
  843. package/dist/services/files-mounts.js +0 -207
  844. package/dist/services/files-mounts.js.map +0 -1
  845. package/dist/services/instance-admin.d.ts +0 -26
  846. package/dist/services/instance-admin.js +0 -218
  847. package/dist/services/instance-admin.js.map +0 -1
  848. package/dist/services/instance-manager.d.ts +0 -192
  849. package/dist/services/instance-manager.js +0 -1289
  850. package/dist/services/instance-manager.js.map +0 -1
  851. package/dist/services/macos-launchd.js +0 -312
  852. package/dist/services/macos-launchd.js.map +0 -1
  853. package/dist/services/nomad-manager.d.ts +0 -307
  854. package/dist/services/nomad-manager.js +0 -3958
  855. package/dist/services/nomad-manager.js.map +0 -1
  856. package/dist/services/organize/applier.js +0 -218
  857. package/dist/services/organize/applier.js.map +0 -1
  858. package/dist/services/organize/rules.js +0 -286
  859. package/dist/services/organize/rules.js.map +0 -1
  860. package/dist/services/organize/scanner.js +0 -366
  861. package/dist/services/organize/scanner.js.map +0 -1
  862. package/dist/services/organize/store.js +0 -82
  863. package/dist/services/organize/store.js.map +0 -1
  864. package/dist/services/plugin-installer.js +0 -128
  865. package/dist/services/plugin-installer.js.map +0 -1
  866. package/dist/services/process-manager.d.ts +0 -25
  867. package/dist/services/process-manager.js +0 -568
  868. package/dist/services/process-manager.js.map +0 -1
  869. package/dist/services/runtime/adapters/custom.d.ts +0 -20
  870. package/dist/services/runtime/adapters/custom.js +0 -188
  871. package/dist/services/runtime/adapters/custom.js.map +0 -1
  872. package/dist/services/runtime/adapters/hermes.d.ts +0 -204
  873. package/dist/services/runtime/adapters/hermes.js +0 -1684
  874. package/dist/services/runtime/adapters/hermes.js.map +0 -1
  875. package/dist/services/runtime/adapters/openclaw-mcporter.d.ts +0 -45
  876. package/dist/services/runtime/adapters/openclaw-mcporter.js +0 -108
  877. package/dist/services/runtime/adapters/openclaw-mcporter.js.map +0 -1
  878. package/dist/services/runtime/adapters/openclaw.d.ts +0 -426
  879. package/dist/services/runtime/adapters/openclaw.js +0 -3975
  880. package/dist/services/runtime/adapters/openclaw.js.map +0 -1
  881. package/dist/services/runtime/index.d.ts +0 -34
  882. package/dist/services/runtime/index.js +0 -51
  883. package/dist/services/runtime/index.js.map +0 -1
  884. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +0 -46
  885. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +0 -281
  886. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +0 -1
  887. package/dist/services/runtime/mcp-shims/drive-shim.js +0 -490
  888. package/dist/services/runtime/mcp-shims/drive-shim.js.map +0 -1
  889. package/dist/services/runtime/mcp-shims/jishukb-shim.d.ts +0 -48
  890. package/dist/services/runtime/mcp-shims/jishukb-shim.js +0 -723
  891. package/dist/services/runtime/mcp-shims/jishukb-shim.js.map +0 -1
  892. package/dist/services/runtime/mcp-shims/mcporter-lite.js +0 -276
  893. package/dist/services/runtime/mcp-shims/mcporter-lite.js.map +0 -1
  894. package/dist/services/runtime/migrations.d.ts +0 -23
  895. package/dist/services/runtime/migrations.js +0 -125
  896. package/dist/services/runtime/migrations.js.map +0 -1
  897. package/dist/services/runtime/registry.d.ts +0 -13
  898. package/dist/services/runtime/registry.js +0 -32
  899. package/dist/services/runtime/registry.js.map +0 -1
  900. package/dist/services/runtime-identity.d.ts +0 -13
  901. package/dist/services/runtime-identity.js +0 -166
  902. package/dist/services/runtime-identity.js.map +0 -1
  903. package/dist/services/runtime-repair.d.ts +0 -52
  904. package/dist/services/runtime-repair.js +0 -352
  905. package/dist/services/runtime-repair.js.map +0 -1
  906. package/dist/services/setup-manager.d.ts +0 -158
  907. package/dist/services/setup-manager.js +0 -2740
  908. package/dist/services/setup-manager.js.map +0 -1
  909. package/dist/services/suggestions.d.ts +0 -27
  910. package/dist/services/suggestions.js +0 -133
  911. package/dist/services/suggestions.js.map +0 -1
  912. package/dist/services/system-monitor.js +0 -79
  913. package/dist/services/system-monitor.js.map +0 -1
  914. package/dist/services/system-ollama-provider.d.ts +0 -14
  915. package/dist/services/system-ollama-provider.js +0 -125
  916. package/dist/services/system-ollama-provider.js.map +0 -1
  917. package/dist/services/system-reconciler.d.ts +0 -72
  918. package/dist/services/system-reconciler.js +0 -600
  919. package/dist/services/system-reconciler.js.map +0 -1
  920. package/dist/services/task-registry.d.ts +0 -44
  921. package/dist/services/task-registry.js +0 -76
  922. package/dist/services/task-registry.js.map +0 -1
  923. package/dist/services/types-shim.d.ts +0 -16
  924. package/dist/services/types-shim.js +0 -2
  925. package/dist/services/types-shim.js.map +0 -1
  926. package/dist/services/update-manager.d.ts +0 -47
  927. package/dist/services/update-manager.js +0 -351
  928. package/dist/services/update-manager.js.map +0 -1
  929. package/dist/services/webdav/server.d.ts +0 -24
  930. package/dist/services/webdav/server.js +0 -420
  931. package/dist/services/webdav/server.js.map +0 -1
  932. package/dist/services/webdav/xml-builder.js.map +0 -1
  933. package/dist/services/workspace-builder.d.ts +0 -29
  934. package/dist/services/workspace-builder.js +0 -188
  935. package/dist/services/workspace-builder.js.map +0 -1
  936. package/node_modules/@fastify/static/.github/stale.yml +0 -21
  937. package/node_modules/@isaacs/cliui/LICENSE.md +0 -63
  938. package/node_modules/@isaacs/cliui/README.md +0 -151
  939. package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.d.ts +0 -4
  940. package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.d.ts.map +0 -1
  941. package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.js +0 -16
  942. package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.js.map +0 -1
  943. package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.d.ts +0 -34
  944. package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.d.ts.map +0 -1
  945. package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.js +0 -170
  946. package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.js.map +0 -1
  947. package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.d.ts +0 -6
  948. package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.d.ts.map +0 -1
  949. package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.js +0 -307
  950. package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.js.map +0 -1
  951. package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.d.ts +0 -2
  952. package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.d.ts.map +0 -1
  953. package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.js +0 -7
  954. package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.js.map +0 -1
  955. package/node_modules/@isaacs/cliui/dist/commonjs/index.d.ts +0 -41
  956. package/node_modules/@isaacs/cliui/dist/commonjs/index.d.ts.map +0 -1
  957. package/node_modules/@isaacs/cliui/dist/commonjs/index.js +0 -322
  958. package/node_modules/@isaacs/cliui/dist/commonjs/index.js.map +0 -1
  959. package/node_modules/@isaacs/cliui/dist/commonjs/index.min.js +0 -12
  960. package/node_modules/@isaacs/cliui/dist/commonjs/index.min.js.map +0 -7
  961. package/node_modules/@isaacs/cliui/dist/commonjs/package.json +0 -3
  962. package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.d.ts +0 -5
  963. package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.d.ts.map +0 -1
  964. package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.js +0 -49
  965. package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.js.map +0 -1
  966. package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.d.ts +0 -2
  967. package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.d.ts.map +0 -1
  968. package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.js +0 -8
  969. package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.js.map +0 -1
  970. package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.d.ts +0 -7
  971. package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.d.ts.map +0 -1
  972. package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.js +0 -176
  973. package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.js.map +0 -1
  974. package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.d.ts +0 -4
  975. package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.d.ts.map +0 -1
  976. package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.js +0 -12
  977. package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.js.map +0 -1
  978. package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.d.ts +0 -34
  979. package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.d.ts.map +0 -1
  980. package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.js +0 -167
  981. package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.js.map +0 -1
  982. package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.d.ts +0 -6
  983. package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.d.ts.map +0 -1
  984. package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.js +0 -299
  985. package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.js.map +0 -1
  986. package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.d.ts +0 -2
  987. package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.d.ts.map +0 -1
  988. package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.js +0 -3
  989. package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.js.map +0 -1
  990. package/node_modules/@isaacs/cliui/dist/esm/index.d.ts +0 -41
  991. package/node_modules/@isaacs/cliui/dist/esm/index.d.ts.map +0 -1
  992. package/node_modules/@isaacs/cliui/dist/esm/index.js +0 -317
  993. package/node_modules/@isaacs/cliui/dist/esm/index.js.map +0 -1
  994. package/node_modules/@isaacs/cliui/dist/esm/index.min.js +0 -12
  995. package/node_modules/@isaacs/cliui/dist/esm/index.min.js.map +0 -7
  996. package/node_modules/@isaacs/cliui/dist/esm/package.json +0 -3
  997. package/node_modules/@isaacs/cliui/dist/esm/string-width/index.d.ts +0 -5
  998. package/node_modules/@isaacs/cliui/dist/esm/string-width/index.d.ts.map +0 -1
  999. package/node_modules/@isaacs/cliui/dist/esm/string-width/index.js +0 -46
  1000. package/node_modules/@isaacs/cliui/dist/esm/string-width/index.js.map +0 -1
  1001. package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.d.ts +0 -2
  1002. package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.d.ts.map +0 -1
  1003. package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.js +0 -4
  1004. package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.js.map +0 -1
  1005. package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.d.ts +0 -7
  1006. package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.d.ts.map +0 -1
  1007. package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.js +0 -172
  1008. package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.js.map +0 -1
  1009. package/node_modules/@isaacs/cliui/package.json +0 -163
  1010. package/node_modules/content-disposition/HISTORY.md +0 -60
  1011. package/node_modules/cross-spawn/LICENSE +0 -21
  1012. package/node_modules/cross-spawn/README.md +0 -89
  1013. package/node_modules/cross-spawn/index.js +0 -39
  1014. package/node_modules/cross-spawn/lib/enoent.js +0 -59
  1015. package/node_modules/cross-spawn/lib/parse.js +0 -91
  1016. package/node_modules/cross-spawn/lib/util/escape.js +0 -47
  1017. package/node_modules/cross-spawn/lib/util/readShebang.js +0 -23
  1018. package/node_modules/cross-spawn/lib/util/resolveCommand.js +0 -52
  1019. package/node_modules/cross-spawn/package.json +0 -73
  1020. package/node_modules/fastify/test/types/content-type-parser.test-d.ts +0 -72
  1021. package/node_modules/fastify/test/types/decorate-request-reply.test-d.ts +0 -18
  1022. package/node_modules/fastify/test/types/dummy-plugin.ts +0 -9
  1023. package/node_modules/fastify/test/types/errors.test-d.ts +0 -90
  1024. package/node_modules/fastify/test/types/fastify.test-d.ts +0 -352
  1025. package/node_modules/fastify/test/types/hooks.test-d.ts +0 -550
  1026. package/node_modules/fastify/test/types/import.ts +0 -2
  1027. package/node_modules/fastify/test/types/instance.test-d.ts +0 -588
  1028. package/node_modules/fastify/test/types/logger.test-d.ts +0 -277
  1029. package/node_modules/fastify/test/types/plugin.test-d.ts +0 -97
  1030. package/node_modules/fastify/test/types/register.test-d.ts +0 -237
  1031. package/node_modules/fastify/test/types/reply.test-d.ts +0 -254
  1032. package/node_modules/fastify/test/types/request.test-d.ts +0 -188
  1033. package/node_modules/fastify/test/types/route.test-d.ts +0 -553
  1034. package/node_modules/fastify/test/types/schema.test-d.ts +0 -135
  1035. package/node_modules/fastify/test/types/serverFactory.test-d.ts +0 -37
  1036. package/node_modules/fastify/test/types/type-provider.test-d.ts +0 -1213
  1037. package/node_modules/fastify/test/types/using.test-d.ts +0 -17
  1038. package/node_modules/foreground-child/LICENSE +0 -15
  1039. package/node_modules/foreground-child/README.md +0 -128
  1040. package/node_modules/foreground-child/dist/commonjs/all-signals.d.ts +0 -2
  1041. package/node_modules/foreground-child/dist/commonjs/all-signals.d.ts.map +0 -1
  1042. package/node_modules/foreground-child/dist/commonjs/all-signals.js +0 -58
  1043. package/node_modules/foreground-child/dist/commonjs/all-signals.js.map +0 -1
  1044. package/node_modules/foreground-child/dist/commonjs/index.d.ts +0 -58
  1045. package/node_modules/foreground-child/dist/commonjs/index.d.ts.map +0 -1
  1046. package/node_modules/foreground-child/dist/commonjs/index.js +0 -123
  1047. package/node_modules/foreground-child/dist/commonjs/index.js.map +0 -1
  1048. package/node_modules/foreground-child/dist/commonjs/package.json +0 -3
  1049. package/node_modules/foreground-child/dist/commonjs/proxy-signals.d.ts +0 -6
  1050. package/node_modules/foreground-child/dist/commonjs/proxy-signals.d.ts.map +0 -1
  1051. package/node_modules/foreground-child/dist/commonjs/proxy-signals.js +0 -38
  1052. package/node_modules/foreground-child/dist/commonjs/proxy-signals.js.map +0 -1
  1053. package/node_modules/foreground-child/dist/commonjs/watchdog.d.ts +0 -10
  1054. package/node_modules/foreground-child/dist/commonjs/watchdog.d.ts.map +0 -1
  1055. package/node_modules/foreground-child/dist/commonjs/watchdog.js +0 -50
  1056. package/node_modules/foreground-child/dist/commonjs/watchdog.js.map +0 -1
  1057. package/node_modules/foreground-child/dist/esm/all-signals.d.ts +0 -2
  1058. package/node_modules/foreground-child/dist/esm/all-signals.d.ts.map +0 -1
  1059. package/node_modules/foreground-child/dist/esm/all-signals.js +0 -52
  1060. package/node_modules/foreground-child/dist/esm/all-signals.js.map +0 -1
  1061. package/node_modules/foreground-child/dist/esm/index.d.ts +0 -58
  1062. package/node_modules/foreground-child/dist/esm/index.d.ts.map +0 -1
  1063. package/node_modules/foreground-child/dist/esm/index.js +0 -115
  1064. package/node_modules/foreground-child/dist/esm/index.js.map +0 -1
  1065. package/node_modules/foreground-child/dist/esm/package.json +0 -3
  1066. package/node_modules/foreground-child/dist/esm/proxy-signals.d.ts +0 -6
  1067. package/node_modules/foreground-child/dist/esm/proxy-signals.d.ts.map +0 -1
  1068. package/node_modules/foreground-child/dist/esm/proxy-signals.js +0 -34
  1069. package/node_modules/foreground-child/dist/esm/proxy-signals.js.map +0 -1
  1070. package/node_modules/foreground-child/dist/esm/watchdog.d.ts +0 -10
  1071. package/node_modules/foreground-child/dist/esm/watchdog.d.ts.map +0 -1
  1072. package/node_modules/foreground-child/dist/esm/watchdog.js +0 -46
  1073. package/node_modules/foreground-child/dist/esm/watchdog.js.map +0 -1
  1074. package/node_modules/foreground-child/package.json +0 -106
  1075. package/node_modules/glob/dist/esm/bin.d.mts +0 -3
  1076. package/node_modules/glob/dist/esm/bin.d.mts.map +0 -1
  1077. package/node_modules/glob/dist/esm/bin.mjs +0 -346
  1078. package/node_modules/glob/dist/esm/bin.mjs.map +0 -1
  1079. package/node_modules/isexe/.npmignore +0 -2
  1080. package/node_modules/isexe/LICENSE +0 -15
  1081. package/node_modules/isexe/README.md +0 -51
  1082. package/node_modules/isexe/index.js +0 -57
  1083. package/node_modules/isexe/mode.js +0 -41
  1084. package/node_modules/isexe/package.json +0 -31
  1085. package/node_modules/isexe/test/basic.js +0 -221
  1086. package/node_modules/isexe/windows.js +0 -42
  1087. package/node_modules/jackspeak/LICENSE.md +0 -55
  1088. package/node_modules/jackspeak/README.md +0 -394
  1089. package/node_modules/jackspeak/dist/commonjs/index.d.ts +0 -323
  1090. package/node_modules/jackspeak/dist/commonjs/index.d.ts.map +0 -1
  1091. package/node_modules/jackspeak/dist/commonjs/index.js +0 -944
  1092. package/node_modules/jackspeak/dist/commonjs/index.js.map +0 -1
  1093. package/node_modules/jackspeak/dist/commonjs/index.min.js +0 -33
  1094. package/node_modules/jackspeak/dist/commonjs/index.min.js.map +0 -7
  1095. package/node_modules/jackspeak/dist/commonjs/package.json +0 -3
  1096. package/node_modules/jackspeak/dist/esm/index.d.ts +0 -323
  1097. package/node_modules/jackspeak/dist/esm/index.d.ts.map +0 -1
  1098. package/node_modules/jackspeak/dist/esm/index.js +0 -936
  1099. package/node_modules/jackspeak/dist/esm/index.js.map +0 -1
  1100. package/node_modules/jackspeak/dist/esm/index.min.js +0 -33
  1101. package/node_modules/jackspeak/dist/esm/index.min.js.map +0 -7
  1102. package/node_modules/jackspeak/dist/esm/package.json +0 -3
  1103. package/node_modules/jackspeak/package.json +0 -115
  1104. package/node_modules/jishushell-panel/output/public/assets/ApiKeyField-D1i7zWXR.js +0 -1
  1105. package/node_modules/jishushell-panel/output/public/assets/Dashboard-sWIvL43F.js +0 -1
  1106. package/node_modules/jishushell-panel/output/public/assets/HermesChatPanel-DQ8RyvQY.js +0 -1
  1107. package/node_modules/jishushell-panel/output/public/assets/HermesConfigForm-tIbPP1sB.js +0 -4
  1108. package/node_modules/jishushell-panel/output/public/assets/InitPassword-C3Slq3Dd.js +0 -1
  1109. package/node_modules/jishushell-panel/output/public/assets/InstanceDetail-7JqY9tq4.js +0 -92
  1110. package/node_modules/jishushell-panel/output/public/assets/Login-BXLDJlQN.js +0 -1
  1111. package/node_modules/jishushell-panel/output/public/assets/NewInstance-dLc5Xrpx.js +0 -1
  1112. package/node_modules/jishushell-panel/output/public/assets/ProviderRecommendations-DIAXxesl.js +0 -1
  1113. package/node_modules/jishushell-panel/output/public/assets/Settings-Bd5utbBh.js +0 -1
  1114. package/node_modules/jishushell-panel/output/public/assets/Setup-Yn9_20FL.js +0 -1
  1115. package/node_modules/jishushell-panel/output/public/assets/WeixinLoginPanel-C21doQTJ.js +0 -9
  1116. package/node_modules/jishushell-panel/output/public/assets/index-CCkaIEjn.js +0 -20
  1117. package/node_modules/jishushell-panel/output/public/assets/index-D7qxy-Vh.css +0 -1
  1118. package/node_modules/jishushell-panel/output/public/assets/registry-B2ZQZXWL.js +0 -2
  1119. package/node_modules/jishushell-panel/output/public/assets/usePolling-BFZm4do_.js +0 -1
  1120. package/node_modules/jishushell-panel/output/public/assets/vendor-i18n-DqPtOicc.js +0 -9
  1121. package/node_modules/jishushell-panel/output/public/assets/vendor-react-DW5juQin.js +0 -59
  1122. package/node_modules/package-json-from-dist/LICENSE.md +0 -63
  1123. package/node_modules/package-json-from-dist/README.md +0 -110
  1124. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts +0 -89
  1125. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts.map +0 -1
  1126. package/node_modules/package-json-from-dist/dist/commonjs/index.js +0 -134
  1127. package/node_modules/package-json-from-dist/dist/commonjs/index.js.map +0 -1
  1128. package/node_modules/package-json-from-dist/dist/commonjs/package.json +0 -3
  1129. package/node_modules/package-json-from-dist/dist/esm/index.d.ts +0 -89
  1130. package/node_modules/package-json-from-dist/dist/esm/index.d.ts.map +0 -1
  1131. package/node_modules/package-json-from-dist/dist/esm/index.js +0 -129
  1132. package/node_modules/package-json-from-dist/dist/esm/index.js.map +0 -1
  1133. package/node_modules/package-json-from-dist/dist/esm/package.json +0 -3
  1134. package/node_modules/package-json-from-dist/package.json +0 -68
  1135. package/node_modules/path-key/index.d.ts +0 -40
  1136. package/node_modules/path-key/index.js +0 -16
  1137. package/node_modules/path-key/license +0 -9
  1138. package/node_modules/path-key/package.json +0 -39
  1139. package/node_modules/path-key/readme.md +0 -61
  1140. package/node_modules/safe-buffer/LICENSE +0 -21
  1141. package/node_modules/safe-buffer/README.md +0 -584
  1142. package/node_modules/safe-buffer/index.d.ts +0 -187
  1143. package/node_modules/safe-buffer/index.js +0 -65
  1144. package/node_modules/safe-buffer/package.json +0 -51
  1145. package/node_modules/shebang-command/index.js +0 -19
  1146. package/node_modules/shebang-command/license +0 -9
  1147. package/node_modules/shebang-command/package.json +0 -34
  1148. package/node_modules/shebang-command/readme.md +0 -34
  1149. package/node_modules/shebang-regex/index.d.ts +0 -22
  1150. package/node_modules/shebang-regex/index.js +0 -2
  1151. package/node_modules/shebang-regex/license +0 -9
  1152. package/node_modules/shebang-regex/package.json +0 -35
  1153. package/node_modules/shebang-regex/readme.md +0 -33
  1154. package/node_modules/signal-exit/LICENSE.txt +0 -16
  1155. package/node_modules/signal-exit/README.md +0 -74
  1156. package/node_modules/signal-exit/dist/cjs/browser.d.ts +0 -12
  1157. package/node_modules/signal-exit/dist/cjs/browser.d.ts.map +0 -1
  1158. package/node_modules/signal-exit/dist/cjs/browser.js +0 -10
  1159. package/node_modules/signal-exit/dist/cjs/browser.js.map +0 -1
  1160. package/node_modules/signal-exit/dist/cjs/index.d.ts +0 -48
  1161. package/node_modules/signal-exit/dist/cjs/index.d.ts.map +0 -1
  1162. package/node_modules/signal-exit/dist/cjs/index.js +0 -279
  1163. package/node_modules/signal-exit/dist/cjs/index.js.map +0 -1
  1164. package/node_modules/signal-exit/dist/cjs/package.json +0 -3
  1165. package/node_modules/signal-exit/dist/cjs/signals.d.ts +0 -29
  1166. package/node_modules/signal-exit/dist/cjs/signals.d.ts.map +0 -1
  1167. package/node_modules/signal-exit/dist/cjs/signals.js +0 -42
  1168. package/node_modules/signal-exit/dist/cjs/signals.js.map +0 -1
  1169. package/node_modules/signal-exit/dist/mjs/browser.d.ts +0 -12
  1170. package/node_modules/signal-exit/dist/mjs/browser.d.ts.map +0 -1
  1171. package/node_modules/signal-exit/dist/mjs/browser.js +0 -4
  1172. package/node_modules/signal-exit/dist/mjs/browser.js.map +0 -1
  1173. package/node_modules/signal-exit/dist/mjs/index.d.ts +0 -48
  1174. package/node_modules/signal-exit/dist/mjs/index.d.ts.map +0 -1
  1175. package/node_modules/signal-exit/dist/mjs/index.js +0 -275
  1176. package/node_modules/signal-exit/dist/mjs/index.js.map +0 -1
  1177. package/node_modules/signal-exit/dist/mjs/package.json +0 -3
  1178. package/node_modules/signal-exit/dist/mjs/signals.d.ts +0 -29
  1179. package/node_modules/signal-exit/dist/mjs/signals.d.ts.map +0 -1
  1180. package/node_modules/signal-exit/dist/mjs/signals.js +0 -39
  1181. package/node_modules/signal-exit/dist/mjs/signals.js.map +0 -1
  1182. package/node_modules/signal-exit/package.json +0 -106
  1183. package/node_modules/which/CHANGELOG.md +0 -166
  1184. package/node_modules/which/LICENSE +0 -15
  1185. package/node_modules/which/README.md +0 -54
  1186. package/node_modules/which/bin/node-which +0 -52
  1187. package/node_modules/which/package.json +0 -43
  1188. package/node_modules/which/which.js +0 -125
  1189. package/scripts/check-adapter-isolation.ts +0 -293
  1190. /package/dist/services/{app → app-common}/app-compiler.d.ts +0 -0
  1191. /package/dist/services/{app → app-common}/terminal-session-manager.d.ts +0 -0
  1192. /package/dist/services/{backup-verify.d.ts → backup/backup-verify.d.ts} +0 -0
  1193. /package/dist/services/{external-mounts.d.ts → files/external-mounts.d.ts} +0 -0
  1194. /package/dist/services/{organize → files/organize}/applier.d.ts +0 -0
  1195. /package/dist/services/{organize → files/organize}/rules.d.ts +0 -0
  1196. /package/dist/services/{organize → files/organize}/scanner.d.ts +0 -0
  1197. /package/dist/services/{organize → files/organize}/store.d.ts +0 -0
  1198. /package/dist/services/{webdav → files/webdav}/xml-builder.d.ts +0 -0
  1199. /package/dist/services/{webdav → files/webdav}/xml-builder.js +0 -0
  1200. /package/dist/services/{app-passwords.d.ts → instances/passwords.d.ts} +0 -0
  1201. /package/dist/services/{agent-apps → integrations/installable}/installers/registry-probe.d.ts +0 -0
  1202. /package/dist/services/{agent-apps → integrations/installable}/installers/registry-probe.js +0 -0
  1203. /package/dist/services/{runtime/mcp-shims → integrations/openclaw}/drive-shim.d.ts +0 -0
  1204. /package/dist/services/{runtime/mcp-shims → integrations/openclaw}/mcporter-lite.d.ts +0 -0
  1205. /package/dist/services/{plugin-installer.d.ts → setup/plugin-installer.d.ts} +0 -0
  1206. /package/dist/services/{macos-launchd.d.ts → system/macos-launchd.d.ts} +0 -0
  1207. /package/dist/services/{system-monitor.d.ts → system/system-monitor.d.ts} +0 -0
@@ -0,0 +1,2318 @@
1
+ import { execFileSync, spawn as spawnChild } from "child_process";
2
+ import { createHash, randomBytes, randomUUID } from "crypto";
3
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "fs";
4
+ import { dirname, join, relative } from "path";
5
+ import { posix as pathPosix } from "path";
6
+ import { BACKUPS_DIR, TMP_DIR } from "../../config.js";
7
+ /**
8
+ * Encode an absolute filesystem path for use inside backup archives.
9
+ * Ported from OpenClaw official: encodeAbsolutePathForBackupArchive.
10
+ */
11
+ export function encodeAbsolutePathForArchive(sourcePath) {
12
+ const normalized = sourcePath.replaceAll("\\", "/");
13
+ const windowsMatch = normalized.match(/^([A-Za-z]):\/(.*)$/);
14
+ if (windowsMatch) {
15
+ const drive = (windowsMatch[1] ?? "UNKNOWN").toUpperCase();
16
+ const rest = windowsMatch[2] ?? "";
17
+ return pathPosix.join("windows", drive, rest);
18
+ }
19
+ if (normalized.startsWith("/"))
20
+ return pathPosix.join("posix", normalized.slice(1));
21
+ return pathPosix.join("relative", normalized);
22
+ }
23
+ /**
24
+ * Decode a path produced by {@link encodeAbsolutePathForArchive} back to
25
+ * the original absolute filesystem path.
26
+ *
27
+ * - `"posix/Users/arm/foo"` → `"/Users/arm/foo"`
28
+ * - `"windows/C/Users/foo"` → `"C:\\Users\\foo"`
29
+ * - `"relative/some/path"` → `"some/path"`
30
+ *
31
+ * Returns the input unchanged when the prefix is unrecognised.
32
+ */
33
+ export function decodeArchivePath(encodedPath) {
34
+ const normalized = encodedPath.replaceAll("\\", "/");
35
+ if (normalized.startsWith("posix/")) {
36
+ return "/" + normalized.slice("posix/".length);
37
+ }
38
+ if (normalized.startsWith("windows/")) {
39
+ const rest = normalized.slice("windows/".length);
40
+ const slashIdx = rest.indexOf("/");
41
+ const drive = slashIdx >= 0 ? rest.slice(0, slashIdx) : rest;
42
+ // Drive letter must be a single alphabetic character
43
+ if (!/^[A-Za-z]$/.test(drive))
44
+ return encodedPath;
45
+ const tail = slashIdx >= 0 ? rest.slice(slashIdx + 1) : "";
46
+ return `${drive}:\\${tail.replaceAll("/", "\\")}`;
47
+ }
48
+ if (normalized.startsWith("relative/")) {
49
+ return normalized.slice("relative/".length);
50
+ }
51
+ return encodedPath;
52
+ }
53
+ /**
54
+ * Validate that a decoded `oldHome` path is safe to use with `replaceAll`.
55
+ *
56
+ * Guards against catastrophic replacements when the inferred path is empty,
57
+ * a filesystem root, or relative (which would match too broadly).
58
+ */
59
+ function isValidOldHome(path) {
60
+ if (!path || path === "." || path === "..")
61
+ return false;
62
+ // POSIX: must start with / and not be root itself
63
+ if (path.startsWith("/"))
64
+ return path.length > 1;
65
+ // Windows: must be drive:\something (at least one segment after drive)
66
+ const winMatch = path.match(/^[A-Za-z]:[/\\](.+)$/);
67
+ if (winMatch)
68
+ return true;
69
+ return false;
70
+ }
71
+ const MAX_REWRITE_FILE_SIZE = 100 * 1024 * 1024; // 100 MB
72
+ function integrationHomeFromMetadata(meta, instanceId) {
73
+ const integrationHome = meta?.paths?.integrationHome;
74
+ if (typeof integrationHome === "string" && integrationHome.trim()) {
75
+ return integrationHome;
76
+ }
77
+ throw Object.assign(new Error(`Instance '${instanceId}' has no canonical paths.integrationHome; run upgrade repair before backup restore`), { statusCode: 409, code: "needs-migration" });
78
+ }
79
+ /** Core facts/derived layouts must never be restored from another instance. */
80
+ function isNonPortableHomeEntry(rel) {
81
+ return rel === ".jishushell-owned"
82
+ || rel === "workspace"
83
+ || rel.startsWith("workspace/")
84
+ || rel === "openclaw.json";
85
+ }
86
+ /**
87
+ * Rewrite absolute paths in session data and symlinks after copying from a
88
+ * backup archive. Best-effort per file: individual failures produce warnings
89
+ * but do not block the import or affect other files.
90
+ *
91
+ * Walks all `.json` and `.jsonl` files under {@link targetDir} recursively
92
+ * and replaces occurrences of {@link oldHome} with {@link newHome}. Also
93
+ * rewrites symlink targets that contain {@link oldHome}.
94
+ */
95
+ function rewriteInstancePaths(targetDir, oldHome, newHome, warnings) {
96
+ if (oldHome === newHome)
97
+ return;
98
+ if (!isValidOldHome(oldHome)) {
99
+ warnings.push(`Path rewrite skipped: inferred source path "${oldHome}" is invalid or too broad`);
100
+ return;
101
+ }
102
+ const walkDir = (dir) => {
103
+ if (!existsSync(dir))
104
+ return;
105
+ let entries;
106
+ try {
107
+ entries = readdirSync(dir);
108
+ }
109
+ catch {
110
+ return;
111
+ }
112
+ for (const entry of entries) {
113
+ const fullPath = join(dir, entry);
114
+ let s;
115
+ try {
116
+ s = lstatSync(fullPath);
117
+ }
118
+ catch {
119
+ continue;
120
+ }
121
+ // Rewrite symlink targets
122
+ if (s.isSymbolicLink()) {
123
+ try {
124
+ const target = readlinkSync(fullPath);
125
+ if (target.includes(oldHome)) {
126
+ unlinkSync(fullPath);
127
+ symlinkSync(target.replaceAll(oldHome, newHome), fullPath);
128
+ }
129
+ }
130
+ catch (e) {
131
+ warnings.push(`Symlink rewrite failed for ${entry}: ${e.message}`);
132
+ }
133
+ continue;
134
+ }
135
+ if (s.isDirectory()) {
136
+ walkDir(fullPath);
137
+ continue;
138
+ }
139
+ // Only process .json and .jsonl regular files
140
+ if (!s.isFile())
141
+ continue;
142
+ if (!entry.endsWith(".json") && !entry.endsWith(".jsonl"))
143
+ continue;
144
+ if (s.size > MAX_REWRITE_FILE_SIZE) {
145
+ warnings.push(`Skipped path rewrite for large file: ${entry} (${s.size} bytes)`);
146
+ continue;
147
+ }
148
+ try {
149
+ const content = readFileSync(fullPath, "utf-8");
150
+ if (content.includes(oldHome)) {
151
+ writeFileSync(fullPath, content.replaceAll(oldHome, newHome));
152
+ }
153
+ }
154
+ catch (e) {
155
+ warnings.push(`Path rewrite failed for ${entry}: ${e.message}`);
156
+ }
157
+ }
158
+ };
159
+ walkDir(targetDir);
160
+ }
161
+ /**
162
+ * Detect the original `openclawHome` path that was used when the backup
163
+ * archive was created.
164
+ *
165
+ * Priority:
166
+ * 1. `manifest.source_home` (written by newer selfPackOfficialFormat)
167
+ * 2. Fallback: decode the encoded path from the payload/ directory structure
168
+ */
169
+ function detectOldHome(manifest, payloadDir, extractedStateDir) {
170
+ // Priority 1: manifest metadata
171
+ if (manifest?.source_home && typeof manifest.source_home === "string") {
172
+ return manifest.source_home;
173
+ }
174
+ // Priority 2: decode from payload/ path structure
175
+ try {
176
+ const extractedHomeDir = dirname(extractedStateDir);
177
+ const relativeToPayload = relative(payloadDir, extractedHomeDir);
178
+ // Reject if relative() produced a `..` traversal (unexpected layout)
179
+ if (relativeToPayload.startsWith(".."))
180
+ return null;
181
+ return decodeArchivePath(relativeToPayload);
182
+ }
183
+ catch {
184
+ return null;
185
+ }
186
+ }
187
+ async function installOpenclawRestoreTarget(instanceId, name, description = "") {
188
+ const { getInstance, listBuiltinAppSpecs } = await import("../app-common/service.js");
189
+ const { getIntegration } = await import("../integrations/index.js");
190
+ const { installInstance } = await import("../app-common/lifecycle-service.js");
191
+ const template = listBuiltinAppSpecs().find((entry) => entry.fileName === "openclaw-container.yaml" || entry.id === "openclaw-container");
192
+ if (!template) {
193
+ throw new Error("Builtin app spec 'openclaw-container.yaml' not found");
194
+ }
195
+ await installInstance(template.yaml, instanceId, {
196
+ bootstrap: {
197
+ name,
198
+ description,
199
+ },
200
+ });
201
+ const meta = getInstance(instanceId);
202
+ if (!meta) {
203
+ throw new Error(`Imported instance '${instanceId}' did not produce canonical instance metadata`);
204
+ }
205
+ const integrationHome = meta.paths?.integrationHome;
206
+ if (typeof integrationHome !== "string" || !integrationHome.trim()) {
207
+ throw new Error(`Imported instance '${instanceId}' has no canonical paths.integrationHome`);
208
+ }
209
+ const integration = getIntegration("openclaw");
210
+ if (typeof integration.resolveAgentHome !== "function") {
211
+ throw new Error("OpenClaw integration does not expose its canonical home path");
212
+ }
213
+ const openclawHome = integration.resolveAgentHome(instanceId);
214
+ if (openclawHome !== integrationHome) {
215
+ throw new Error(`Imported instance '${instanceId}' integration home is inconsistent`);
216
+ }
217
+ return {
218
+ openclawHome,
219
+ stateDir: join(openclawHome, ".openclaw"),
220
+ };
221
+ }
222
+ async function rollbackOpenclawRestoreTarget(instanceId) {
223
+ const { uninstallInstance } = await import("../app-common/lifecycle-service.js");
224
+ await uninstallInstance(instanceId);
225
+ }
226
+ /**
227
+ * Try to create a backup by calling the official `openclaw backup create` CLI.
228
+ * Returns ok:false if the binary is missing or the command fails.
229
+ */
230
+ export async function callOpenclawBackup(instanceId, outputDir, opts) {
231
+ // Resolve executable details through the integration so backup-manager
232
+ // does not reach into OpenClaw's private implementation. The backup CLI
233
+ // itself is OpenClaw-specific today, but bin/env discovery belongs to the
234
+ // integration that owns that CLI contract.
235
+ const { getIntegration } = await import("../integrations/index.js");
236
+ const { resolveExecutionOwner } = await import("../app-common/execution-owner.js");
237
+ const owner = resolveExecutionOwner(instanceId);
238
+ const kind = owner?.type === "integration" ? owner.integrationKind : undefined;
239
+ if (!kind) {
240
+ return { ok: false, error: `Instance '${instanceId}' does not expose a runtime integration backup CLI` };
241
+ }
242
+ const integration = getIntegration(kind);
243
+ if (typeof integration.resolveBin !== "function" || typeof integration.buildCliEnv !== "function") {
244
+ return { ok: false, error: `Runtime "${kind}" does not support backup CLI` };
245
+ }
246
+ const openclawBin = integration.resolveBin();
247
+ const cliEnv = integration.buildCliEnv(instanceId);
248
+ if (!existsSync(openclawBin)) {
249
+ return { ok: false, error: `agent binary not found: ${openclawBin}` };
250
+ }
251
+ if (!existsSync(outputDir))
252
+ mkdirSync(outputDir, { recursive: true });
253
+ const preExisting = new Set(readdirSync(outputDir).filter(f => f.endsWith(".tar.gz")));
254
+ const args = ["backup", "create", "--output", outputDir];
255
+ if (opts.onlyConfig)
256
+ args.push("--only-config");
257
+ if (opts.noWorkspace)
258
+ args.push("--no-include-workspace");
259
+ try {
260
+ await new Promise((resolve, reject) => {
261
+ const child = spawnChild(openclawBin, args, {
262
+ env: { ...process.env, ...cliEnv },
263
+ stdio: ["ignore", "pipe", "pipe"],
264
+ timeout: 120_000,
265
+ });
266
+ let stderr = "";
267
+ child.stderr?.on("data", (chunk) => { stderr += chunk.toString(); });
268
+ child.on("close", (code) => {
269
+ if (code !== 0)
270
+ reject(new Error(`openclaw backup create exited ${code}: ${stderr.slice(0, 500)}`));
271
+ else
272
+ resolve();
273
+ });
274
+ child.on("error", (err) => reject(err));
275
+ });
276
+ // Pick up only entries created by THIS CLI run. If the CLI happens to
277
+ // produce more than one archive (it currently produces exactly one),
278
+ // take the newest by mtime.
279
+ const newFiles = readdirSync(outputDir)
280
+ .filter(f => f.endsWith(".tar.gz") && !preExisting.has(f))
281
+ .map(f => ({ name: f, mtime: statSync(join(outputDir, f)).mtimeMs }))
282
+ .sort((a, b) => b.mtime - a.mtime);
283
+ if (newFiles.length === 0) {
284
+ return { ok: false, error: "openclaw backup create produced no new .tar.gz file" };
285
+ }
286
+ return { ok: true, archivePath: join(outputDir, newFiles[0].name) };
287
+ }
288
+ catch (e) {
289
+ return { ok: false, error: e.message };
290
+ }
291
+ }
292
+ // ── Directory initialization ──
293
+ /** Ensure backup and tmp directories exist */
294
+ export function ensureBackupDirs() {
295
+ for (const dir of [BACKUPS_DIR, TMP_DIR]) {
296
+ if (!existsSync(dir))
297
+ mkdirSync(dir, { recursive: true, mode: 0o755 });
298
+ }
299
+ }
300
+ // ── Tmp file management ──
301
+ const TMP_MAX_AGE_MS = 30 * 60 * 1000; // 30 minutes
302
+ const EXPORTS_SUBDIR = "exports";
303
+ /**
304
+ * Get the per-instance export output directory (TMP_DIR/exports/<id>/).
305
+ * Exports live in an isolated subdirectory so the download route can enforce
306
+ * `:id` → file ownership and one instance's URL can't reach another's archive.
307
+ */
308
+ export function getInstanceExportDir(instanceId) {
309
+ const dir = join(TMP_DIR, EXPORTS_SUBDIR, instanceId);
310
+ if (!existsSync(dir))
311
+ mkdirSync(dir, { recursive: true, mode: 0o755 });
312
+ return dir;
313
+ }
314
+ /** Clean up stale tmp files older than 30 minutes. Call on startup and periodically. */
315
+ export function cleanupStaleTmpFiles() {
316
+ if (!existsSync(TMP_DIR))
317
+ return 0;
318
+ let cleaned = 0;
319
+ const now = Date.now();
320
+ for (const entry of readdirSync(TMP_DIR)) {
321
+ const fullPath = join(TMP_DIR, entry);
322
+ // Exports live in per-instance subdirs; sweep them one file at a time so
323
+ // we don't wipe an active export collection because of a stale sibling.
324
+ if (entry === EXPORTS_SUBDIR) {
325
+ try {
326
+ for (const instanceEntry of readdirSync(fullPath)) {
327
+ const instanceDir = join(fullPath, instanceEntry);
328
+ try {
329
+ if (!statSync(instanceDir).isDirectory())
330
+ continue;
331
+ for (const fileEntry of readdirSync(instanceDir)) {
332
+ const filePath = join(instanceDir, fileEntry);
333
+ try {
334
+ const stat = statSync(filePath);
335
+ if (now - stat.mtimeMs > TMP_MAX_AGE_MS) {
336
+ rmSync(filePath, { recursive: true, force: true });
337
+ cleaned++;
338
+ console.log(`[backup] Cleaned stale export: ${instanceEntry}/${fileEntry}`);
339
+ }
340
+ }
341
+ catch { /* skip */ }
342
+ }
343
+ }
344
+ catch { /* skip */ }
345
+ }
346
+ }
347
+ catch { /* skip */ }
348
+ continue;
349
+ }
350
+ try {
351
+ const stat = statSync(fullPath);
352
+ if (now - stat.mtimeMs > TMP_MAX_AGE_MS) {
353
+ rmSync(fullPath, { recursive: true, force: true });
354
+ cleaned++;
355
+ console.log(`[backup] Cleaned stale tmp: ${entry}`);
356
+ }
357
+ }
358
+ catch {
359
+ // Skip entries that disappeared during iteration
360
+ }
361
+ }
362
+ return cleaned;
363
+ }
364
+ // ── Tmp cleanup scheduler ──
365
+ let cleanupTimer = null;
366
+ const CLEANUP_INTERVAL_MS = 10 * 60 * 1000; // Check every 10 minutes
367
+ /** Start periodic tmp cleanup. Call once at server startup. */
368
+ export function startTmpCleanupScheduler() {
369
+ // Run immediately on startup
370
+ ensureBackupDirs();
371
+ cleanupStaleTmpFiles();
372
+ // Schedule periodic cleanup
373
+ if (cleanupTimer)
374
+ clearInterval(cleanupTimer);
375
+ cleanupTimer = setInterval(() => {
376
+ cleanupStaleTmpFiles();
377
+ }, CLEANUP_INTERVAL_MS);
378
+ // Don't prevent process exit
379
+ if (cleanupTimer.unref)
380
+ cleanupTimer.unref();
381
+ }
382
+ /** Stop the cleanup scheduler (for graceful shutdown). */
383
+ export function stopTmpCleanupScheduler() {
384
+ if (cleanupTimer) {
385
+ clearInterval(cleanupTimer);
386
+ cleanupTimer = null;
387
+ }
388
+ }
389
+ // ── Backup directory helpers ──
390
+ /** Get the backup directory for an instance (creates if needed) */
391
+ export function getInstanceBackupDir(instanceId) {
392
+ const dir = join(BACKUPS_DIR, instanceId);
393
+ if (!existsSync(dir))
394
+ mkdirSync(dir, { recursive: true, mode: 0o755 });
395
+ return dir;
396
+ }
397
+ /** List all backup files for an instance, sorted by mtime descending (newest first) */
398
+ export function listInstanceBackups(instanceId) {
399
+ const dir = join(BACKUPS_DIR, instanceId);
400
+ if (!existsSync(dir))
401
+ return [];
402
+ return readdirSync(dir)
403
+ .filter(f => f.endsWith(".tar.gz"))
404
+ .map(filename => {
405
+ const stat = statSync(join(dir, filename));
406
+ let type = "manual-backup";
407
+ if (filename.startsWith("auto-backup"))
408
+ type = "auto-backup";
409
+ else if (filename.startsWith("pre-restore"))
410
+ type = "pre-restore";
411
+ return {
412
+ filename,
413
+ size: stat.size,
414
+ created_at: stat.mtime.toISOString(),
415
+ type,
416
+ };
417
+ })
418
+ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
419
+ }
420
+ /** List all instance IDs that have backups (including orphans) */
421
+ export function listAllBackupInstanceIds() {
422
+ if (!existsSync(BACKUPS_DIR))
423
+ return [];
424
+ return readdirSync(BACKUPS_DIR).filter(entry => {
425
+ const dir = join(BACKUPS_DIR, entry);
426
+ try {
427
+ return statSync(dir).isDirectory();
428
+ }
429
+ catch {
430
+ return false;
431
+ }
432
+ });
433
+ }
434
+ const instanceLocks = new Map();
435
+ const HEARTBEAT_STALE_MS = 2 * 60 * 1000; // 2 minutes without heartbeat = stale
436
+ /** Acquire an exclusive lock for an instance. Returns false if already locked. */
437
+ export function acquireInstanceLock(instanceId, operation) {
438
+ const existing = instanceLocks.get(instanceId);
439
+ if (existing) {
440
+ if (Date.now() - existing.lastHeartbeat > HEARTBEAT_STALE_MS) {
441
+ console.warn(`[backup] Force-releasing stale lock for ${instanceId} (op: ${existing.operation}, age: ${Math.round((Date.now() - existing.since) / 1000)}s)`);
442
+ instanceLocks.delete(instanceId);
443
+ }
444
+ else {
445
+ return false;
446
+ }
447
+ }
448
+ instanceLocks.set(instanceId, { operation, since: Date.now(), lastHeartbeat: Date.now() });
449
+ return true;
450
+ }
451
+ /** Update heartbeat for a held lock. Call periodically during long operations. */
452
+ export function touchInstanceLock(instanceId) {
453
+ const lock = instanceLocks.get(instanceId);
454
+ if (lock)
455
+ lock.lastHeartbeat = Date.now();
456
+ }
457
+ /** Release the lock for an instance. */
458
+ export function releaseInstanceLock(instanceId) {
459
+ instanceLocks.delete(instanceId);
460
+ }
461
+ /** Get current lock status for an instance (for status API). */
462
+ export function getInstanceLockStatus(instanceId) {
463
+ const lock = instanceLocks.get(instanceId);
464
+ if (!lock)
465
+ return null;
466
+ if (Date.now() - lock.lastHeartbeat > HEARTBEAT_STALE_MS) {
467
+ instanceLocks.delete(instanceId);
468
+ return null;
469
+ }
470
+ return { locked: true, operation: lock.operation };
471
+ }
472
+ /** Start a heartbeat interval for long operations. Returns a cleanup function. */
473
+ export function startLockHeartbeat(instanceId, intervalMs = 15_000) {
474
+ const timer = setInterval(() => touchInstanceLock(instanceId), intervalMs);
475
+ return () => clearInterval(timer);
476
+ }
477
+ /** Check if instance is locked. Throws with 409 status if locked. */
478
+ export function assertNotLocked(instanceId) {
479
+ const lock = getInstanceLockStatus(instanceId);
480
+ if (lock?.locked) {
481
+ const err = new Error(`Instance ${instanceId} is locked: ${lock.operation}`);
482
+ err.statusCode = 409;
483
+ throw err;
484
+ }
485
+ }
486
+ // ── Tar variant detection ──
487
+ let _tarVariant = null;
488
+ /** Detect whether system tar is GNU or BSD. Cached after first call. */
489
+ export function detectTarVariant() {
490
+ if (_tarVariant)
491
+ return _tarVariant;
492
+ try {
493
+ const output = execFileSync("tar", ["--version"], { encoding: "utf-8", timeout: 5000 });
494
+ _tarVariant = output.includes("GNU tar") ? "gnu" : "bsd";
495
+ }
496
+ catch {
497
+ // BSD tar may not support --version, or tar may not exist
498
+ try {
499
+ execFileSync("tar", ["--help"], { encoding: "utf-8", timeout: 5000 });
500
+ _tarVariant = "bsd";
501
+ }
502
+ catch {
503
+ throw new Error("tar command not found. Please install tar.");
504
+ }
505
+ }
506
+ console.log(`[backup] Detected tar variant: ${_tarVariant}`);
507
+ return _tarVariant;
508
+ }
509
+ // ── Secure extraction ──
510
+ const MAX_UNCOMPRESSED_SIZE = 2 * 1024 * 1024 * 1024; // 2GB
511
+ /** Parse tar -tvzf output into structured entries */
512
+ function parseTarVerboseOutput(output) {
513
+ const entries = [];
514
+ for (const line of output.split("\n")) {
515
+ if (!line.trim())
516
+ continue;
517
+ // Format: -rw-r--r-- user/group 12345 2026-04-08 10:00 path/to/file
518
+ // Or: lrwxrwxrwx user/group 0 2026-04-08 10:00 link -> target
519
+ // Or: drwxr-xr-x user/group 0 2026-04-08 10:00 dir/
520
+ const perms = line.charAt(0);
521
+ let type = "-";
522
+ if (perms === "l")
523
+ type = "l"; // symlink
524
+ else if (perms === "h")
525
+ type = "h"; // hardlink
526
+ else if (perms === "d")
527
+ type = "d"; // directory
528
+ // Parse size (field after user/group)
529
+ const parts = line.split(/\s+/);
530
+ // parts: [perms, user/group, size, date, time, path...]
531
+ const size = parseInt(parts[2], 10) || 0;
532
+ // Path is everything after the time field
533
+ // Find the path by looking for the portion after date+time
534
+ const pathMatch = line.match(/\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+(.+)/);
535
+ const path = pathMatch ? pathMatch[1].replace(/ -> .+$/, "").trim() : parts.slice(5).join(" ");
536
+ // Also detect symlinks from " -> " in the path
537
+ if (line.includes(" -> ") && type !== "l")
538
+ type = "l";
539
+ entries.push({ type, size, path });
540
+ }
541
+ return entries;
542
+ }
543
+ /**
544
+ * Pre-scan a tar.gz archive for security threats.
545
+ * Throws on: path traversal, symlinks, hardlinks, absolute paths, oversized.
546
+ */
547
+ export function preScanArchive(archivePath) {
548
+ detectTarVariant(); // Ensure tar exists
549
+ const output = execFileSync("tar", ["-tvzf", archivePath], {
550
+ encoding: "utf-8",
551
+ timeout: 60_000, // 60s timeout for large archives
552
+ maxBuffer: 50 * 1024 * 1024, // 50MB buffer for listing
553
+ });
554
+ const entries = parseTarVerboseOutput(output);
555
+ let totalSize = 0;
556
+ for (const entry of entries) {
557
+ // Symlinks are allowed — npm packages routinely contain
558
+ // node_modules/.bin/ symlinks pointing at sibling paths within the
559
+ // archive. The post-extract walk (verifyNoEscapes) will reject any
560
+ // symlink whose real target resolves outside the extraction directory.
561
+ // We only hard-ban hardlinks here because they can reference files
562
+ // by inode in ways that bypass the extraction root without a
563
+ // resolvable path.
564
+ // Reject hardlinks
565
+ if (entry.type === "h") {
566
+ throw new Error(`Archive contains hardlink: ${entry.path}`);
567
+ }
568
+ // Reject path traversal
569
+ if (entry.path.includes("../../") || entry.path.includes("..\\")) {
570
+ throw new Error(`Archive contains path traversal: ${entry.path}`);
571
+ }
572
+ // Reject absolute paths
573
+ if (entry.path.startsWith("/")) {
574
+ throw new Error(`Archive contains absolute path: ${entry.path}`);
575
+ }
576
+ // Accumulate size
577
+ totalSize += entry.size;
578
+ if (totalSize > MAX_UNCOMPRESSED_SIZE) {
579
+ throw new Error(`Archive uncompressed size exceeds 2GB limit (${Math.round(totalSize / 1024 / 1024)}MB)`);
580
+ }
581
+ }
582
+ return { entries, totalSize };
583
+ }
584
+ /**
585
+ * Safely extract a tar.gz archive to destDir.
586
+ * Pre-scans for security threats, then extracts with safety flags.
587
+ */
588
+ export async function safeExtract(archivePath, destDir) {
589
+ // Step 1: Pre-scan
590
+ const scanResult = preScanArchive(archivePath);
591
+ // Step 2: Ensure dest directory exists
592
+ if (!existsSync(destDir))
593
+ mkdirSync(destDir, { recursive: true });
594
+ // Step 3: Build extract args based on tar variant
595
+ const variant = detectTarVariant();
596
+ const args = ["-xzf", archivePath, "-C", destDir, "--no-same-owner"];
597
+ if (variant === "gnu") {
598
+ args.push("--no-same-permissions");
599
+ }
600
+ // Step 4: Extract via spawn (non-blocking)
601
+ await new Promise((resolve, reject) => {
602
+ const child = spawnChild("tar", args, { stdio: ["ignore", "pipe", "pipe"] });
603
+ let stderr = "";
604
+ child.stderr?.on("data", (chunk) => { stderr += chunk.toString(); });
605
+ child.on("close", (code) => {
606
+ if (code !== 0) {
607
+ reject(new Error(`tar extract failed (exit ${code}): ${stderr.slice(0, 500)}`));
608
+ }
609
+ else {
610
+ resolve();
611
+ }
612
+ });
613
+ child.on("error", reject);
614
+ });
615
+ // Step 5: Post-extract verification — ensure no entry's real path
616
+ // escapes the extraction directory.
617
+ //
618
+ // Symlinks get special-cased: we treat them as opaque blobs that will
619
+ // never be followed by downstream cpSync calls (those pass
620
+ // verbatimSymlinks: true). That eliminates the "symlink-to-/etc/passwd
621
+ // then write through it" attack surface, which means a symlink's
622
+ // target doesn't need to resolve at all for the archive to be safe.
623
+ // This matters because real archives routinely contain:
624
+ // - npm node_modules/.bin/* pointing at siblings by relative path
625
+ // - docker-originated absolute paths like `/app/...` that are
626
+ // intentionally dangling on the host but valid inside a container
627
+ // Escape detection still runs for non-symlink entries via realpathSync.
628
+ const destDirReal = realpathSync(destDir);
629
+ function verifyNoEscapes(dir) {
630
+ for (const entry of readdirSync(dir)) {
631
+ const fullPath = join(dir, entry);
632
+ const stat = lstatSync(fullPath);
633
+ if (stat.isSymbolicLink()) {
634
+ // Opaque blob — skip real-path check and do not recurse.
635
+ continue;
636
+ }
637
+ // Regular files/dirs: realpath is defined. If anything resolves
638
+ // outside destDir, it's an escape attempt that slipped past the
639
+ // pre-scan (e.g. via hardlink creativity).
640
+ const real = realpathSync(fullPath);
641
+ if (!real.startsWith(destDirReal + "/") && real !== destDirReal) {
642
+ throw new Error(`Extracted entry escaped target directory: ${fullPath} -> ${real}`);
643
+ }
644
+ if (stat.isDirectory()) {
645
+ verifyNoEscapes(fullPath);
646
+ }
647
+ }
648
+ }
649
+ verifyNoEscapes(destDir);
650
+ return scanResult;
651
+ }
652
+ // ── Pack / backup helpers ──
653
+ /** Calculate SHA-256 checksum of files in a directory, sorted by relative path */
654
+ function calculateContentChecksum(baseDir, files) {
655
+ const hash = createHash("sha256");
656
+ const sorted = [...files].sort();
657
+ for (const f of sorted) {
658
+ const fullPath = join(baseDir, f);
659
+ // Use lstat so we never follow a symlink during hashing; `files` is
660
+ // built by collectFiles which already excludes symlinks, but be
661
+ // defensive in case another caller passes a symlinked entry.
662
+ let stat;
663
+ try {
664
+ stat = lstatSync(fullPath);
665
+ }
666
+ catch {
667
+ continue;
668
+ }
669
+ if (stat.isFile()) {
670
+ hash.update(readFileSync(fullPath));
671
+ }
672
+ }
673
+ return hash.digest("hex");
674
+ }
675
+ /**
676
+ * Best-effort libc detection. Returns "glibc", "musl", "unknown", or "n/a" for non-Linux.
677
+ */
678
+ function detectLibc() {
679
+ if (process.platform !== "linux")
680
+ return "n/a";
681
+ try {
682
+ const output = execFileSync("ldd", ["--version"], { encoding: "utf-8", timeout: 2000 }).toString();
683
+ if (/musl/i.test(output))
684
+ return "musl";
685
+ if (/glibc|GNU libc/i.test(output))
686
+ return "glibc";
687
+ return "unknown";
688
+ }
689
+ catch {
690
+ return "unknown";
691
+ }
692
+ }
693
+ /**
694
+ * Copy a directory tree preserving symlinks as-is, without ever following
695
+ * them. Uses lstat exclusively so dangling / absolute-path symlinks (e.g.
696
+ * `app/openclaw.mjs -> /app/...` from a docker-cp'd container tree) do not
697
+ * crash the copy with ENOENT.
698
+ *
699
+ * `filter(rel)` returns false to skip the entry; for directories, returning
700
+ * false also skips the entire subtree. `rel` is the source-relative path
701
+ * using forward slashes.
702
+ *
703
+ * Used instead of `fs.cpSync` because Node's cpSync internally calls
704
+ * `stat` (not `lstat`) during bookkeeping, which follows symlinks and
705
+ * fails on broken targets even with `verbatimSymlinks: true`.
706
+ */
707
+ function copyTreeLstat(src, dst, filter, baseRel = "") {
708
+ // Create the destination directory itself (no-op if it exists).
709
+ if (!existsSync(dst))
710
+ mkdirSync(dst, { recursive: true });
711
+ for (const entry of readdirSync(src)) {
712
+ const srcPath = join(src, entry);
713
+ const rel = baseRel ? `${baseRel}/${entry}` : entry;
714
+ if (!filter(rel, entry))
715
+ continue;
716
+ let stat;
717
+ try {
718
+ stat = lstatSync(srcPath);
719
+ }
720
+ catch {
721
+ continue;
722
+ }
723
+ const dstPath = join(dst, entry);
724
+ if (stat.isSymbolicLink()) {
725
+ // Preserve the link target verbatim — never read through it.
726
+ try {
727
+ const target = readlinkSync(srcPath);
728
+ try {
729
+ rmSync(dstPath, { force: true });
730
+ }
731
+ catch { /* ignore */ }
732
+ symlinkSync(target, dstPath);
733
+ }
734
+ catch { /* best effort — skip unreadable symlinks */ }
735
+ }
736
+ else if (stat.isDirectory()) {
737
+ copyTreeLstat(srcPath, dstPath, filter, rel);
738
+ }
739
+ else if (stat.isFile()) {
740
+ try {
741
+ copyFileSync(srcPath, dstPath);
742
+ }
743
+ catch { /* best effort */ }
744
+ }
745
+ // Special files (sockets, devices, fifos) are silently skipped — they
746
+ // don't belong in a backup archive.
747
+ }
748
+ }
749
+ /**
750
+ * Recursively collect all regular files under a directory.
751
+ *
752
+ * Uses `lstat` so symlinks are identified as symlinks (not followed) — we
753
+ * intentionally skip them so dangling/absolute symlinks (like
754
+ * `app/openclaw.mjs -> /app/...` from a docker-cp'd tree) don't blow up
755
+ * checksum calculation with ENOENT. Symlinks are preserved in the tar
756
+ * archive by the packing step; they simply don't contribute file bytes
757
+ * to the content checksum.
758
+ */
759
+ function collectFiles(dir, baseDir, excludes) {
760
+ const results = [];
761
+ if (!existsSync(dir))
762
+ return results;
763
+ for (const entry of readdirSync(dir)) {
764
+ const fullPath = join(dir, entry);
765
+ const relPath = fullPath.slice(baseDir.length + 1); // relative to baseDir
766
+ // Check exclusions
767
+ if (excludes.some(ex => relPath.startsWith(ex) || relPath.endsWith(ex)))
768
+ continue;
769
+ let stat;
770
+ try {
771
+ stat = lstatSync(fullPath);
772
+ }
773
+ catch {
774
+ continue;
775
+ }
776
+ if (stat.isSymbolicLink())
777
+ continue; // skip; don't follow
778
+ if (stat.isDirectory()) {
779
+ results.push(...collectFiles(fullPath, baseDir, excludes));
780
+ }
781
+ else if (stat.isFile()) {
782
+ results.push(relPath);
783
+ }
784
+ }
785
+ return results;
786
+ }
787
+ /** Create a manual or auto backup of an instance */
788
+ export async function backupInstance(instanceId, opts = {}) {
789
+ const backupDir = getInstanceBackupDir(instanceId);
790
+ const type = opts.type || "manual-backup";
791
+ // Auto-backup is always state-scope (small, frequent).
792
+ // Manual backup defaults to home-scope (full disaster recovery).
793
+ const scope = type === "auto-backup" ? "state" : (opts.scope ?? "home");
794
+ // Workspace is included by default; callers can pass `includeWorkspace: false`
795
+ // to drop it. `onlyConfig` implies no workspace (it's a config-only subset).
796
+ const includeWorkspace = opts.onlyConfig ? false : (opts.includeWorkspace ?? true);
797
+ // Only state scope can use the official CLI; home scope must self-pack.
798
+ if (scope === "state") {
799
+ const cliResult = await callOpenclawBackup(instanceId, backupDir, {
800
+ onlyConfig: opts.onlyConfig,
801
+ noWorkspace: !includeWorkspace,
802
+ });
803
+ if (cliResult.ok && cliResult.archivePath) {
804
+ // Rename the CLI's output to JishuShell's canonical
805
+ // "<type>-<timestamp>.tar.gz" pattern. Both listInstanceBackups()
806
+ // (filename-prefix classification) and cleanOldAutoBackups() (prefix-
807
+ // based rolling deletion) depend on this convention. Without it, an
808
+ // auto-backup created by the official CLI — whose naming is not under
809
+ // our control — would be misclassified as "manual-backup" AND would
810
+ // never be rolled out of the backups directory, causing unbounded growth.
811
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace("T", "-").slice(0, 19);
812
+ const canonicalName = `${type}-${timestamp}.tar.gz`;
813
+ const canonicalPath = join(backupDir, canonicalName);
814
+ let finalArchivePath = cliResult.archivePath;
815
+ if (canonicalPath !== cliResult.archivePath) {
816
+ try {
817
+ // rename may fail if the canonical path already exists (extremely
818
+ // unlikely at second-precision timestamp, but guard anyway).
819
+ if (existsSync(canonicalPath))
820
+ rmSync(canonicalPath, { force: true });
821
+ renameSync(cliResult.archivePath, canonicalPath);
822
+ finalArchivePath = canonicalPath;
823
+ }
824
+ catch (e) {
825
+ console.warn(`[backup] Failed to rename CLI output ${cliResult.archivePath} -> ${canonicalPath}: ${e.message}. ` +
826
+ `File will be kept under its original name; it will be listed as "manual-backup" and will NOT be auto-rotated.`);
827
+ }
828
+ }
829
+ // Official CLI produced the archive — read its manifest for return value
830
+ const tmpDir = join(TMP_DIR, `manifest-read-${Date.now()}`);
831
+ try {
832
+ mkdirSync(tmpDir, { recursive: true });
833
+ // Full extract (state-scope archives are small) so resolveArchiveRoot
834
+ // can locate manifest.json regardless of whether the CLI wrapped it
835
+ // inside a <basename>/ top-level directory.
836
+ try {
837
+ execFileSync("tar", ["-xzf", finalArchivePath, "-C", tmpDir], { timeout: 30_000 });
838
+ }
839
+ catch { /* extraction may fail, continue with defaults */ }
840
+ const archiveRoot = resolveArchiveRoot(tmpDir);
841
+ const manifestPath = join(archiveRoot, "manifest.json");
842
+ const manifest = existsSync(manifestPath)
843
+ ? JSON.parse(readFileSync(manifestPath, "utf-8"))
844
+ : { schemaVersion: 1, type };
845
+ // Ensure scope + type fields match what JishuShell asked for (official
846
+ // CLI doesn't know about our scope enum and may not write type).
847
+ if (!manifest.scope)
848
+ manifest.scope = "state";
849
+ if (!manifest.type)
850
+ manifest.type = type;
851
+ const size = statSync(finalArchivePath).size;
852
+ return {
853
+ filename: finalArchivePath.split("/").pop(),
854
+ filepath: finalArchivePath,
855
+ size,
856
+ manifest,
857
+ };
858
+ }
859
+ finally {
860
+ rmSync(tmpDir, { recursive: true, force: true });
861
+ }
862
+ }
863
+ console.log(`[backup] Official CLI unavailable (${cliResult.error}), using self-pack fallback`);
864
+ }
865
+ // Self-pack path: used for home scope always, or as fallback for state scope
866
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace("T", "-").slice(0, 19);
867
+ const filename = `${type}-${timestamp}.tar.gz`;
868
+ const outputPath = opts.outputPath || join(backupDir, filename);
869
+ return selfPackOfficialFormat(instanceId, outputPath, {
870
+ type,
871
+ scope,
872
+ includeSessions: opts.includeSessions,
873
+ includeWorkspace,
874
+ onlyConfig: opts.onlyConfig,
875
+ });
876
+ }
877
+ /**
878
+ * Pack an instance in the official OpenClaw backup archive format.
879
+ * Layout: manifest.json at root + payload/<encoded-path>/.openclaw/...
880
+ * This is the fallback when `openclaw backup create` CLI is unavailable.
881
+ */
882
+ export async function selfPackOfficialFormat(instanceId, outputPath, opts) {
883
+ const { getInstance } = await import("../app-common/service.js");
884
+ const meta = getInstance(instanceId);
885
+ if (!meta)
886
+ throw new Error(`Instance ${instanceId} not found`);
887
+ const openclawHome = integrationHomeFromMetadata(meta, instanceId);
888
+ const stateDir = join(openclawHome, ".openclaw");
889
+ if (!existsSync(stateDir))
890
+ throw new Error(`State directory not found: ${stateDir}`);
891
+ const scope = opts.scope ?? "state";
892
+ const encodedPath = encodeAbsolutePathForArchive(openclawHome);
893
+ const stagingDir = join(TMP_DIR, `selfpack-${instanceId}-${Date.now()}`);
894
+ const stagingHomeDir = join(stagingDir, "payload", encodedPath);
895
+ const payloadStateDir = join(stagingHomeDir, ".openclaw");
896
+ try {
897
+ if (scope === "home") {
898
+ // Home scope: pack entire openclaw-home/ tree minus cache dirs
899
+ mkdirSync(stagingHomeDir, { recursive: true });
900
+ const homeTopLevelExcludes = [
901
+ ".jishushell-owned",
902
+ "workspace",
903
+ "openclaw.json",
904
+ ".npm/_cacache",
905
+ ".npm/_logs",
906
+ ".npm/_npx",
907
+ ".npm/_update-notifier-last-checked",
908
+ ".cache",
909
+ ".node_compile_cache",
910
+ ];
911
+ const stateSubExcludes = [
912
+ ".npm",
913
+ ".cache",
914
+ ".node_compile_cache",
915
+ "workspace/.npm-global",
916
+ ];
917
+ const sessionExcludes = opts.includeSessions ? [] : ["agents/main/sessions"];
918
+ const workspaceExcludes = opts.includeWorkspace === false ? ["workspace"] : [];
919
+ if (opts.onlyConfig) {
920
+ // Only include openclaw.json inside .openclaw/
921
+ mkdirSync(payloadStateDir, { recursive: true });
922
+ const configSrc = join(stateDir, "openclaw.json");
923
+ if (existsSync(configSrc)) {
924
+ copyFileSync(configSrc, join(payloadStateDir, "openclaw.json"));
925
+ }
926
+ }
927
+ else {
928
+ // Use copyTreeLstat so we never follow symlinks (even dangling
929
+ // ones like `app/openclaw.mjs -> /app/...` from a docker-cp'd
930
+ // container tree). fs.cpSync cannot be used here: even with
931
+ // verbatimSymlinks: true, its internal bookkeeping calls `stat`
932
+ // on dest entries which follows broken links and crashes with
933
+ // ENOENT on the first dangling symlink.
934
+ copyTreeLstat(openclawHome, stagingHomeDir, (rel) => {
935
+ if (!rel)
936
+ return true;
937
+ if (homeTopLevelExcludes.some(ex => rel === ex || rel.startsWith(ex + "/")))
938
+ return false;
939
+ if (rel.startsWith(".npm-global/") && rel.includes("/.cache/"))
940
+ return false;
941
+ if (rel === ".openclaw" || rel.startsWith(".openclaw/")) {
942
+ const stateRel = rel === ".openclaw" ? "" : rel.slice(".openclaw/".length);
943
+ if (!stateRel)
944
+ return true;
945
+ if (stateSubExcludes.some(ex => stateRel === ex || stateRel.startsWith(ex + "/")))
946
+ return false;
947
+ if (sessionExcludes.some(ex => stateRel === ex || stateRel.startsWith(ex + "/")))
948
+ return false;
949
+ if (workspaceExcludes.some(ex => stateRel === ex || stateRel.startsWith(ex + "/")))
950
+ return false;
951
+ }
952
+ return true;
953
+ });
954
+ }
955
+ }
956
+ else {
957
+ // State scope (default): only .openclaw/ contents
958
+ mkdirSync(payloadStateDir, { recursive: true });
959
+ // Build exclusion list
960
+ const excludes = [
961
+ ".npm/",
962
+ ".cache/",
963
+ ".node_compile_cache/",
964
+ "workspace/.npm-global/",
965
+ ];
966
+ if (!opts.includeSessions)
967
+ excludes.push("agents/main/sessions/");
968
+ if (opts.includeWorkspace === false)
969
+ excludes.push("workspace/");
970
+ if (opts.onlyConfig) {
971
+ // Only copy openclaw.json
972
+ const configSrc = join(stateDir, "openclaw.json");
973
+ if (existsSync(configSrc)) {
974
+ copyFileSync(configSrc, join(payloadStateDir, "openclaw.json"));
975
+ }
976
+ }
977
+ else {
978
+ // Copy .openclaw/ contents (respecting excludes). Manual lstat
979
+ // walker — same rationale as the home-scope branch above.
980
+ copyTreeLstat(stateDir, payloadStateDir, (rel) => {
981
+ if (!rel)
982
+ return true; // root dir
983
+ return !excludes.some(ex => rel.startsWith(ex));
984
+ });
985
+ }
986
+ }
987
+ // Collect all files for checksum (excluding manifest which doesn't exist yet)
988
+ const allFiles = collectFiles(stagingDir, stagingDir, []);
989
+ // Calculate checksum from staged files
990
+ const checksum = calculateContentChecksum(stagingDir, allFiles);
991
+ const hasSessions = opts.includeSessions ?? false;
992
+ // Generate manifest (minimum required fields matching official format)
993
+ const manifest = {
994
+ schemaVersion: 1,
995
+ type: opts.type,
996
+ scope,
997
+ createdAt: new Date().toISOString(),
998
+ name: meta.name || instanceId,
999
+ description: meta.description || "",
1000
+ platform: process.platform,
1001
+ arch: process.arch,
1002
+ libc: detectLibc(),
1003
+ nodeVersion: process.version,
1004
+ paths: {
1005
+ stateDir: `payload/${encodedPath}/.openclaw`,
1006
+ },
1007
+ source_home: openclawHome,
1008
+ has_sessions: hasSessions,
1009
+ checksum: `sha256:${checksum}`,
1010
+ checksum_scope: "content-excluding-manifest",
1011
+ };
1012
+ // Write manifest to staging root
1013
+ writeFileSync(join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2));
1014
+ // Single-pass tar via spawn with heartbeat
1015
+ const stopHeartbeat = startLockHeartbeat(instanceId);
1016
+ try {
1017
+ await new Promise((resolve, reject) => {
1018
+ const child = spawnChild("tar", ["-czf", outputPath, "."], {
1019
+ cwd: stagingDir,
1020
+ stdio: ["ignore", "pipe", "pipe"],
1021
+ });
1022
+ let stderr = "";
1023
+ child.stderr?.on("data", (chunk) => { stderr += chunk.toString(); });
1024
+ child.on("close", (code) => {
1025
+ if (code !== 0)
1026
+ reject(new Error(`tar pack failed (exit ${code}): ${stderr.slice(0, 500)}`));
1027
+ else
1028
+ resolve();
1029
+ });
1030
+ child.on("error", reject);
1031
+ });
1032
+ }
1033
+ finally {
1034
+ stopHeartbeat();
1035
+ }
1036
+ const finalSize = statSync(outputPath).size;
1037
+ return { filename: outputPath.split("/").pop(), filepath: outputPath, size: finalSize, manifest };
1038
+ }
1039
+ finally {
1040
+ // Cleanup staging
1041
+ rmSync(stagingDir, { recursive: true, force: true });
1042
+ }
1043
+ }
1044
+ /** Step 13: only nomad service manager is supported on the runtime path. */
1045
+ async function getSvc() {
1046
+ const { getServiceManagerType } = await import("../../config.js");
1047
+ const type = getServiceManagerType();
1048
+ if (type !== "nomad") {
1049
+ throw new Error(`service_manager='${type}' is no longer supported. ` +
1050
+ "Only 'nomad' runtimes are accepted on the runtime path. " +
1051
+ "Run `jishushell migrate legacy` to convert legacy instances.");
1052
+ }
1053
+ return import("../runtime/drivers/nomad.js");
1054
+ }
1055
+ /**
1056
+ * Restore an instance from a backup file.
1057
+ * Full flow: lock -> extract -> validate -> pre-restore backup -> stop -> restore -> rebuild -> start -> healthcheck -> rollback on failure
1058
+ */
1059
+ export async function restoreInstance(instanceId, backupFilePath) {
1060
+ const { getInstance } = await import("../app-common/service.js");
1061
+ const meta = getInstance(instanceId);
1062
+ if (!meta)
1063
+ throw new Error(`Instance ${instanceId} not found`);
1064
+ const shouldRestart = meta.desiredState === "running";
1065
+ const openclawHome = integrationHomeFromMetadata(meta, instanceId);
1066
+ const warnings = [];
1067
+ // Default to "preserved"; promoted to "lost" below if we detect the archive
1068
+ // was built on a different machine (platform/arch mismatch is a proxy signal).
1069
+ let apiKeyStatus = "preserved";
1070
+ // Step 1: Acquire lock
1071
+ if (!acquireInstanceLock(instanceId, "restoring")) {
1072
+ const lock = getInstanceLockStatus(instanceId);
1073
+ throw Object.assign(new Error(`Instance ${instanceId} is locked: ${lock?.operation}`), { statusCode: 409 });
1074
+ }
1075
+ const stopHeartbeat = startLockHeartbeat(instanceId);
1076
+ const tmpDir = join(TMP_DIR, `restore-${instanceId}-${Date.now()}`);
1077
+ let currentPreRestorePath = null;
1078
+ try {
1079
+ // Step 2: Safe extract to temp dir
1080
+ mkdirSync(tmpDir, { recursive: true });
1081
+ await safeExtract(backupFilePath, tmpDir);
1082
+ touchInstanceLock(instanceId);
1083
+ // Step 3: Resolve the archive root (handles both self-pack layout and
1084
+ // official CLI's top-level wrapper directory) and validate manifest
1085
+ const archiveRoot = resolveArchiveRoot(tmpDir);
1086
+ const manifestPath = join(archiveRoot, "manifest.json");
1087
+ if (!existsSync(manifestPath))
1088
+ throw new Error("Archive missing manifest.json");
1089
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1090
+ if (manifest.schemaVersion && manifest.schemaVersion > 1) {
1091
+ warnings.push(`Archive schemaVersion ${manifest.schemaVersion} is newer than supported (1)`);
1092
+ }
1093
+ // Step 4: Locate .openclaw/ via payload/ directory
1094
+ const payloadDir = join(archiveRoot, "payload");
1095
+ if (!existsSync(payloadDir))
1096
+ throw new Error("Archive missing payload/ directory");
1097
+ const extractedStateDir = findStateDir(payloadDir);
1098
+ if (!extractedStateDir)
1099
+ throw new Error("Could not locate .openclaw directory in archive");
1100
+ // extractedHomeDir is the parent of .openclaw — source for home-scope restore
1101
+ const extractedHomeDir = dirname(extractedStateDir);
1102
+ // Verify openclaw.json exists in located dir
1103
+ const extractedConfigPath = join(extractedStateDir, "openclaw.json");
1104
+ if (!existsSync(extractedConfigPath))
1105
+ throw new Error("Archive missing openclaw.json");
1106
+ JSON.parse(readFileSync(extractedConfigPath, "utf-8")); // verify parseable
1107
+ // Determine effective scope and check arch compatibility
1108
+ const archiveScope = manifest.scope === "home" ? "home" : "state";
1109
+ let effectiveScope = archiveScope;
1110
+ const platformMismatch = manifest.platform && manifest.platform !== process.platform;
1111
+ const archMismatch = manifest.arch && manifest.arch !== process.arch;
1112
+ if (platformMismatch || archMismatch) {
1113
+ // Archive was built elsewhere — openclaw.json may reference API key env
1114
+ // vars that this machine's provider.env doesn't have set.
1115
+ apiKeyStatus = "lost";
1116
+ }
1117
+ if (archiveScope === "home" && (platformMismatch || archMismatch)) {
1118
+ warnings.push(`Architecture mismatch: archive built for ${manifest.platform}/${manifest.arch}, ` +
1119
+ `current host is ${process.platform}/${process.arch}. ` +
1120
+ `Downgrading to state-only restore. You may need to re-run "openclaw update" after restore.`);
1121
+ effectiveScope = "state";
1122
+ }
1123
+ touchInstanceLock(instanceId);
1124
+ // Step 5: Create pre-restore backup at the SAME scope we're about to
1125
+ // overwrite, so rollback can actually put everything back. This snapshot
1126
+ // is transaction-local: it lives under this restore's tmp directory and
1127
+ // is removed in finally whether restore succeeds, fails, or rolls back.
1128
+ // If snapshot creation fails, restoration must stop before touching the
1129
+ // current instance.
1130
+ try {
1131
+ const preRestorePath = join(tmpDir, "pre-restore-current.tar.gz");
1132
+ await selfPackOfficialFormat(instanceId, preRestorePath, {
1133
+ type: "pre-restore",
1134
+ scope: effectiveScope,
1135
+ includeSessions: true,
1136
+ includeWorkspace: true,
1137
+ });
1138
+ currentPreRestorePath = preRestorePath;
1139
+ }
1140
+ catch (e) {
1141
+ throw Object.assign(new Error(`Restore cancelled because the safety snapshot could not be created: ${e?.message ?? e}`), { statusCode: 500, code: "restore-snapshot-failed" });
1142
+ }
1143
+ touchInstanceLock(instanceId);
1144
+ // Step 6: Stop instance
1145
+ try {
1146
+ const { stopInstance } = await import("../app-common/lifecycle-service.js");
1147
+ await stopInstance(instanceId);
1148
+ }
1149
+ catch {
1150
+ // Instance may not be running -- ignore stop failures
1151
+ }
1152
+ // Clean proxy state
1153
+ try {
1154
+ const { cleanupInstance } = await import("../llm-proxy/instance-proxy.js");
1155
+ cleanupInstance(instanceId);
1156
+ }
1157
+ catch { /* ignore */ }
1158
+ touchInstanceLock(instanceId);
1159
+ // Step 7: Clear current target directory based on effective scope.
1160
+ // Canonical integration paths preserve custom-home installs without a
1161
+ // second OpenClaw-specific path field.
1162
+ const stateDir = join(openclawHome, ".openclaw");
1163
+ const cacheKeepDirs = new Set([".npm", ".cache", ".node_compile_cache"]);
1164
+ const preservedHomeEntries = new Set([
1165
+ ".npm",
1166
+ ".cache",
1167
+ ".node_compile_cache",
1168
+ ".jishushell-owned",
1169
+ "workspace",
1170
+ ]);
1171
+ if (effectiveScope === "home") {
1172
+ // Home scope: clear openclaw-home except cache dirs
1173
+ if (existsSync(openclawHome)) {
1174
+ for (const entry of readdirSync(openclawHome)) {
1175
+ if (preservedHomeEntries.has(entry))
1176
+ continue;
1177
+ rmSync(join(openclawHome, entry), { recursive: true, force: true });
1178
+ }
1179
+ }
1180
+ else {
1181
+ mkdirSync(openclawHome, { recursive: true });
1182
+ }
1183
+ }
1184
+ else {
1185
+ // State scope: clear only .openclaw/, keep cache subdirs inside
1186
+ const legacyConfig = join(openclawHome, "openclaw.json");
1187
+ if (existsSync(legacyConfig))
1188
+ rmSync(legacyConfig, { force: true });
1189
+ if (existsSync(stateDir)) {
1190
+ for (const entry of readdirSync(stateDir)) {
1191
+ if (cacheKeepDirs.has(entry))
1192
+ continue;
1193
+ rmSync(join(stateDir, entry), { recursive: true, force: true });
1194
+ }
1195
+ }
1196
+ else {
1197
+ mkdirSync(stateDir, { recursive: true });
1198
+ }
1199
+ }
1200
+ touchInstanceLock(instanceId);
1201
+ // Step 8: Copy restored content based on effective scope. Uses the
1202
+ // manual lstat walker (not fs.cpSync) so dangling/absolute symlinks
1203
+ // survive the round-trip without ENOENT.
1204
+ if (effectiveScope === "home") {
1205
+ // Home scope: copy entire extracted home dir into openclawHome
1206
+ copyTreeLstat(extractedHomeDir, openclawHome, (rel) => !isNonPortableHomeEntry(rel));
1207
+ }
1208
+ else {
1209
+ // State scope: copy only .openclaw/
1210
+ if (existsSync(extractedStateDir)) {
1211
+ copyTreeLstat(extractedStateDir, stateDir, () => true);
1212
+ }
1213
+ }
1214
+ touchInstanceLock(instanceId);
1215
+ // Rewrite absolute paths in session data and symlinks when the backup
1216
+ // originates from a different machine or instance (cross-machine restore).
1217
+ const oldHome = detectOldHome(manifest, payloadDir, extractedStateDir);
1218
+ if (oldHome) {
1219
+ rewriteInstancePaths(effectiveScope === "home" ? openclawHome : stateDir, oldHome, openclawHome, warnings);
1220
+ }
1221
+ // Step 10: Rebuild runtime state
1222
+ try {
1223
+ const { bootstrapInstanceProxy } = await import("../llm-proxy/instance-proxy.js");
1224
+ await bootstrapInstanceProxy(instanceId);
1225
+ }
1226
+ catch (e) {
1227
+ warnings.push(`Proxy bootstrap warning: ${e.message}`);
1228
+ }
1229
+ touchInstanceLock(instanceId);
1230
+ // Step 11: Restore the user's long-term desired state. A restore must not
1231
+ // turn a deliberately stopped instance into a running one.
1232
+ let started = false;
1233
+ if (shouldRestart) {
1234
+ try {
1235
+ const { startInstance } = await import("../app-common/lifecycle-service.js");
1236
+ const result = await startInstance(instanceId);
1237
+ started = result.ok !== false;
1238
+ }
1239
+ catch { /* ignore start failures */ }
1240
+ }
1241
+ touchInstanceLock(instanceId);
1242
+ // Step 12: Use normal observed readiness. Allocation running alone is not
1243
+ // enough; health and Core proxy reachability must also be ready.
1244
+ if (shouldRestart) {
1245
+ let ready = false;
1246
+ if (started) {
1247
+ try {
1248
+ const { waitForObservedStatus } = await import("../app-common/status-refresh.js");
1249
+ const snapshot = await waitForObservedStatus(instanceId, "ready", {
1250
+ reason: "manual",
1251
+ timeoutMs: 30_000,
1252
+ });
1253
+ ready = snapshot?.instance.status.phase === "ready";
1254
+ }
1255
+ catch { /* handled by rollback below */ }
1256
+ }
1257
+ if (!ready) {
1258
+ warnings.push("Health check failed after restore. Attempting auto-rollback...");
1259
+ if (!currentPreRestorePath) {
1260
+ warnings.push("No snapshot from this restore operation is available; automatic rollback was skipped.");
1261
+ return { ok: false, warnings, api_key_status: apiKeyStatus, rolled_back: false };
1262
+ }
1263
+ try {
1264
+ await _rollbackFromPreRestore(instanceId, meta, currentPreRestorePath);
1265
+ warnings.push("Auto-rollback completed. Instance restored to pre-restore state.");
1266
+ return { ok: false, warnings, api_key_status: apiKeyStatus, rolled_back: true };
1267
+ }
1268
+ catch (e) {
1269
+ warnings.push(`Auto-rollback also failed: ${e.message}. Manual recovery needed.`);
1270
+ return { ok: false, warnings, api_key_status: apiKeyStatus, rolled_back: false };
1271
+ }
1272
+ }
1273
+ }
1274
+ return { ok: true, warnings, api_key_status: apiKeyStatus };
1275
+ }
1276
+ finally {
1277
+ stopHeartbeat();
1278
+ if (existsSync(tmpDir))
1279
+ rmSync(tmpDir, { recursive: true, force: true });
1280
+ releaseInstanceLock(instanceId);
1281
+ }
1282
+ }
1283
+ /**
1284
+ * Create a new instance from a backup file.
1285
+ * Does NOT copy model.env (new proxy token), provider.env (can't decrypt), or IM credentials.
1286
+ * Hard rule: provider.env is always ignored — no decrypt attempt, no migration.
1287
+ */
1288
+ export async function createFromBackup(backupFilePath, opts) {
1289
+ const warnings = [];
1290
+ const tmpDir = join(TMP_DIR, `create-from-backup-${opts.newId}-${Date.now()}`);
1291
+ let targetInstalled = false;
1292
+ try {
1293
+ // Extract and validate
1294
+ mkdirSync(tmpDir, { recursive: true });
1295
+ await safeExtract(backupFilePath, tmpDir);
1296
+ // Resolve root — handles both self-pack and official CLI wrapper layouts
1297
+ const archiveRoot = resolveArchiveRoot(tmpDir);
1298
+ // Detect archive scope — for home-scope archives we restore the full
1299
+ // openclaw-home tree, not just .openclaw (see importInstance for the
1300
+ // rationale).
1301
+ let archiveScope = "state";
1302
+ let manifest = null;
1303
+ const manifestPath = join(archiveRoot, "manifest.json");
1304
+ if (existsSync(manifestPath)) {
1305
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1306
+ if (manifest.has_api_keys === false || !manifest.has_api_keys) {
1307
+ warnings.push("No API Key in this backup. You will need to configure one.");
1308
+ }
1309
+ if (manifest?.scope === "home")
1310
+ archiveScope = "home";
1311
+ }
1312
+ // Locate .openclaw/ via payload/ directory (official format)
1313
+ const payloadDir = join(archiveRoot, "payload");
1314
+ if (!existsSync(payloadDir))
1315
+ throw new Error("Archive missing payload/ directory");
1316
+ const extractedStateDir = findStateDir(payloadDir);
1317
+ if (!extractedStateDir)
1318
+ throw new Error("Could not locate .openclaw directory in archive");
1319
+ const { openclawHome: newOpenclawHome, stateDir: newStateDir } = await installOpenclawRestoreTarget(opts.newId, opts.newName, opts.newDescription || "");
1320
+ targetInstalled = true;
1321
+ // Copy content to the new instance's openclaw-home. Manual lstat
1322
+ // walker preserves symlinks as opaque blobs (see pack-side notes).
1323
+ if (archiveScope === "home") {
1324
+ // Home scope: copy the entire extracted home tree.
1325
+ const extractedHomeDir = dirname(extractedStateDir);
1326
+ copyTreeLstat(extractedHomeDir, newOpenclawHome, (rel) => {
1327
+ if (!rel)
1328
+ return true;
1329
+ if (isNonPortableHomeEntry(rel))
1330
+ return false;
1331
+ if (rel === ".openclaw/openclaw-weixin")
1332
+ return false;
1333
+ if (rel.startsWith(".openclaw/openclaw-weixin/"))
1334
+ return false;
1335
+ return true;
1336
+ });
1337
+ }
1338
+ else {
1339
+ // State scope: copy only .openclaw/.
1340
+ copyTreeLstat(extractedStateDir, newStateDir, (rel) => {
1341
+ if (!rel)
1342
+ return true;
1343
+ if (rel.startsWith("openclaw-weixin"))
1344
+ return false;
1345
+ return true;
1346
+ });
1347
+ }
1348
+ // Rewrite absolute paths in session data and symlinks from the source
1349
+ // instance to the new instance (fixes ENOENT crash on Docker import
1350
+ // and silent cross-instance data leakage on local import).
1351
+ const oldHome = detectOldHome(manifest, payloadDir, extractedStateDir);
1352
+ if (oldHome) {
1353
+ rewriteInstancePaths(archiveScope === "home" ? newOpenclawHome : newStateDir, oldHome, newOpenclawHome, warnings);
1354
+ }
1355
+ // Scrub channel credentials from copied openclaw.json (new instance should not inherit IM bindings)
1356
+ await scrubNewInstanceConfig(join(newStateDir, "openclaw.json"));
1357
+ // Regenerate gateway auth token (scrub cleared it to prevent credential leakage,
1358
+ // but the gateway requires a valid token to accept requests)
1359
+ regenerateGatewayAuthToken(join(newStateDir, "openclaw.json"));
1360
+ // Bootstrap proxy for new instance (generates new token)
1361
+ try {
1362
+ const { bootstrapInstanceProxy } = await import("../llm-proxy/instance-proxy.js");
1363
+ await bootstrapInstanceProxy(opts.newId);
1364
+ }
1365
+ catch (e) {
1366
+ warnings.push(`Proxy bootstrap warning: ${e.message}`);
1367
+ }
1368
+ warnings.push("Instance created. Configure API Key before starting.");
1369
+ return { ok: true, instance_id: opts.newId, warnings };
1370
+ }
1371
+ catch (error) {
1372
+ if (targetInstalled) {
1373
+ try {
1374
+ await rollbackOpenclawRestoreTarget(opts.newId);
1375
+ }
1376
+ catch (rollbackError) {
1377
+ console.error(`[backup] failed to rollback partial restore target ${opts.newId}: ${rollbackError?.message ?? rollbackError}`);
1378
+ }
1379
+ }
1380
+ throw error;
1381
+ }
1382
+ finally {
1383
+ if (existsSync(tmpDir))
1384
+ rmSync(tmpDir, { recursive: true, force: true });
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Internal rollback helper -- restores only from the snapshot created by the
1389
+ * current restore operation.
1390
+ * Does NOT re-acquire lock (caller already holds it).
1391
+ * Does NOT create another pre-restore backup.
1392
+ */
1393
+ async function _rollbackFromPreRestore(instanceId, meta, preRestorePath) {
1394
+ if (!existsSync(preRestorePath)) {
1395
+ throw new Error("Current restore snapshot is missing");
1396
+ }
1397
+ const tmpDir = join(TMP_DIR, `rollback-${instanceId}-${Date.now()}`);
1398
+ try {
1399
+ mkdirSync(tmpDir, { recursive: true });
1400
+ await safeExtract(preRestorePath, tmpDir);
1401
+ const openclawHome = integrationHomeFromMetadata(meta, instanceId);
1402
+ const stateDir = join(openclawHome, ".openclaw");
1403
+ // Resolve archive root (pre-restore is always self-pack so this is
1404
+ // usually a no-op, but handle either layout for safety).
1405
+ const archiveRoot = resolveArchiveRoot(tmpDir);
1406
+ // Locate .openclaw/ and its parent (source for home-scope rollback)
1407
+ const payloadDir = join(archiveRoot, "payload");
1408
+ const extractedStateDir = existsSync(payloadDir) ? findStateDir(payloadDir) : null;
1409
+ if (!extractedStateDir)
1410
+ throw new Error("pre-restore archive missing .openclaw directory");
1411
+ const extractedHomeDir = dirname(extractedStateDir);
1412
+ // Read the pre-restore's own manifest to decide rollback scope.
1413
+ // If the pre-restore was packed as home scope, we must rollback the full
1414
+ // portable home tree (.npm-global/, .codex/, etc.) — not just
1415
+ // .openclaw/ — otherwise we leave the instance in a half-deleted state.
1416
+ let rollbackScope = "state";
1417
+ const manifestPath = join(archiveRoot, "manifest.json");
1418
+ if (existsSync(manifestPath)) {
1419
+ try {
1420
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1421
+ if (manifest?.scope === "home")
1422
+ rollbackScope = "home";
1423
+ }
1424
+ catch { /* fall back to state */ }
1425
+ }
1426
+ const cacheKeepDirs = new Set([".npm", ".cache", ".node_compile_cache"]);
1427
+ const preservedHomeEntries = new Set([
1428
+ ".npm",
1429
+ ".cache",
1430
+ ".node_compile_cache",
1431
+ ".jishushell-owned",
1432
+ "workspace",
1433
+ ]);
1434
+ if (rollbackScope === "home") {
1435
+ // Clear openclaw-home/ except cache dirs, then restore the entire tree.
1436
+ if (existsSync(openclawHome)) {
1437
+ for (const entry of readdirSync(openclawHome)) {
1438
+ if (preservedHomeEntries.has(entry))
1439
+ continue;
1440
+ rmSync(join(openclawHome, entry), { recursive: true, force: true });
1441
+ }
1442
+ }
1443
+ else {
1444
+ mkdirSync(openclawHome, { recursive: true });
1445
+ }
1446
+ copyTreeLstat(extractedHomeDir, openclawHome, (rel) => !isNonPortableHomeEntry(rel));
1447
+ }
1448
+ else {
1449
+ // State-scope rollback: clear and restore only .openclaw/.
1450
+ if (existsSync(stateDir)) {
1451
+ for (const entry of readdirSync(stateDir)) {
1452
+ if (cacheKeepDirs.has(entry))
1453
+ continue;
1454
+ rmSync(join(stateDir, entry), { recursive: true, force: true });
1455
+ }
1456
+ }
1457
+ else {
1458
+ mkdirSync(stateDir, { recursive: true });
1459
+ }
1460
+ copyTreeLstat(extractedStateDir, stateDir, () => true);
1461
+ }
1462
+ // Rebuild proxy state
1463
+ try {
1464
+ const { bootstrapInstanceProxy } = await import("../llm-proxy/instance-proxy.js");
1465
+ await bootstrapInstanceProxy(instanceId);
1466
+ }
1467
+ catch { /* best effort */ }
1468
+ // Try to restart
1469
+ try {
1470
+ const { startInstance } = await import("../app-common/lifecycle-service.js");
1471
+ await startInstance(instanceId);
1472
+ }
1473
+ catch { /* best effort */ }
1474
+ }
1475
+ finally {
1476
+ if (existsSync(tmpDir))
1477
+ rmSync(tmpDir, { recursive: true, force: true });
1478
+ }
1479
+ }
1480
+ // ── Export ──
1481
+ /**
1482
+ * Export an instance for sharing. Uses WHITELIST strategy — only safe directories included.
1483
+ * API keys and credentials are stripped. Sensitive fields in openclaw.json are scrubbed.
1484
+ */
1485
+ export async function exportInstance(instanceId, opts = {}) {
1486
+ const { getInstance } = await import("../app-common/service.js");
1487
+ const meta = getInstance(instanceId);
1488
+ if (!meta)
1489
+ throw new Error(`Instance ${instanceId} not found`);
1490
+ const openclawHome = integrationHomeFromMetadata(meta, instanceId);
1491
+ const stateDir = join(openclawHome, ".openclaw");
1492
+ if (!existsSync(stateDir))
1493
+ throw new Error(`State directory not found: ${stateDir}`);
1494
+ if (!acquireInstanceLock(instanceId, "exporting")) {
1495
+ throw Object.assign(new Error(`Instance ${instanceId} is locked`), { statusCode: 409 });
1496
+ }
1497
+ const stopHeartbeat = startLockHeartbeat(instanceId);
1498
+ // Export downloads are served per-instance from TMP_DIR/exports/<instanceId>/.
1499
+ // The download route enforces this isolation so one instance's URL can't
1500
+ // reach another's file.
1501
+ const instanceExportDir = getInstanceExportDir(instanceId);
1502
+ const outputPath = opts.outputPath || join(instanceExportDir, `export-${instanceId}-${Date.now()}.tar.gz`);
1503
+ const stagingDir = join(TMP_DIR, `export-staging-${instanceId}-${Date.now()}`);
1504
+ const tmpBackupPath = join(TMP_DIR, `export-raw-${instanceId}-${Date.now()}.tar.gz`);
1505
+ try {
1506
+ // Step 1: Self-pack in official format (always self-pack for export — we need to filter)
1507
+ await selfPackOfficialFormat(instanceId, tmpBackupPath, {
1508
+ type: "export",
1509
+ includeSessions: opts.includeSessions,
1510
+ includeWorkspace: true,
1511
+ });
1512
+ // Step 2: Extract to staging
1513
+ mkdirSync(stagingDir, { recursive: true });
1514
+ await safeExtract(tmpBackupPath, stagingDir);
1515
+ if (existsSync(tmpBackupPath))
1516
+ rmSync(tmpBackupPath, { force: true });
1517
+ // Step 3: Resolve archive root + locate .openclaw/ via payload/.
1518
+ // self-pack always writes at root so resolveArchiveRoot is a no-op here,
1519
+ // but using it keeps all consumers on one canonical path.
1520
+ const stagingRoot = resolveArchiveRoot(stagingDir);
1521
+ const payloadDir = join(stagingRoot, "payload");
1522
+ if (!existsSync(payloadDir))
1523
+ throw new Error("Self-pack missing payload/ directory");
1524
+ const foundDir = findStateDir(payloadDir);
1525
+ if (!foundDir)
1526
+ throw new Error("Could not locate .openclaw in self-pack");
1527
+ // Step 4: Whitelist filter — remove everything outside allowed dirs
1528
+ const allowedRoots = ["openclaw.json", "extensions", "workspace"];
1529
+ if (opts.includeSessions)
1530
+ allowedRoots.push("agents");
1531
+ for (const entry of readdirSync(foundDir)) {
1532
+ if (!allowedRoots.includes(entry)) {
1533
+ rmSync(join(foundDir, entry), { recursive: true, force: true });
1534
+ }
1535
+ }
1536
+ // Step 5: Scrub openclaw.json
1537
+ const configPath = join(foundDir, "openclaw.json");
1538
+ const scrubWarnings = [];
1539
+ if (existsSync(configPath)) {
1540
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
1541
+ // Scrub provider API keys
1542
+ const providers = config?.models?.providers;
1543
+ if (providers) {
1544
+ for (const [pid, prov] of Object.entries(providers)) {
1545
+ if (prov?.apiKey)
1546
+ prov.apiKey = "";
1547
+ // Remove proxy providers
1548
+ if (typeof prov?.baseUrl === "string" && prov.baseUrl.includes("/proxy/")) {
1549
+ delete providers[pid];
1550
+ }
1551
+ }
1552
+ }
1553
+ // Scrub channel credentials
1554
+ const channels = config?.channels;
1555
+ if (channels) {
1556
+ for (const [, ch] of Object.entries(channels)) {
1557
+ if (ch?.appSecret)
1558
+ ch.appSecret = "";
1559
+ if (ch?.appId)
1560
+ ch.appId = "";
1561
+ if (ch?.token)
1562
+ ch.token = "";
1563
+ if (ch?.secret)
1564
+ ch.secret = "";
1565
+ if (ch?.credentials)
1566
+ ch.credentials = {};
1567
+ }
1568
+ }
1569
+ // Scrub gateway control-UI token — this is the token the panel uses
1570
+ // to drive OpenClaw's gateway control API; leaking it would let anyone
1571
+ // with the export package call the recipient's gateway once imported.
1572
+ if (config?.gateway?.auth?.token)
1573
+ config.gateway.auth.token = "";
1574
+ // Remove proxy model reference
1575
+ const defaultModel = config?.agents?.defaults?.model;
1576
+ if (typeof defaultModel === "string" && (defaultModel.startsWith("jsproxy/") || defaultModel.startsWith("js-"))) {
1577
+ delete config.agents.defaults.model;
1578
+ }
1579
+ // Residual scan — walk the object and flag any string-valued field
1580
+ // whose key name looks sensitive. The warning only records the JSON
1581
+ // path and field length, NEVER the value itself, so a residual field
1582
+ // can't leak through manifest.warnings.
1583
+ const SENSITIVE_KEY_RE = /^(api_?key|token|secret|credential|password|auth_?key|app_?secret|app_?id)$/i;
1584
+ const walk = (node, path) => {
1585
+ if (!node || typeof node !== "object")
1586
+ return;
1587
+ for (const [key, val] of Object.entries(node)) {
1588
+ const here = path ? `${path}.${key}` : key;
1589
+ if (typeof val === "string" && val.length >= 4 && SENSITIVE_KEY_RE.test(key)) {
1590
+ scrubWarnings.push(`Possible residual sensitive field at ${here} (${val.length} chars)`);
1591
+ }
1592
+ else if (val && typeof val === "object") {
1593
+ walk(val, here);
1594
+ }
1595
+ }
1596
+ };
1597
+ walk(config, "");
1598
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1599
+ }
1600
+ // Step 6: Update manifest
1601
+ const manifestPath = join(stagingDir, "manifest.json");
1602
+ const manifest = existsSync(manifestPath)
1603
+ ? JSON.parse(readFileSync(manifestPath, "utf-8"))
1604
+ : { schemaVersion: 1 };
1605
+ manifest.type = "export";
1606
+ manifest.has_api_keys = false;
1607
+ manifest.has_sessions = opts.includeSessions ?? false;
1608
+ if (scrubWarnings.length > 0)
1609
+ manifest.warnings = scrubWarnings;
1610
+ // Recalculate checksum
1611
+ const allFiles = collectFiles(stagingDir, stagingDir, []);
1612
+ const nonManifest = allFiles.filter(f => f !== "manifest.json");
1613
+ const checksum = calculateContentChecksum(stagingDir, nonManifest);
1614
+ manifest.checksum = `sha256:${checksum}`;
1615
+ manifest.checksum_scope = "content-excluding-manifest";
1616
+ manifest.stats = { total_files: allFiles.length };
1617
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
1618
+ // Step 7: Repack
1619
+ touchInstanceLock(instanceId);
1620
+ await new Promise((resolve, reject) => {
1621
+ const child = spawnChild("tar", ["-czf", outputPath, "."], {
1622
+ cwd: stagingDir,
1623
+ stdio: ["ignore", "pipe", "pipe"],
1624
+ });
1625
+ let stderr = "";
1626
+ child.stderr?.on("data", (chunk) => { stderr += chunk.toString(); });
1627
+ child.on("close", (code) => {
1628
+ if (code !== 0)
1629
+ reject(new Error(`tar pack failed (exit ${code}): ${stderr.slice(0, 500)}`));
1630
+ else
1631
+ resolve();
1632
+ });
1633
+ child.on("error", reject);
1634
+ });
1635
+ const finalSize = statSync(outputPath).size;
1636
+ return { filename: outputPath.split("/").pop(), filepath: outputPath, size: finalSize, manifest };
1637
+ }
1638
+ finally {
1639
+ stopHeartbeat();
1640
+ if (existsSync(stagingDir))
1641
+ rmSync(stagingDir, { recursive: true, force: true });
1642
+ if (existsSync(tmpBackupPath))
1643
+ rmSync(tmpBackupPath, { force: true });
1644
+ releaseInstanceLock(instanceId);
1645
+ }
1646
+ }
1647
+ // ── Import (three-step) ──
1648
+ /** Step 1: Store uploaded file to tmp. Returns temp_id for subsequent calls. */
1649
+ export async function storeUpload(filePath) {
1650
+ const tempId = randomUUID();
1651
+ const destPath = join(TMP_DIR, `import-${tempId}.tar.gz`);
1652
+ ensureBackupDirs();
1653
+ copyFileSync(filePath, destPath);
1654
+ return { temp_id: tempId };
1655
+ }
1656
+ /** Step 2: Preview an uploaded archive without creating an instance. */
1657
+ export async function previewImport(tempId) {
1658
+ const archivePath = join(TMP_DIR, `import-${tempId}.tar.gz`);
1659
+ if (!existsSync(archivePath))
1660
+ throw new Error("Upload not found or expired");
1661
+ const tmpDir = join(TMP_DIR, `preview-${tempId}`);
1662
+ const warnings = [];
1663
+ try {
1664
+ mkdirSync(tmpDir, { recursive: true });
1665
+ const scanResult = await safeExtract(archivePath, tmpDir);
1666
+ // Resolve archive root — handles both self-pack and CLI wrapper layouts
1667
+ const archiveRoot = resolveArchiveRoot(tmpDir);
1668
+ // Read manifest
1669
+ const manifestPath = join(archiveRoot, "manifest.json");
1670
+ let manifest = null;
1671
+ if (existsSync(manifestPath)) {
1672
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1673
+ }
1674
+ // Detect format — all archives use the official payload/ layout
1675
+ let format = "unknown";
1676
+ if (existsSync(join(archiveRoot, "payload"))) {
1677
+ format = "official";
1678
+ }
1679
+ // Check for missing API keys
1680
+ if (manifest?.has_api_keys === false) {
1681
+ warnings.push("This package does not contain API keys. You will need to configure them.");
1682
+ }
1683
+ // Check sessions
1684
+ if (manifest?.has_sessions === false) {
1685
+ warnings.push("This package does not contain conversation history.");
1686
+ }
1687
+ // Version compatibility
1688
+ if (manifest?.runtimeVersion) {
1689
+ warnings.push(`Created with OpenClaw ${manifest.runtimeVersion}`);
1690
+ }
1691
+ return {
1692
+ manifest,
1693
+ warnings,
1694
+ estimated_size: scanResult.totalSize,
1695
+ format,
1696
+ };
1697
+ }
1698
+ finally {
1699
+ if (existsSync(tmpDir))
1700
+ rmSync(tmpDir, { recursive: true, force: true });
1701
+ }
1702
+ }
1703
+ /** Step 3: Create a new instance from a previously uploaded archive. */
1704
+ export async function importInstance(tempId, opts) {
1705
+ const archivePath = join(TMP_DIR, `import-${tempId}.tar.gz`);
1706
+ if (!existsSync(archivePath))
1707
+ throw new Error("Upload not found or expired");
1708
+ const warnings = [];
1709
+ const tmpDir = join(TMP_DIR, `import-create-${tempId}`);
1710
+ let targetInstalled = false;
1711
+ try {
1712
+ mkdirSync(tmpDir, { recursive: true });
1713
+ await safeExtract(archivePath, tmpDir);
1714
+ // Resolve archive root — handles both self-pack and CLI wrapper layouts
1715
+ const archiveRoot = resolveArchiveRoot(tmpDir);
1716
+ // Locate .openclaw/ via payload/ directory (official format)
1717
+ const payloadDir = join(archiveRoot, "payload");
1718
+ if (!existsSync(payloadDir))
1719
+ throw new Error("Archive missing payload/ directory");
1720
+ const extractedStateDir = findStateDir(payloadDir);
1721
+ if (!extractedStateDir)
1722
+ throw new Error("Could not locate .openclaw directory in archive");
1723
+ // Detect archive scope — for home-scope archives we restore the full
1724
+ // openclaw-home tree (app/, .npm-global/, .codex/, etc.), not just
1725
+ // the .openclaw state dir. Without this, importing a home-scope
1726
+ // backup silently drops the runtime/app tree that motivated the
1727
+ // home-scope in the first place.
1728
+ let archiveScope = "state";
1729
+ let manifest = null;
1730
+ const manifestPath = join(archiveRoot, "manifest.json");
1731
+ if (existsSync(manifestPath)) {
1732
+ try {
1733
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1734
+ if (manifest?.scope === "home")
1735
+ archiveScope = "home";
1736
+ }
1737
+ catch { /* fall back to state */ }
1738
+ }
1739
+ const { openclawHome: newOpenclawHome, stateDir: newStateDir } = await installOpenclawRestoreTarget(opts.id, opts.name, opts.description || "");
1740
+ targetInstalled = true;
1741
+ // Manual lstat walker preserves symlinks as opaque blobs and never
1742
+ // follows dangling targets.
1743
+ if (archiveScope === "home") {
1744
+ // Home scope: copy the portable extracted home tree so app/,
1745
+ // .npm-global/ (upgraded runtime), .codex/, and user files survive.
1746
+ // Still exclude IM credentials — same channel can't bind multiple
1747
+ // instances.
1748
+ const extractedHomeDir = dirname(extractedStateDir);
1749
+ copyTreeLstat(extractedHomeDir, newOpenclawHome, (rel) => {
1750
+ if (!rel)
1751
+ return true;
1752
+ if (isNonPortableHomeEntry(rel))
1753
+ return false;
1754
+ if (rel === ".openclaw/openclaw-weixin")
1755
+ return false;
1756
+ if (rel.startsWith(".openclaw/openclaw-weixin/"))
1757
+ return false;
1758
+ return true;
1759
+ });
1760
+ }
1761
+ else {
1762
+ // State scope: copy only .openclaw/.
1763
+ copyTreeLstat(extractedStateDir, newStateDir, (rel) => {
1764
+ if (!rel)
1765
+ return true;
1766
+ if (rel.startsWith("openclaw-weixin"))
1767
+ return false;
1768
+ return true;
1769
+ });
1770
+ }
1771
+ // Rewrite absolute paths in session data and symlinks from the source
1772
+ // instance to the new instance (fixes ENOENT crash on Docker import
1773
+ // and silent cross-instance data leakage on local import).
1774
+ const oldHome = detectOldHome(manifest, payloadDir, extractedStateDir);
1775
+ if (oldHome) {
1776
+ rewriteInstancePaths(archiveScope === "home" ? newOpenclawHome : newStateDir, oldHome, newOpenclawHome, warnings);
1777
+ }
1778
+ // Scrub channel credentials from copied openclaw.json (new instance should not inherit IM bindings)
1779
+ await scrubNewInstanceConfig(join(newStateDir, "openclaw.json"));
1780
+ // Regenerate gateway auth token (scrub cleared it to prevent credential leakage,
1781
+ // but the gateway requires a valid token to accept requests)
1782
+ regenerateGatewayAuthToken(join(newStateDir, "openclaw.json"));
1783
+ // DO NOT copy model.env, provider.env, instance.json
1784
+ // Bootstrap proxy
1785
+ try {
1786
+ const { bootstrapInstanceProxy } = await import("../llm-proxy/instance-proxy.js");
1787
+ await bootstrapInstanceProxy(opts.id);
1788
+ }
1789
+ catch (e) {
1790
+ warnings.push(`Proxy bootstrap: ${e.message}`);
1791
+ }
1792
+ // Check if a default provider can be injected
1793
+ try {
1794
+ const { getDefaultLlmProvider } = await import("../llm-proxy/providers.js");
1795
+ if (getDefaultLlmProvider()?.apiKey) {
1796
+ warnings.push("Default provider detected. You may configure API Key from the config page.");
1797
+ }
1798
+ }
1799
+ catch { /* ignore */ }
1800
+ warnings.push("Instance created. Configure API Key before starting.");
1801
+ // Clean up the upload file
1802
+ rmSync(archivePath, { force: true });
1803
+ return { ok: true, instance_id: opts.id, warnings };
1804
+ }
1805
+ catch (error) {
1806
+ if (targetInstalled) {
1807
+ try {
1808
+ await rollbackOpenclawRestoreTarget(opts.id);
1809
+ }
1810
+ catch (rollbackError) {
1811
+ console.error(`[backup] failed to rollback partial import target ${opts.id}: ${rollbackError?.message ?? rollbackError}`);
1812
+ }
1813
+ }
1814
+ throw error;
1815
+ }
1816
+ finally {
1817
+ if (existsSync(tmpDir))
1818
+ rmSync(tmpDir, { recursive: true, force: true });
1819
+ }
1820
+ }
1821
+ /**
1822
+ * Scrub proxy identity and IM bindings from a copied openclaw.json before it
1823
+ * is handed to a brand-new instance (importInstance / createFromBackup).
1824
+ *
1825
+ * Matches the domain-clone behavior in `createInstance` so both paths produce
1826
+ * identical "clean slate" configs: no inherited proxy provider, no inherited
1827
+ * channel bindings, no dangling IM plugin entries. This avoids the hazard
1828
+ * where imported configs kept `channels.*.enabled = true` with scrubbed
1829
+ * credentials, leaving the plugin loader to boot a half-configured binding.
1830
+ *
1831
+ * Only the canonical `.openclaw/openclaw.json` is written. Old archive
1832
+ * layouts may contain an outer-home mirror, but import filters it out rather
1833
+ * than creating a second config source in the new instance.
1834
+ */
1835
+ async function scrubNewInstanceConfig(configPath) {
1836
+ // Integration dispatch keeps framework code independent of OpenClaw's
1837
+ // private channel/plugin config structure.
1838
+ const { stripImBindings } = await import("../integrations/index.js");
1839
+ const scrubOne = (path) => {
1840
+ if (!existsSync(path))
1841
+ return;
1842
+ try {
1843
+ const config = JSON.parse(readFileSync(path, "utf-8"));
1844
+ // Remove proxy providers (will be regenerated by bootstrapInstanceProxy)
1845
+ const providers = config?.models?.providers;
1846
+ if (providers) {
1847
+ for (const [pid, prov] of Object.entries(providers)) {
1848
+ if (typeof prov?.baseUrl === "string" && prov.baseUrl.includes("/proxy/")) {
1849
+ delete providers[pid];
1850
+ }
1851
+ }
1852
+ }
1853
+ // Remove proxy model reference from agent defaults
1854
+ const defaultModel = config?.agents?.defaults?.model;
1855
+ if (typeof defaultModel === "string" && (defaultModel.startsWith("jsproxy/") || defaultModel.startsWith("js-"))) {
1856
+ delete config.agents.defaults.model;
1857
+ }
1858
+ // Unified IM scrub: delete channels block + matching plugin entries
1859
+ stripImBindings(config);
1860
+ writeFileSync(path, JSON.stringify(config, null, 2));
1861
+ }
1862
+ catch { /* best effort */ }
1863
+ };
1864
+ scrubOne(configPath);
1865
+ }
1866
+ /**
1867
+ * Regenerate the `gateway.auth.token` in an openclaw.json that was scrubbed
1868
+ * by {@link scrubNewInstanceConfig}. The scrub intentionally clears the old
1869
+ * token (to avoid leaking the source instance's credential), but for newly
1870
+ * created instances the gateway needs a valid token — otherwise it rejects
1871
+ * all requests with "unauthorized: gateway token missing".
1872
+ *
1873
+ * Writes only the canonical config path.
1874
+ */
1875
+ function regenerateGatewayAuthToken(configPath) {
1876
+ const regenOne = (path) => {
1877
+ if (!existsSync(path))
1878
+ return;
1879
+ try {
1880
+ const config = JSON.parse(readFileSync(path, "utf-8"));
1881
+ if (!config.gateway)
1882
+ config.gateway = {};
1883
+ if (!config.gateway.auth)
1884
+ config.gateway.auth = { mode: "token" };
1885
+ config.gateway.auth.token = randomBytes(24).toString("hex");
1886
+ writeFileSync(path, JSON.stringify(config, null, 2));
1887
+ }
1888
+ catch { /* best effort — instance will still start, user can set token from UI */ }
1889
+ };
1890
+ regenOne(configPath);
1891
+ }
1892
+ /**
1893
+ * Resolve the effective "archive root" inside an extracted tarball.
1894
+ *
1895
+ * Returns the directory where `manifest.json` and `payload/` live. Two
1896
+ * archive layouts are supported:
1897
+ *
1898
+ * 1. **JishuShell self-pack** (`selfPackOfficialFormat`): writes
1899
+ * `manifest.json` and `payload/` directly at the archive root, so the
1900
+ * effective root is the extraction dir itself.
1901
+ *
1902
+ * 2. **Official OpenClaw `backup create`**: wraps everything inside a
1903
+ * single top-level directory named after the archive basename, e.g.
1904
+ * `2026-04-10T03-06-23.257Z-openclaw-backup/`. The wrapper name is
1905
+ * also advertised via `manifest.archiveRoot`.
1906
+ *
1907
+ * Detection strategy (cheapest first):
1908
+ * a. If `<tmpDir>/manifest.json` exists → self-pack → return tmpDir.
1909
+ * b. Else scan top-level entries; if exactly one is a directory containing
1910
+ * `manifest.json`, that's the official CLI wrapper → return it.
1911
+ * c. Fall back to tmpDir; the caller will throw its own missing-manifest
1912
+ * error with a meaningful path.
1913
+ *
1914
+ * Without this helper, `restoreInstance` / `importInstance` / `verifyArchive`
1915
+ * would throw "Archive missing manifest.json" on every state-scope archive
1916
+ * produced by the official CLI.
1917
+ */
1918
+ export function resolveArchiveRoot(tmpDir) {
1919
+ if (existsSync(join(tmpDir, "manifest.json")))
1920
+ return tmpDir;
1921
+ try {
1922
+ for (const entry of readdirSync(tmpDir)) {
1923
+ const candidate = join(tmpDir, entry);
1924
+ try {
1925
+ if (statSync(candidate).isDirectory() && existsSync(join(candidate, "manifest.json"))) {
1926
+ return candidate;
1927
+ }
1928
+ }
1929
+ catch { /* skip inaccessible */ }
1930
+ }
1931
+ }
1932
+ catch { /* skip */ }
1933
+ return tmpDir;
1934
+ }
1935
+ /**
1936
+ * Locate the OpenClaw state directory inside an extracted archive.
1937
+ *
1938
+ * Finds a directory **literally named `.openclaw`** that contains an
1939
+ * `openclaw.json` file. Uses BFS so the shallowest match wins, which guards
1940
+ * against nested copies inside extensions/ or workspace/.
1941
+ *
1942
+ * This is intentionally stricter than "any directory containing
1943
+ * openclaw.json". Historical archives may contain an outer-home config
1944
+ * mirror, so a naive search can select the wrong level for home-scope
1945
+ * archives.
1946
+ */
1947
+ export function findStateDir(rootDir) {
1948
+ const queue = [rootDir];
1949
+ while (queue.length > 0) {
1950
+ const current = queue.shift();
1951
+ try {
1952
+ for (const entry of readdirSync(current)) {
1953
+ const fullPath = join(current, entry);
1954
+ try {
1955
+ if (!statSync(fullPath).isDirectory())
1956
+ continue;
1957
+ if (entry === ".openclaw" && existsSync(join(fullPath, "openclaw.json"))) {
1958
+ return fullPath;
1959
+ }
1960
+ queue.push(fullPath);
1961
+ }
1962
+ catch { /* skip inaccessible entries */ }
1963
+ }
1964
+ }
1965
+ catch { /* skip inaccessible dirs */ }
1966
+ }
1967
+ return null;
1968
+ }
1969
+ // ── Auto-backup scheduler ──
1970
+ const autoBackupTimers = new Map();
1971
+ let autoBackupShuttingDown = false;
1972
+ /** Get auto-backup config from instance.json */
1973
+ export async function getAutoBackupConfig(instanceId) {
1974
+ try {
1975
+ const { getInstance } = await import("../app-common/service.js");
1976
+ const meta = getInstance(instanceId);
1977
+ return meta?.auto_backup || null;
1978
+ }
1979
+ catch {
1980
+ return null;
1981
+ }
1982
+ }
1983
+ /** Update auto-backup status in instance.json */
1984
+ async function updateAutoBackupStatus(instanceId, patch) {
1985
+ const { updateInstanceMeta, getInstance } = await import("../app-common/service.js");
1986
+ const meta = getInstance(instanceId);
1987
+ const current = meta?.auto_backup || {};
1988
+ updateInstanceMeta(instanceId, { auto_backup: { ...current, ...patch } });
1989
+ }
1990
+ /** Check if instance has changed since last backup (mtime-based) */
1991
+ async function hasChangedSince(instanceId, sinceMs) {
1992
+ try {
1993
+ const { getInstance } = await import("../app-common/service.js");
1994
+ const meta = getInstance(instanceId);
1995
+ const openclawHome = integrationHomeFromMetadata(meta, instanceId);
1996
+ const stateDir = join(openclawHome, ".openclaw");
1997
+ if (!existsSync(stateDir))
1998
+ return false;
1999
+ return getMaxMtime(stateDir, 0) > sinceMs;
2000
+ }
2001
+ catch {
2002
+ return true; // On error, assume changed (safe default)
2003
+ }
2004
+ }
2005
+ /** Recursively find max mtime in a directory, skipping cache dirs */
2006
+ function getMaxMtime(dir, currentMax) {
2007
+ const skipDirs = new Set([".npm", ".cache", ".node_compile_cache", ".npm-global"]);
2008
+ try {
2009
+ for (const entry of readdirSync(dir)) {
2010
+ if (skipDirs.has(entry))
2011
+ continue;
2012
+ const fullPath = join(dir, entry);
2013
+ try {
2014
+ const stat = statSync(fullPath);
2015
+ if (stat.mtimeMs > currentMax)
2016
+ currentMax = stat.mtimeMs;
2017
+ if (stat.isDirectory()) {
2018
+ currentMax = getMaxMtime(fullPath, currentMax);
2019
+ }
2020
+ }
2021
+ catch { /* skip inaccessible */ }
2022
+ }
2023
+ }
2024
+ catch { /* skip */ }
2025
+ return currentMax;
2026
+ }
2027
+ /** Clean old auto-backup files, keeping only the newest `keepCount` */
2028
+ export async function cleanOldAutoBackups(instanceId, keepCount) {
2029
+ const backupDir = join(BACKUPS_DIR, instanceId);
2030
+ if (!existsSync(backupDir))
2031
+ return 0;
2032
+ const autoBackups = readdirSync(backupDir)
2033
+ .filter(f => f.startsWith("auto-backup-") && f.endsWith(".tar.gz"))
2034
+ .map(f => ({ name: f, mtime: statSync(join(backupDir, f)).mtimeMs }))
2035
+ .sort((a, b) => b.mtime - a.mtime); // newest first
2036
+ let cleaned = 0;
2037
+ for (let i = keepCount; i < autoBackups.length; i++) {
2038
+ rmSync(join(backupDir, autoBackups[i].name), { force: true });
2039
+ cleaned++;
2040
+ }
2041
+ return cleaned;
2042
+ }
2043
+ /** Check available disk space (returns bytes) */
2044
+ function getAvailableDiskSpace() {
2045
+ try {
2046
+ const output = execFileSync("df", ["-B1", "--output=avail", BACKUPS_DIR], {
2047
+ encoding: "utf-8",
2048
+ timeout: 5000,
2049
+ });
2050
+ const lines = output.trim().split("\n");
2051
+ return parseInt(lines[lines.length - 1].trim(), 10) || 0;
2052
+ }
2053
+ catch {
2054
+ try {
2055
+ // macOS fallback
2056
+ const output = execFileSync("df", ["-k", BACKUPS_DIR], { encoding: "utf-8", timeout: 5000 });
2057
+ const lines = output.trim().split("\n");
2058
+ const parts = lines[lines.length - 1].split(/\s+/);
2059
+ return (parseInt(parts[3], 10) || 0) * 1024; // Convert KB to bytes
2060
+ }
2061
+ catch {
2062
+ return Infinity; // Can't check, don't block
2063
+ }
2064
+ }
2065
+ }
2066
+ const MIN_DISK_BYTES = 2 * 1024 * 1024 * 1024; // 2GB
2067
+ const jobQueue = [];
2068
+ const jobHistory = [];
2069
+ const MAX_HISTORY = 20;
2070
+ let currentJob = null;
2071
+ let queueProcessing = false;
2072
+ // Map of job ID to resolve/reject for callers that want to await completion
2073
+ const jobWaiters = new Map();
2074
+ // Store execute functions for queued jobs
2075
+ const jobExecutors = new Map();
2076
+ /** Enqueue a backup operation. Returns the job immediately (non-blocking). */
2077
+ export function enqueueJob(instanceId, operation, executeFn) {
2078
+ const job = {
2079
+ id: randomUUID(),
2080
+ instanceId,
2081
+ operation,
2082
+ status: "queued",
2083
+ createdAt: Date.now(),
2084
+ };
2085
+ jobQueue.push(job);
2086
+ // Store the execute function for when this job's turn comes
2087
+ jobExecutors.set(job.id, executeFn);
2088
+ // Start processing if not already running
2089
+ if (!queueProcessing) {
2090
+ processQueue();
2091
+ }
2092
+ return job;
2093
+ }
2094
+ /** Enqueue and wait for completion. Returns the completed job. */
2095
+ export function enqueueJobAndWait(instanceId, operation, executeFn) {
2096
+ const job = enqueueJob(instanceId, operation, executeFn);
2097
+ return new Promise((resolve, reject) => {
2098
+ jobWaiters.set(job.id, { resolve, reject });
2099
+ });
2100
+ }
2101
+ async function processQueue() {
2102
+ if (queueProcessing)
2103
+ return;
2104
+ queueProcessing = true;
2105
+ while (jobQueue.length > 0) {
2106
+ const job = jobQueue.shift();
2107
+ currentJob = job;
2108
+ job.status = "running";
2109
+ job.startedAt = Date.now();
2110
+ // Get the executor function
2111
+ const executeFn = jobExecutors.get(job.id);
2112
+ jobExecutors.delete(job.id);
2113
+ if (!executeFn) {
2114
+ job.status = "failed";
2115
+ job.error = "No executor function found";
2116
+ job.completedAt = Date.now();
2117
+ moveToHistory(job);
2118
+ continue;
2119
+ }
2120
+ try {
2121
+ const result = await executeFn(job);
2122
+ job.status = "completed";
2123
+ job.result = result;
2124
+ }
2125
+ catch (e) {
2126
+ job.status = "failed";
2127
+ job.error = e.message || "Unknown error";
2128
+ }
2129
+ finally {
2130
+ job.completedAt = Date.now();
2131
+ currentJob = null;
2132
+ moveToHistory(job);
2133
+ // Notify waiter if any
2134
+ const waiter = jobWaiters.get(job.id);
2135
+ if (waiter) {
2136
+ jobWaiters.delete(job.id);
2137
+ if (job.status === "completed") {
2138
+ waiter.resolve(job);
2139
+ }
2140
+ else {
2141
+ waiter.reject(new Error(job.error || "Job failed"));
2142
+ }
2143
+ }
2144
+ }
2145
+ }
2146
+ queueProcessing = false;
2147
+ }
2148
+ function moveToHistory(job) {
2149
+ jobHistory.unshift(job);
2150
+ if (jobHistory.length > MAX_HISTORY)
2151
+ jobHistory.pop();
2152
+ }
2153
+ /** Update progress message for the currently running job */
2154
+ export function updateJobProgress(jobId, progress) {
2155
+ if (currentJob?.id === jobId) {
2156
+ currentJob.progress = progress;
2157
+ }
2158
+ }
2159
+ /** Get full queue status */
2160
+ export function getQueueStatus() {
2161
+ return {
2162
+ current: currentJob,
2163
+ queued: [...jobQueue],
2164
+ recent: jobHistory.slice(0, 10),
2165
+ };
2166
+ }
2167
+ /** Get a specific job by ID (checks current, queue, and history) */
2168
+ export function getJob(jobId) {
2169
+ if (currentJob?.id === jobId)
2170
+ return currentJob;
2171
+ const queued = jobQueue.find(j => j.id === jobId);
2172
+ if (queued)
2173
+ return queued;
2174
+ const historic = jobHistory.find(j => j.id === jobId);
2175
+ return historic || null;
2176
+ }
2177
+ /** Cancel a queued job (cannot cancel running jobs) */
2178
+ export function cancelJob(jobId) {
2179
+ const idx = jobQueue.findIndex(j => j.id === jobId);
2180
+ if (idx === -1)
2181
+ return false;
2182
+ const job = jobQueue.splice(idx, 1)[0];
2183
+ job.status = "failed";
2184
+ job.error = "Cancelled";
2185
+ job.completedAt = Date.now();
2186
+ moveToHistory(job);
2187
+ jobExecutors.delete(jobId);
2188
+ const waiter = jobWaiters.get(jobId);
2189
+ if (waiter) {
2190
+ jobWaiters.delete(jobId);
2191
+ waiter.reject(new Error("Job cancelled"));
2192
+ }
2193
+ return true;
2194
+ }
2195
+ /** Schedule auto-backup for an instance */
2196
+ export function scheduleAutoBackup(instanceId, config) {
2197
+ cancelAutoBackup(instanceId);
2198
+ if (autoBackupShuttingDown || !config.enabled)
2199
+ return;
2200
+ const lastAt = config.last_backup_at ? new Date(config.last_backup_at).getTime() : 0;
2201
+ const intervalMs = config.interval_hours * 3600_000;
2202
+ const delay = Math.max(0, intervalMs - (Date.now() - lastAt));
2203
+ const timer = setTimeout(async () => {
2204
+ if (autoBackupShuttingDown)
2205
+ return;
2206
+ // Always work against the freshest persisted config so we don't race
2207
+ // against user edits or updateAutoBackupStatus writes. The `config` arg
2208
+ // is only used for scheduling delay above.
2209
+ const liveConfig = (await getAutoBackupConfig(instanceId)) || config;
2210
+ const liveLastAt = liveConfig.last_backup_at
2211
+ ? new Date(liveConfig.last_backup_at).getTime()
2212
+ : 0;
2213
+ const keepCount = liveConfig.keep_count || 7;
2214
+ try {
2215
+ // Check disk space
2216
+ const available = getAvailableDiskSpace();
2217
+ if (available < MIN_DISK_BYTES) {
2218
+ await updateAutoBackupStatus(instanceId, {
2219
+ last_backup_ok: false,
2220
+ consecutive_failures: (liveConfig.consecutive_failures || 0) + 1,
2221
+ warnings: ["Auto-backup paused: disk space below 2GB"],
2222
+ });
2223
+ console.warn(`[auto-backup] ${instanceId}: paused, disk below 2GB`);
2224
+ // Still reschedule to check again later
2225
+ const fresh = await getAutoBackupConfig(instanceId);
2226
+ if (!autoBackupShuttingDown && fresh?.enabled)
2227
+ scheduleAutoBackup(instanceId, fresh);
2228
+ return;
2229
+ }
2230
+ // Check if changed since last backup
2231
+ if (await hasChangedSince(instanceId, liveLastAt)) {
2232
+ // Enqueue to avoid parallel I/O contention on RPi SD card
2233
+ await enqueueJobAndWait(instanceId, "auto-backup", async (job) => {
2234
+ updateJobProgress(job.id, "Backing up...");
2235
+ const result = await backupInstance(instanceId, { type: "auto-backup" });
2236
+ updateJobProgress(job.id, "Cleaning old backups...");
2237
+ await cleanOldAutoBackups(instanceId, keepCount);
2238
+ return result;
2239
+ });
2240
+ await updateAutoBackupStatus(instanceId, {
2241
+ last_backup_at: new Date().toISOString(),
2242
+ last_backup_ok: true,
2243
+ consecutive_failures: 0,
2244
+ warnings: [],
2245
+ });
2246
+ console.log(`[auto-backup] ${instanceId}: completed`);
2247
+ }
2248
+ else {
2249
+ console.log(`[auto-backup] ${instanceId}: skipped (no changes)`);
2250
+ }
2251
+ }
2252
+ catch (e) {
2253
+ const failures = (liveConfig.consecutive_failures || 0) + 1;
2254
+ const warnings = failures >= 3
2255
+ ? [`Auto-backup failed ${failures} times: ${e.message}`]
2256
+ : [];
2257
+ await updateAutoBackupStatus(instanceId, {
2258
+ last_backup_ok: false,
2259
+ consecutive_failures: failures,
2260
+ warnings,
2261
+ });
2262
+ console.error(`[auto-backup] ${instanceId} failed (${failures}x):`, e.message);
2263
+ }
2264
+ // Reschedule (re-read config in case user changed it)
2265
+ const freshConfig = await getAutoBackupConfig(instanceId);
2266
+ if (!autoBackupShuttingDown && freshConfig?.enabled) {
2267
+ scheduleAutoBackup(instanceId, freshConfig);
2268
+ }
2269
+ }, delay);
2270
+ timer.unref(); // Don't prevent process exit
2271
+ autoBackupTimers.set(instanceId, timer);
2272
+ }
2273
+ /** Cancel auto-backup for an instance */
2274
+ export function cancelAutoBackup(instanceId) {
2275
+ const t = autoBackupTimers.get(instanceId);
2276
+ if (t) {
2277
+ clearTimeout(t);
2278
+ autoBackupTimers.delete(instanceId);
2279
+ }
2280
+ }
2281
+ /** Stop every per-instance auto-backup timer during Core shutdown. */
2282
+ export function shutdownAutoBackup() {
2283
+ autoBackupShuttingDown = true;
2284
+ for (const timer of autoBackupTimers.values()) {
2285
+ clearTimeout(timer);
2286
+ }
2287
+ autoBackupTimers.clear();
2288
+ }
2289
+ /** Initialize auto-backup for all instances (call on server startup) */
2290
+ export async function initAutoBackup() {
2291
+ autoBackupShuttingDown = false;
2292
+ try {
2293
+ const { listInstances } = await import("../app-common/service.js");
2294
+ const instances = listInstances();
2295
+ let jitterIndex = 0;
2296
+ for (const inst of instances) {
2297
+ const config = inst.auto_backup;
2298
+ if (config?.enabled) {
2299
+ // Stagger startup: if multiple instances are due at the same time,
2300
+ // add incremental jitter (2 min per instance) to avoid I/O contention
2301
+ if (!config.last_backup_at) {
2302
+ const jitterMs = jitterIndex * 2 * 60_000; // 2 min apart
2303
+ const jitteredConfig = { ...config, last_backup_at: new Date(Date.now() - (config.interval_hours * 3600_000) + jitterMs).toISOString() };
2304
+ scheduleAutoBackup(inst.id, jitteredConfig);
2305
+ }
2306
+ else {
2307
+ scheduleAutoBackup(inst.id, config);
2308
+ }
2309
+ jitterIndex++;
2310
+ console.log(`[auto-backup] ${inst.id}: scheduled (interval: ${config.interval_hours}h, keep: ${config.keep_count})`);
2311
+ }
2312
+ }
2313
+ }
2314
+ catch (e) {
2315
+ console.error("[auto-backup] Init failed:", e.message);
2316
+ }
2317
+ }
2318
+ //# sourceMappingURL=backup-manager.js.map