devchain-cli 0.24.1 → 0.24.2

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 (343) hide show
  1. package/dist/build-info.json +2 -2
  2. package/dist/cli.js +40 -10
  3. package/dist/host-install/devchain-host-bootstrap.tgz +0 -0
  4. package/dist/host-install/pins.json +31 -2
  5. package/dist/lib/exclusive-run.js +51 -3
  6. package/dist/lib/git-owner.js +284 -0
  7. package/dist/lib/host-install.js +19 -151
  8. package/dist/lib/local-api.js +175 -0
  9. package/dist/server/common/config/env.config.d.ts +3 -0
  10. package/dist/server/common/config/env.config.js +1 -0
  11. package/dist/server/common/config/env.config.js.map +1 -1
  12. package/dist/server/common/config/host-image.json +3 -3
  13. package/dist/server/common/constants/syncthing-markers.d.ts +1 -0
  14. package/dist/server/common/constants/syncthing-markers.js +6 -1
  15. package/dist/server/common/constants/syncthing-markers.js.map +1 -1
  16. package/dist/server/common/filesystem/vm-home-path.d.ts +11 -0
  17. package/dist/server/common/filesystem/vm-home-path.js +48 -0
  18. package/dist/server/common/filesystem/vm-home-path.js.map +1 -0
  19. package/dist/server/common/process-ids-env.d.ts +1 -0
  20. package/dist/server/common/process-ids-env.js +13 -0
  21. package/dist/server/common/process-ids-env.js.map +1 -0
  22. package/dist/server/common/test/api-test-app.d.ts +12 -0
  23. package/dist/server/common/test/api-test-app.js +76 -0
  24. package/dist/server/common/test/api-test-app.js.map +1 -0
  25. package/dist/server/common/test/app-bootstrap.helper.js +8 -1
  26. package/dist/server/common/test/app-bootstrap.helper.js.map +1 -1
  27. package/dist/server/common/test/fake-bootstrap.server.js +3 -2
  28. package/dist/server/common/test/fake-bootstrap.server.js.map +1 -1
  29. package/dist/server/common/test/fake-docker-engine.server.d.ts +17 -6
  30. package/dist/server/common/test/fake-docker-engine.server.js +77 -33
  31. package/dist/server/common/test/fake-docker-engine.server.js.map +1 -1
  32. package/dist/server/common/test/fake-proxmox.server.js +4 -4
  33. package/dist/server/common/test/fake-ssh.server.js +6 -6
  34. package/dist/server/common/test/fake-ssh.server.js.map +1 -1
  35. package/dist/server/common/test/test-database.helper.d.ts +6 -0
  36. package/dist/server/common/test/test-database.helper.js +29 -0
  37. package/dist/server/common/test/test-database.helper.js.map +1 -0
  38. package/dist/server/modules/core/controllers/docker-archive.js +1 -1
  39. package/dist/server/modules/core/controllers/docker-archive.js.map +1 -1
  40. package/dist/server/modules/core/controllers/docker-engine.client.d.ts +2 -1
  41. package/dist/server/modules/core/controllers/docker-engine.client.js +8 -4
  42. package/dist/server/modules/core/controllers/docker-engine.client.js.map +1 -1
  43. package/dist/server/modules/core/controllers/docker-image-metadata.d.ts +248 -0
  44. package/dist/server/modules/core/controllers/docker-image-metadata.js +161 -0
  45. package/dist/server/modules/core/controllers/docker-image-metadata.js.map +1 -0
  46. package/dist/server/modules/core/controllers/docker-settings.d.ts +13 -2
  47. package/dist/server/modules/core/controllers/docker-settings.js +49 -11
  48. package/dist/server/modules/core/controllers/docker-settings.js.map +1 -1
  49. package/dist/server/modules/core/controllers/host-user-identity.d.ts +34 -0
  50. package/dist/server/modules/core/controllers/host-user-identity.js +23 -0
  51. package/dist/server/modules/core/controllers/host-user-identity.js.map +1 -0
  52. package/dist/server/modules/core/controllers/runtime.controller.d.ts +11 -4
  53. package/dist/server/modules/core/controllers/runtime.controller.js +2 -0
  54. package/dist/server/modules/core/controllers/runtime.controller.js.map +1 -1
  55. package/dist/server/modules/file-sync/build-exclusion-suggestions.d.ts +15 -0
  56. package/dist/server/modules/file-sync/build-exclusion-suggestions.js +402 -0
  57. package/dist/server/modules/file-sync/build-exclusion-suggestions.js.map +1 -0
  58. package/dist/server/modules/file-sync/file-sync-auto-fix.dto.d.ts +102 -0
  59. package/dist/server/modules/file-sync/file-sync-auto-fix.dto.js +22 -0
  60. package/dist/server/modules/file-sync/file-sync-auto-fix.dto.js.map +1 -0
  61. package/dist/server/modules/file-sync/file-sync-auto-fix.store.d.ts +12 -0
  62. package/dist/server/modules/file-sync/file-sync-auto-fix.store.js +73 -0
  63. package/dist/server/modules/file-sync/file-sync-auto-fix.store.js.map +1 -0
  64. package/dist/server/modules/file-sync/file-sync-conflicts.d.ts +3 -0
  65. package/dist/server/modules/file-sync/file-sync-conflicts.js +191 -0
  66. package/dist/server/modules/file-sync/file-sync-conflicts.js.map +1 -0
  67. package/dist/server/modules/file-sync/file-sync-ignores.store.d.ts +5 -1
  68. package/dist/server/modules/file-sync/file-sync-ignores.store.js +39 -8
  69. package/dist/server/modules/file-sync/file-sync-ignores.store.js.map +1 -1
  70. package/dist/server/modules/file-sync/file-sync.controller.d.ts +1 -3
  71. package/dist/server/modules/file-sync/file-sync.controller.js +5 -14
  72. package/dist/server/modules/file-sync/file-sync.controller.js.map +1 -1
  73. package/dist/server/modules/file-sync/file-sync.dto.d.ts +220 -1
  74. package/dist/server/modules/file-sync/file-sync.dto.js +70 -4
  75. package/dist/server/modules/file-sync/file-sync.dto.js.map +1 -1
  76. package/dist/server/modules/file-sync/file-sync.module.js +16 -1
  77. package/dist/server/modules/file-sync/file-sync.module.js.map +1 -1
  78. package/dist/server/modules/file-sync/file-sync.service.d.ts +18 -4
  79. package/dist/server/modules/file-sync/file-sync.service.js +166 -34
  80. package/dist/server/modules/file-sync/file-sync.service.js.map +1 -1
  81. package/dist/server/modules/file-sync/git-guard.dto.d.ts +61 -0
  82. package/dist/server/modules/file-sync/git-guard.dto.js +26 -0
  83. package/dist/server/modules/file-sync/git-guard.dto.js.map +1 -0
  84. package/dist/server/modules/file-sync/home-git-guard.service.d.ts +4 -2
  85. package/dist/server/modules/file-sync/home-git-guard.service.js +50 -25
  86. package/dist/server/modules/file-sync/home-git-guard.service.js.map +1 -1
  87. package/dist/server/modules/file-sync/host-git-guard.controller.d.ts +10 -0
  88. package/dist/server/modules/file-sync/host-git-guard.controller.js +64 -0
  89. package/dist/server/modules/file-sync/host-git-guard.controller.js.map +1 -0
  90. package/dist/server/modules/file-sync/host-git-index.controller.d.ts +9 -0
  91. package/dist/server/modules/file-sync/host-git-index.controller.js +67 -0
  92. package/dist/server/modules/file-sync/host-git-index.controller.js.map +1 -0
  93. package/dist/server/modules/file-sync/host-sync.controller.d.ts +15 -2
  94. package/dist/server/modules/file-sync/host-sync.controller.js +122 -14
  95. package/dist/server/modules/file-sync/host-sync.controller.js.map +1 -1
  96. package/dist/server/modules/file-sync/ignore-pattern-matcher.d.ts +30 -0
  97. package/dist/server/modules/file-sync/ignore-pattern-matcher.js +183 -0
  98. package/dist/server/modules/file-sync/ignore-pattern-matcher.js.map +1 -0
  99. package/dist/server/modules/file-sync/project-repository.d.ts +2 -0
  100. package/dist/server/modules/file-sync/project-repository.js +20 -0
  101. package/dist/server/modules/file-sync/project-repository.js.map +1 -0
  102. package/dist/server/modules/file-sync/sync-chown.dto.d.ts +99 -0
  103. package/dist/server/modules/file-sync/sync-chown.dto.js +46 -0
  104. package/dist/server/modules/file-sync/sync-chown.dto.js.map +1 -0
  105. package/dist/server/modules/file-sync/sync-chown.service.d.ts +12 -0
  106. package/dist/server/modules/file-sync/sync-chown.service.js +230 -0
  107. package/dist/server/modules/file-sync/sync-chown.service.js.map +1 -0
  108. package/dist/server/modules/file-sync/sync-path-inspection.dto.d.ts +626 -0
  109. package/dist/server/modules/file-sync/sync-path-inspection.dto.js +108 -0
  110. package/dist/server/modules/file-sync/sync-path-inspection.dto.js.map +1 -0
  111. package/dist/server/modules/file-sync/sync-path-inspector.d.ts +12 -0
  112. package/dist/server/modules/file-sync/sync-path-inspector.js +413 -0
  113. package/dist/server/modules/file-sync/sync-path-inspector.js.map +1 -0
  114. package/dist/server/modules/file-sync/testing/fake-file-sync.service.d.ts +27 -4
  115. package/dist/server/modules/file-sync/testing/fake-file-sync.service.js +66 -4
  116. package/dist/server/modules/file-sync/testing/fake-file-sync.service.js.map +1 -1
  117. package/dist/server/modules/file-sync/translate-git-ignore-rule.d.ts +3 -0
  118. package/dist/server/modules/file-sync/translate-git-ignore-rule.js +30 -0
  119. package/dist/server/modules/file-sync/translate-git-ignore-rule.js.map +1 -0
  120. package/dist/server/modules/mcp/services/handlers/skill-tools.js +2 -2
  121. package/dist/server/modules/mcp/services/handlers/skill-tools.js.map +1 -1
  122. package/dist/server/modules/remotes/connect-choices.controller.d.ts +9 -0
  123. package/dist/server/modules/remotes/connect-choices.controller.js +54 -0
  124. package/dist/server/modules/remotes/connect-choices.controller.js.map +1 -0
  125. package/dist/server/modules/remotes/connect-choices.dto.d.ts +52 -0
  126. package/dist/server/modules/remotes/connect-choices.dto.js +19 -0
  127. package/dist/server/modules/remotes/connect-choices.dto.js.map +1 -0
  128. package/dist/server/modules/remotes/connect-choices.store.d.ts +12 -0
  129. package/dist/server/modules/remotes/connect-choices.store.js +95 -0
  130. package/dist/server/modules/remotes/connect-choices.store.js.map +1 -0
  131. package/dist/server/modules/remotes/controllers/remotes.controller.d.ts +3 -0
  132. package/dist/server/modules/remotes/controllers/remotes.controller.js +9 -1
  133. package/dist/server/modules/remotes/controllers/remotes.controller.js.map +1 -1
  134. package/dist/server/modules/remotes/docker/docker-copy-back.js +40 -16
  135. package/dist/server/modules/remotes/docker/docker-copy-back.js.map +1 -1
  136. package/dist/server/modules/remotes/docker/docker-handoff.d.ts +5 -3
  137. package/dist/server/modules/remotes/docker/docker-handoff.js +146 -42
  138. package/dist/server/modules/remotes/docker/docker-handoff.js.map +1 -1
  139. package/dist/server/modules/remotes/docker/docker-handoff.store.d.ts +3 -0
  140. package/dist/server/modules/remotes/docker/docker-handoff.store.js.map +1 -1
  141. package/dist/server/modules/remotes/docker/docker-plan-fit.d.ts +4 -1
  142. package/dist/server/modules/remotes/docker/docker-plan-fit.js +19 -6
  143. package/dist/server/modules/remotes/docker/docker-plan-fit.js.map +1 -1
  144. package/dist/server/modules/remotes/docker/docker-plan-policy.d.ts +6 -2
  145. package/dist/server/modules/remotes/docker/docker-plan-policy.js +191 -38
  146. package/dist/server/modules/remotes/docker/docker-plan-policy.js.map +1 -1
  147. package/dist/server/modules/remotes/docker/docker-plan-source.service.d.ts +52 -8
  148. package/dist/server/modules/remotes/docker/docker-plan-source.service.js +273 -50
  149. package/dist/server/modules/remotes/docker/docker-plan-source.service.js.map +1 -1
  150. package/dist/server/modules/remotes/docker/docker-plan.controller.d.ts +2 -1
  151. package/dist/server/modules/remotes/docker/docker-plan.controller.js +14 -0
  152. package/dist/server/modules/remotes/docker/docker-plan.controller.js.map +1 -1
  153. package/dist/server/modules/remotes/docker/docker-plan.dto.d.ts +46 -1
  154. package/dist/server/modules/remotes/docker/docker-plan.dto.js +1 -0
  155. package/dist/server/modules/remotes/docker/docker-plan.dto.js.map +1 -1
  156. package/dist/server/modules/remotes/docker/docker-plan.service.d.ts +13 -7
  157. package/dist/server/modules/remotes/docker/docker-plan.service.js +57 -13
  158. package/dist/server/modules/remotes/docker/docker-plan.service.js.map +1 -1
  159. package/dist/server/modules/remotes/docker/docker-project-compose.d.ts +7 -0
  160. package/dist/server/modules/remotes/docker/docker-project-compose.js +30 -0
  161. package/dist/server/modules/remotes/docker/docker-project-compose.js.map +1 -0
  162. package/dist/server/modules/remotes/dtos/remote.dto.d.ts +2 -0
  163. package/dist/server/modules/remotes/dtos/remote.dto.js.map +1 -1
  164. package/dist/server/modules/remotes/git-owner.store.d.ts +13 -0
  165. package/dist/server/modules/remotes/git-owner.store.js +69 -0
  166. package/dist/server/modules/remotes/git-owner.store.js.map +1 -0
  167. package/dist/server/modules/remotes/host/host-docker.controller.d.ts +8 -0
  168. package/dist/server/modules/remotes/host/host-docker.controller.js +22 -6
  169. package/dist/server/modules/remotes/host/host-docker.controller.js.map +1 -1
  170. package/dist/server/modules/remotes/host/host-docker.dto.d.ts +606 -0
  171. package/dist/server/modules/remotes/host/host-docker.dto.js +47 -7
  172. package/dist/server/modules/remotes/host/host-docker.dto.js.map +1 -1
  173. package/dist/server/modules/remotes/host/host-docker.service.d.ts +16 -4
  174. package/dist/server/modules/remotes/host/host-docker.service.js +157 -82
  175. package/dist/server/modules/remotes/host/host-docker.service.js.map +1 -1
  176. package/dist/server/modules/remotes/host/host-ipv4-routes.d.ts +4 -0
  177. package/dist/server/modules/remotes/host/host-ipv4-routes.js +43 -0
  178. package/dist/server/modules/remotes/host/host-ipv4-routes.js.map +1 -0
  179. package/dist/server/modules/remotes/host-image.d.ts +1 -1
  180. package/dist/server/modules/remotes/host-image.js +1 -1
  181. package/dist/server/modules/remotes/host-install/host-install-block.d.ts +1 -0
  182. package/dist/server/modules/remotes/host-install/host-install-block.js +76 -2
  183. package/dist/server/modules/remotes/host-install/host-install-block.js.map +1 -1
  184. package/dist/server/modules/remotes/host-install/host-install.service.d.ts +0 -1
  185. package/dist/server/modules/remotes/host-install/host-install.service.js +9 -26
  186. package/dist/server/modules/remotes/host-install/host-install.service.js.map +1 -1
  187. package/dist/server/modules/remotes/operations/attach.operation.d.ts +5 -1
  188. package/dist/server/modules/remotes/operations/attach.operation.js +40 -5
  189. package/dist/server/modules/remotes/operations/attach.operation.js.map +1 -1
  190. package/dist/server/modules/remotes/operations/claim.operation.d.ts +1 -0
  191. package/dist/server/modules/remotes/operations/claim.operation.js +6 -1
  192. package/dist/server/modules/remotes/operations/claim.operation.js.map +1 -1
  193. package/dist/server/modules/remotes/operations/detach.operation.d.ts +4 -1
  194. package/dist/server/modules/remotes/operations/detach.operation.js +10 -2
  195. package/dist/server/modules/remotes/operations/detach.operation.js.map +1 -1
  196. package/dist/server/modules/remotes/operations/docker-import-inventory.store.d.ts +4 -0
  197. package/dist/server/modules/remotes/operations/docker-import-inventory.store.js +11 -0
  198. package/dist/server/modules/remotes/operations/docker-import-inventory.store.js.map +1 -1
  199. package/dist/server/modules/remotes/operations/file-sync-handoff.d.ts +29 -3
  200. package/dist/server/modules/remotes/operations/file-sync-handoff.js +477 -50
  201. package/dist/server/modules/remotes/operations/file-sync-handoff.js.map +1 -1
  202. package/dist/server/modules/remotes/operations/force-sync.operation.d.ts +39 -0
  203. package/dist/server/modules/remotes/operations/force-sync.operation.js +177 -0
  204. package/dist/server/modules/remotes/operations/force-sync.operation.js.map +1 -0
  205. package/dist/server/modules/remotes/operations/git-owner.dto.d.ts +83 -0
  206. package/dist/server/modules/remotes/operations/git-owner.dto.js +25 -0
  207. package/dist/server/modules/remotes/operations/git-owner.dto.js.map +1 -0
  208. package/dist/server/modules/remotes/operations/git-owner.operation.d.ts +35 -0
  209. package/dist/server/modules/remotes/operations/git-owner.operation.js +216 -0
  210. package/dist/server/modules/remotes/operations/git-owner.operation.js.map +1 -0
  211. package/dist/server/modules/remotes/operations/remote-host.client.d.ts +71 -7
  212. package/dist/server/modules/remotes/operations/remote-host.client.js +141 -11
  213. package/dist/server/modules/remotes/operations/remote-host.client.js.map +1 -1
  214. package/dist/server/modules/remotes/operations/remote-operation.dto.d.ts +19 -0
  215. package/dist/server/modules/remotes/operations/remote-operation.dto.js +5 -1
  216. package/dist/server/modules/remotes/operations/remote-operation.dto.js.map +1 -1
  217. package/dist/server/modules/remotes/operations/remote-operation.runner.d.ts +3 -1
  218. package/dist/server/modules/remotes/operations/remote-operation.runner.js +52 -21
  219. package/dist/server/modules/remotes/operations/remote-operation.runner.js.map +1 -1
  220. package/dist/server/modules/remotes/operations/remote-operation.types.d.ts +4 -1
  221. package/dist/server/modules/remotes/operations/remote-operation.types.js.map +1 -1
  222. package/dist/server/modules/remotes/operations/remote-operations.controller.d.ts +5 -0
  223. package/dist/server/modules/remotes/operations/remote-operations.controller.js +56 -0
  224. package/dist/server/modules/remotes/operations/remote-operations.controller.js.map +1 -1
  225. package/dist/server/modules/remotes/operations/remote-operations.service.d.ts +16 -1
  226. package/dist/server/modules/remotes/operations/remote-operations.service.js +155 -5
  227. package/dist/server/modules/remotes/operations/remote-operations.service.js.map +1 -1
  228. package/dist/server/modules/remotes/operations/update-logins.operation.d.ts +1 -3
  229. package/dist/server/modules/remotes/operations/update-logins.operation.js +3 -18
  230. package/dist/server/modules/remotes/operations/update-logins.operation.js.map +1 -1
  231. package/dist/server/modules/remotes/ports/remote-health.port.d.ts +2 -0
  232. package/dist/server/modules/remotes/ports/remote-health.port.js.map +1 -1
  233. package/dist/server/modules/remotes/remotes.module.js +18 -0
  234. package/dist/server/modules/remotes/remotes.module.js.map +1 -1
  235. package/dist/server/modules/remotes/replica/__fixtures__/replica-seed.d.ts +2 -2
  236. package/dist/server/modules/remotes/replica/__fixtures__/replica-seed.js +2 -13
  237. package/dist/server/modules/remotes/replica/__fixtures__/replica-seed.js.map +1 -1
  238. package/dist/server/modules/remotes/services/remote-health.service.js +5 -0
  239. package/dist/server/modules/remotes/services/remote-health.service.js.map +1 -1
  240. package/dist/server/modules/remotes/sync/file-sync-failures.service.d.ts +18 -0
  241. package/dist/server/modules/remotes/sync/file-sync-failures.service.js +157 -0
  242. package/dist/server/modules/remotes/sync/file-sync-failures.service.js.map +1 -0
  243. package/dist/server/modules/remotes/sync/file-sync-pattern-preview.service.d.ts +14 -0
  244. package/dist/server/modules/remotes/sync/file-sync-pattern-preview.service.js +99 -0
  245. package/dist/server/modules/remotes/sync/file-sync-pattern-preview.service.js.map +1 -0
  246. package/dist/server/modules/remotes/sync/file-sync-suggestions.service.d.ts +15 -0
  247. package/dist/server/modules/remotes/sync/file-sync-suggestions.service.js +90 -0
  248. package/dist/server/modules/remotes/sync/file-sync-suggestions.service.js.map +1 -0
  249. package/dist/server/modules/remotes/sync/project-file-sync.controller.d.ts +29 -0
  250. package/dist/server/modules/remotes/sync/project-file-sync.controller.js +160 -0
  251. package/dist/server/modules/remotes/sync/project-file-sync.controller.js.map +1 -0
  252. package/dist/server/modules/remotes/sync/remote-file-sync.dto.d.ts +72 -0
  253. package/dist/server/modules/remotes/sync/remote-file-sync.dto.js +16 -0
  254. package/dist/server/modules/remotes/sync/remote-file-sync.dto.js.map +1 -0
  255. package/dist/server/modules/remotes/sync/remote-file-sync.service.d.ts +32 -1
  256. package/dist/server/modules/remotes/sync/remote-file-sync.service.js +499 -38
  257. package/dist/server/modules/remotes/sync/remote-file-sync.service.js.map +1 -1
  258. package/dist/server/modules/remotes/sync/remote-live-sync.service.d.ts +2 -0
  259. package/dist/server/modules/remotes/sync/remote-live-sync.service.js +26 -12
  260. package/dist/server/modules/remotes/sync/remote-live-sync.service.js.map +1 -1
  261. package/dist/server/modules/remotes/vm-user-identity.d.ts +29 -0
  262. package/dist/server/modules/remotes/vm-user-identity.js +45 -0
  263. package/dist/server/modules/remotes/vm-user-identity.js.map +1 -0
  264. package/dist/server/modules/runtime-context-capture/codex-plugin-profile-materializer.service.js +5 -1
  265. package/dist/server/modules/runtime-context-capture/codex-plugin-profile-materializer.service.js.map +1 -1
  266. package/dist/server/modules/session-reader/data/pricing.json +55 -7
  267. package/dist/server/modules/sessions/services/provider-launch-config/provider-launch-config.service.js +5 -0
  268. package/dist/server/modules/sessions/services/provider-launch-config/provider-launch-config.service.js.map +1 -1
  269. package/dist/server/modules/settings/local/delegates/skills-settings.delegate.d.ts +6 -0
  270. package/dist/server/modules/settings/local/delegates/skills-settings.delegate.js +26 -0
  271. package/dist/server/modules/settings/local/delegates/skills-settings.delegate.js.map +1 -1
  272. package/dist/server/modules/settings/services/settings.service.d.ts +3 -1
  273. package/dist/server/modules/settings/services/settings.service.js +6 -0
  274. package/dist/server/modules/settings/services/settings.service.js.map +1 -1
  275. package/dist/server/modules/skills/adapters/anthropic-skill-source.adapter.js +5 -5
  276. package/dist/server/modules/skills/adapters/anthropic-skill-source.adapter.js.map +1 -1
  277. package/dist/server/modules/skills/adapters/github-directory-skill-source.adapter.js +5 -5
  278. package/dist/server/modules/skills/adapters/github-directory-skill-source.adapter.js.map +1 -1
  279. package/dist/server/modules/skills/adapters/github-skill-source.base.js +4 -3
  280. package/dist/server/modules/skills/adapters/github-skill-source.base.js.map +1 -1
  281. package/dist/server/modules/skills/adapters/local-skill-source.adapter.js +5 -5
  282. package/dist/server/modules/skills/adapters/local-skill-source.adapter.js.map +1 -1
  283. package/dist/server/modules/skills/adapters/microsoft-skill-source.adapter.js +5 -5
  284. package/dist/server/modules/skills/adapters/microsoft-skill-source.adapter.js.map +1 -1
  285. package/dist/server/modules/skills/adapters/openai-skill-source.adapter.js +5 -5
  286. package/dist/server/modules/skills/adapters/openai-skill-source.adapter.js.map +1 -1
  287. package/dist/server/modules/skills/adapters/skill-source.adapter.d.ts +5 -0
  288. package/dist/server/modules/skills/adapters/skill-sync-temp-cleanup.d.ts +5 -0
  289. package/dist/server/modules/skills/adapters/skill-sync-temp-cleanup.js +75 -0
  290. package/dist/server/modules/skills/adapters/skill-sync-temp-cleanup.js.map +1 -0
  291. package/dist/server/modules/skills/adapters/trailofbits-skill-source.adapter.js +5 -5
  292. package/dist/server/modules/skills/adapters/trailofbits-skill-source.adapter.js.map +1 -1
  293. package/dist/server/modules/skills/adapters/vercel-skill-source.adapter.js +5 -5
  294. package/dist/server/modules/skills/adapters/vercel-skill-source.adapter.js.map +1 -1
  295. package/dist/server/modules/skills/controllers/skills.controller.js +2 -2
  296. package/dist/server/modules/skills/controllers/skills.controller.js.map +1 -1
  297. package/dist/server/modules/skills/services/skill-source-lifecycle.service.d.ts +3 -3
  298. package/dist/server/modules/skills/services/skill-source-lifecycle.service.js +9 -4
  299. package/dist/server/modules/skills/services/skill-source-lifecycle.service.js.map +1 -1
  300. package/dist/server/modules/skills/services/skill-sync.service.d.ts +3 -3
  301. package/dist/server/modules/skills/services/skill-sync.service.js +32 -6
  302. package/dist/server/modules/skills/services/skill-sync.service.js.map +1 -1
  303. package/dist/server/modules/skills/services/skill-sync.types.d.ts +3 -0
  304. package/dist/server/modules/storage/db/schema.d.ts +2 -2
  305. package/dist/server/modules/storage/db/schema.js +2 -0
  306. package/dist/server/modules/storage/db/schema.js.map +1 -1
  307. package/dist/server/modules/storage/models/domain.models.d.ts +1 -1
  308. package/dist/server/modules/storage/models/domain.models.js +2 -0
  309. package/dist/server/modules/storage/models/domain.models.js.map +1 -1
  310. package/dist/server/modules/terminal/gateways/terminal.gateway.d.ts +3 -1
  311. package/dist/server/modules/terminal/gateways/terminal.gateway.js +9 -4
  312. package/dist/server/modules/terminal/gateways/terminal.gateway.js.map +1 -1
  313. package/dist/server/modules/terminal/services/terminal-activity.service.d.ts +0 -1
  314. package/dist/server/modules/terminal/services/terminal-activity.service.js +0 -3
  315. package/dist/server/modules/terminal/services/terminal-activity.service.js.map +1 -1
  316. package/dist/server/modules/terminal/services/terminal-io/lifecycle.js +15 -3
  317. package/dist/server/modules/terminal/services/terminal-io/lifecycle.js.map +1 -1
  318. package/dist/server/modules/terminal/utils/ansi-sanitizer.d.ts +0 -1
  319. package/dist/server/modules/terminal/utils/ansi-sanitizer.js +0 -4
  320. package/dist/server/modules/terminal/utils/ansi-sanitizer.js.map +1 -1
  321. package/dist/server/templates/teams-dev.json +11 -11
  322. package/dist/server/test-setup-node.js +4 -3
  323. package/dist/server/test-setup-node.js.map +1 -1
  324. package/dist/server/test-setup.js +7 -0
  325. package/dist/server/test-setup.js.map +1 -1
  326. package/dist/server/tsconfig.tsbuildinfo +1 -1
  327. package/dist/server/ui/assets/{ExternalRichEditor-DtNvrV0F.js → ExternalRichEditor-CIZPhssf.js} +24 -24
  328. package/dist/server/ui/assets/ReviewDetailPage-winyLSQ7.js +6 -0
  329. package/dist/server/ui/assets/ReviewsPage-BYKBReMp.js +6 -0
  330. package/dist/server/ui/assets/index-CiDJX6Hn.js +1191 -0
  331. package/dist/server/ui/assets/index-f1WOAqXB.css +32 -0
  332. package/dist/server/ui/assets/useReviewSubscription-v3Cto0y4.js +69 -0
  333. package/dist/server/ui/index.html +2 -2
  334. package/dist/templates/teams-dev.json +11 -11
  335. package/package.json +8 -1
  336. package/dist/server/modules/reviews/utils/notification-formatter.d.ts +0 -12
  337. package/dist/server/modules/reviews/utils/notification-formatter.js +0 -60
  338. package/dist/server/modules/reviews/utils/notification-formatter.js.map +0 -1
  339. package/dist/server/ui/assets/ReviewDetailPage-DJzEoM0x.js +0 -6
  340. package/dist/server/ui/assets/ReviewsPage-z2O8MNEw.js +0 -6
  341. package/dist/server/ui/assets/index-CC4P3V8L.js +0 -994
  342. package/dist/server/ui/assets/index-LsJe1ZTS.css +0 -32
  343. package/dist/server/ui/assets/useReviewSubscription-B7AdJBO2.js +0 -69
@@ -14,19 +14,32 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.RemoteFileSyncService = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
+ const node_os_1 = require("node:os");
18
+ const git_owner_store_1 = require("../git-owner.store");
17
19
  const logger_1 = require("../../../common/logging/logger");
20
+ const error_types_1 = require("../../../common/errors/error-types");
18
21
  const home_git_guard_service_1 = require("../../file-sync/home-git-guard.service");
19
22
  const file_sync_managed_exclusions_store_1 = require("../../file-sync/file-sync-managed-exclusions.store");
23
+ const file_sync_auto_fix_store_1 = require("../../file-sync/file-sync-auto-fix.store");
24
+ const sync_path_inspection_dto_1 = require("../../file-sync/sync-path-inspection.dto");
25
+ const sync_chown_dto_1 = require("../../file-sync/sync-chown.dto");
26
+ const file_sync_failures_service_1 = require("./file-sync-failures.service");
20
27
  const file_sync_dto_1 = require("../../file-sync/file-sync.dto");
28
+ const syncthing_rest_client_1 = require("../../file-sync/syncthing-rest.client");
21
29
  const file_sync_service_1 = require("../../file-sync/file-sync.service");
22
30
  const git_service_1 = require("../../git/services/git.service");
31
+ const storage_interface_1 = require("../../storage/interfaces/storage.interface");
23
32
  const remote_host_client_1 = require("../operations/remote-host.client");
24
33
  const remote_health_port_1 = require("../ports/remote-health.port");
25
34
  const remote_bindings_service_1 = require("../services/remote-bindings.service");
35
+ const remote_file_sync_dto_1 = require("./remote-file-sync.dto");
26
36
  const logger = (0, logger_1.createLogger)('RemoteFileSyncService');
27
37
  const CHECK_INTERVAL_MS = 30_000;
28
38
  const PROBLEM_GRACE_MS = 2 * 60_000;
29
39
  const STALLED_MS = 10 * 60_000;
40
+ const REPEAT_REPAIR_MS = 60 * 60_000;
41
+ const MOVE_STEPS = ['create', 'settle', 'ignores', 'directions', 'guard', 'done'];
42
+ const stepIndex = (step) => MOVE_STEPS.indexOf(step);
30
43
  let RemoteFileSyncService = class RemoteFileSyncService {
31
44
  files;
32
45
  host;
@@ -35,8 +48,12 @@ let RemoteFileSyncService = class RemoteFileSyncService {
35
48
  exclusions;
36
49
  git;
37
50
  guard;
51
+ storage;
52
+ autoFix;
53
+ failures;
54
+ gitOwner;
38
55
  projects = new Map();
39
- constructor(files, host, bindings, health, exclusions, git, guard) {
56
+ constructor(files, host, bindings, health, exclusions, git, guard, storage, autoFix, failures, gitOwner) {
40
57
  this.files = files;
41
58
  this.host = host;
42
59
  this.bindings = bindings;
@@ -44,18 +61,286 @@ let RemoteFileSyncService = class RemoteFileSyncService {
44
61
  this.exclusions = exclusions;
45
62
  this.git = git;
46
63
  this.guard = guard;
64
+ this.storage = storage;
65
+ this.autoFix = autoFix;
66
+ this.failures = failures;
67
+ this.gitOwner = gitOwner;
68
+ }
69
+ getIgnores(projectId) {
70
+ return {
71
+ ignores: this.files.getIgnores(projectId),
72
+ revision: this.files.getIgnoresRevision(projectId),
73
+ };
74
+ }
75
+ getAutoFix(projectId) {
76
+ return this.autoFix.get(projectId);
77
+ }
78
+ setAutoFix(projectId, enabled) {
79
+ return this.autoFix.setEnabled(projectId, enabled);
80
+ }
81
+ ownershipNotes(projectId) {
82
+ const notes = this.projects.get(projectId)?.ownershipNotes;
83
+ if (!notes)
84
+ return [];
85
+ const now = Date.now();
86
+ for (const [path, at] of notes)
87
+ if (now - at >= REPEAT_REPAIR_MS)
88
+ notes.delete(path);
89
+ return [...notes.keys()].map((path) => `A container keeps writing as another user in ${path}.`);
90
+ }
91
+ async giveOwnership(projectId, paths, active) {
92
+ await this.assertNoHold(projectId);
93
+ const binding = await this.bindings.get(projectId);
94
+ if (binding?.state !== 'remote' || !active())
95
+ throw new error_types_1.ConflictError('The project must be connected to repair VM owners.');
96
+ const health = this.health.getState(binding.remoteId);
97
+ if (!health.online || health.apiKeyRejected || !health.versionMatches)
98
+ throw new error_types_1.ConflictError('The VM must be online with an accepted key and matching version.');
99
+ const failed = await this.failures.failed(projectId);
100
+ const eligible = new Set(failed.groups.filter(sync_path_inspection_dto_1.isGiveOwnershipEligible).map((group) => group.path));
101
+ const accepted = [...new Set(paths)].filter((path) => eligible.has(path));
102
+ const refused = paths
103
+ .filter((path) => !eligible.has(path))
104
+ .map((path) => ({
105
+ path,
106
+ state: 'refused',
107
+ paths: [],
108
+ reason: 'Give to requires a foreign-owned VM path Git neither tracks nor ignores.',
109
+ }));
110
+ if (!accepted.length)
111
+ return { user: failed.vmUser ?? null, items: refused };
112
+ await this.assertNoHold(projectId);
113
+ if (!active())
114
+ throw new error_types_1.ConflictError('The connection is changing.');
115
+ const { rootPath } = await this.storage.getProject(projectId);
116
+ const result = await this.host.syncChown(binding.remoteId, {
117
+ root: rootPath,
118
+ items: accepted.map((path) => ({ path, mode: 'give' })),
119
+ });
120
+ this.recordRepairs(projectId, result, Date.now());
121
+ if (result.items.some((item) => item.paths.length))
122
+ await this.retryPullErrors(projectId, 'vm', binding.remoteId, active);
123
+ return { ...result, items: [...result.items, ...refused] };
124
+ }
125
+ recordRepairs(projectId, result, now) {
126
+ const paths = [...new Set(result.items.flatMap((item) => item.paths))];
127
+ for (let offset = 0; offset < paths.length; offset += sync_chown_dto_1.SYNC_CHOWN_PATHS_MAX)
128
+ this.autoFix.record(projectId, {
129
+ at: new Date(now).toISOString(),
130
+ kind: 'chown',
131
+ side: 'vm',
132
+ paths: paths.slice(offset, offset + sync_chown_dto_1.SYNC_CHOWN_PATHS_MAX),
133
+ });
47
134
  }
48
135
  warning(projectId) {
49
136
  const state = this.projects.get(projectId);
50
137
  return state?.checkWarning ?? state?.warning ?? state?.guardWarning ?? null;
51
138
  }
139
+ problem(projectId) {
140
+ const state = this.projects.get(projectId);
141
+ if (state?.checkWarning)
142
+ return state.checkProblem ?? null;
143
+ if (state?.warning)
144
+ return 'setup';
145
+ return null;
146
+ }
147
+ async hasOpenHold(projectId) {
148
+ const operations = await this.storage.listRemoteOperations({
149
+ projectId,
150
+ kinds: ['force_sync', 'git_owner'],
151
+ states: ['running', 'failed'],
152
+ limit: 1,
153
+ });
154
+ return operations.some((operation) => (operation.kind === 'force_sync' || operation.kind === 'git_owner') &&
155
+ (operation.state === 'running' || operation.state === 'failed'));
156
+ }
157
+ async assertNoHold(projectId) {
158
+ if (await this.hasOpenHold(projectId))
159
+ throw new error_types_1.ConflictError('Force sync or a Git switch is running for this project. Save the list after it finishes.');
160
+ }
161
+ async forceSyncOffer(projectId, excludeOperationId) {
162
+ const pending = { fromVm: null, fromHome: null };
163
+ const refuse = (reason) => ({ offered: false, reason, pending });
164
+ const binding = await this.bindings.get(projectId);
165
+ if (binding?.state !== 'remote')
166
+ return refuse('The project must be connected to Force sync.');
167
+ const device = await this.host.syncDevice(binding.remoteId).catch(() => null);
168
+ const ids = ['code', 'git'].map((kind) => (0, file_sync_service_1.projectFolderId)(projectId, kind));
169
+ const reads = await Promise.allSettled([
170
+ ...ids.map((id, index) => this.files.status(id, index === 0 ? device?.deviceId : undefined, { allErrors: true })),
171
+ ...ids.map((id) => this.host.syncStatus(binding.remoteId, id, undefined, { allErrors: true })),
172
+ ]);
173
+ const homeCode = reads[0];
174
+ if (homeCode.status === 'fulfilled') {
175
+ pending.fromVm = homeCode.value.needTotalItems ?? null;
176
+ pending.fromHome = homeCode.value.peer?.needItems ?? null;
177
+ }
178
+ const errors = [];
179
+ for (const [index, read] of reads.entries()) {
180
+ if (read.status === 'rejected') {
181
+ const error = read.reason;
182
+ if ((error instanceof syncthing_rest_client_1.SyncthingRestError || error instanceof remote_host_client_1.RemoteHostRequestError) &&
183
+ error.status === 404)
184
+ continue;
185
+ return refuse(index < 2
186
+ ? "DevChain could not read this PC's file sync status."
187
+ : "DevChain could not read the VM's file sync status.");
188
+ }
189
+ const status = read.value;
190
+ errors.push(...(status.fileErrors ?? []).map((entry) => entry.error));
191
+ if (status.error)
192
+ errors.push(status.error);
193
+ }
194
+ const persistent = errors.filter((error) => !(0, file_sync_dto_1.isTransientSyncError)(error));
195
+ if (persistent.some((error) => /permission denied|operation not permitted/i.test(error)))
196
+ return refuse('Repair file permissions before using Force sync.');
197
+ const health = this.health.getState(binding.remoteId);
198
+ if (health.apiKeyRejected)
199
+ return refuse("The VM rejected this PC's API key.");
200
+ if (!health.online)
201
+ return refuse('The VM is offline.');
202
+ if (!health.versionMatches)
203
+ return refuse('The VM runs a different DevChain version.');
204
+ if (!device)
205
+ return refuse("DevChain could not read the VM's file sync status.");
206
+ const connected = await this.files.isConnected(device.deviceId).catch(() => false);
207
+ if (!connected)
208
+ return refuse('There is no file sync connection to the VM.');
209
+ const operations = await this.storage.listRemoteOperations({
210
+ projectId,
211
+ states: ['running', 'failed'],
212
+ });
213
+ if (operations.some((operation) => operation.id !== excludeOperationId))
214
+ return refuse('Another operation is open for this project.');
215
+ const problem = this.problem(projectId);
216
+ const failedFiles = reads.some((read) => read.status === 'fulfilled' &&
217
+ (read.value.fileErrors ?? []).some((entry) => !(0, file_sync_dto_1.isTransientSyncError)(entry.error)));
218
+ if (!(0, remote_file_sync_dto_1.isStuckFileSyncProblem)(problem) && !failedFiles)
219
+ return refuse('File sync has no stuck problem to repair.');
220
+ return { offered: true, reason: null, pending };
221
+ }
222
+ failedCounts(projectId) {
223
+ return this.projects.get(projectId)?.failed ?? null;
224
+ }
52
225
  forget(projectId) {
53
226
  this.projects.delete(projectId);
54
227
  }
228
+ async saveIgnores(projectId, list, active, expectedRevision) {
229
+ const ignores = this.files.setIgnores(projectId, list, expectedRevision);
230
+ const revision = this.files.getIgnoresRevision(projectId);
231
+ const notApplied = (reason) => ({
232
+ ignores,
233
+ revision,
234
+ applied: false,
235
+ message: `Saved, not applied yet: ${reason}; DevChain applies it automatically while the project stays connected.`,
236
+ });
237
+ const deferred = (reason) => {
238
+ const state = this.projects.get(projectId);
239
+ if (state && stepIndex(state.step) > stepIndex('ignores'))
240
+ state.step = 'ignores';
241
+ return notApplied(reason);
242
+ };
243
+ let reason = 'the connection could not be checked';
244
+ try {
245
+ const binding = await this.bindings.get(projectId);
246
+ if (binding?.state === 'attaching')
247
+ return notApplied('the project is connecting');
248
+ if (binding?.state !== 'remote') {
249
+ return {
250
+ ignores,
251
+ revision,
252
+ applied: false,
253
+ message: 'Saved. The list applies at the next Connect.',
254
+ };
255
+ }
256
+ if (!active())
257
+ return deferred('live sync is not running');
258
+ const health = this.health.getState(binding.remoteId);
259
+ if (health.apiKeyRejected)
260
+ return deferred("the VM rejected this PC's API key");
261
+ if (!health.online)
262
+ return deferred('the VM is offline');
263
+ if (!health.versionMatches)
264
+ return deferred('the VM runs a different DevChain version');
265
+ const state = this.projects.get(projectId);
266
+ if (!state ||
267
+ state.remoteId !== binding.remoteId ||
268
+ stepIndex(state.step) < stepIndex('ignores')) {
269
+ return deferred('file sync setup is still running');
270
+ }
271
+ reason = 'the ignore list could not be prepared';
272
+ const installed = (0, file_sync_dto_1.codeIgnores)(this.exclusions.get(projectId), ignores);
273
+ const codeId = (0, file_sync_service_1.projectFolderId)(projectId, 'code');
274
+ reason = 'the VM could not apply the list';
275
+ await this.host.syncFolderType(binding.remoteId, codeId, { ignores: installed });
276
+ await this.retryPullErrors(projectId, 'vm', binding.remoteId, active);
277
+ if (!active())
278
+ return deferred('the connection is changing');
279
+ reason = 'this PC could not apply the list';
280
+ try {
281
+ await this.files.updateFolder(codeId, { ignores: installed });
282
+ await this.retryPullErrors(projectId, 'home', binding.remoteId, active);
283
+ }
284
+ catch (error) {
285
+ if (error instanceof syncthing_rest_client_1.SyncthingRestError && error.status === 404)
286
+ reason = 'the folder is not shared on this PC';
287
+ throw error;
288
+ }
289
+ return { ignores, revision, applied: true, message: 'Applied to the VM and this PC.' };
290
+ }
291
+ catch (error) {
292
+ logger.warn({ error, projectId }, 'Saved file sync ignores will retry');
293
+ return deferred(reason);
294
+ }
295
+ }
296
+ async retryPullErrors(projectId, side, remoteId, active) {
297
+ const codeId = (0, file_sync_service_1.projectFolderId)(projectId, 'code');
298
+ try {
299
+ if (!active())
300
+ return;
301
+ const status = side === 'vm'
302
+ ? await this.host.syncStatus(remoteId, codeId)
303
+ : await this.files.status(codeId);
304
+ if (!active() || (status.pullErrors ?? 0) <= 0)
305
+ return;
306
+ const state = this.projects.get(projectId);
307
+ if (state)
308
+ (state.resume ??= new Set()).add(side);
309
+ try {
310
+ await this.pauseCode(codeId, side, remoteId, true);
311
+ }
312
+ finally {
313
+ await this.pauseCode(codeId, side, remoteId, false);
314
+ state?.resume?.delete(side);
315
+ }
316
+ }
317
+ catch (error) {
318
+ logger.warn({ error, projectId, side }, 'File sync folder restart failed after applying ignores');
319
+ }
320
+ }
321
+ pauseCode(codeId, side, remoteId, paused) {
322
+ return side === 'vm'
323
+ ? this.host.syncFolderType(remoteId, codeId, { paused })
324
+ : this.files.updateFolder(codeId, { paused });
325
+ }
326
+ async resumeOwed(projectId, state) {
327
+ const codeId = (0, file_sync_service_1.projectFolderId)(projectId, 'code');
328
+ for (const side of [...(state.resume ?? [])]) {
329
+ try {
330
+ await this.pauseCode(codeId, side, state.remoteId, false);
331
+ state.resume.delete(side);
332
+ }
333
+ catch (error) {
334
+ logger.warn({ error, projectId, side }, 'File sync folder resume will retry');
335
+ }
336
+ }
337
+ }
55
338
  async tick(projectId, remoteId, active) {
56
339
  const health = this.health.getState(remoteId);
57
340
  if (!active() || !health.online || health.apiKeyRejected || !health.versionMatches)
58
341
  return;
342
+ if (await this.hasOpenHold(projectId))
343
+ return;
59
344
  const binding = await this.bindings.get(projectId);
60
345
  if (!active() || binding?.state !== 'remote' || binding.remoteId !== remoteId)
61
346
  return;
@@ -65,6 +350,10 @@ let RemoteFileSyncService = class RemoteFileSyncService {
65
350
  this.projects.set(projectId, state);
66
351
  }
67
352
  try {
353
+ if (state.resume?.size)
354
+ await this.resumeOwed(projectId, state);
355
+ if (!active())
356
+ return;
68
357
  if (state.step !== 'done') {
69
358
  await this.move(projectId, state, active);
70
359
  return;
@@ -72,24 +361,51 @@ let RemoteFileSyncService = class RemoteFileSyncService {
72
361
  await this.checkSync(projectId, state, active);
73
362
  if (!state.hasGit || !active())
74
363
  return;
364
+ const owner = this.gitOwner.get(projectId);
365
+ const receiver = owner === 'vm' ? 'home' : 'vm';
366
+ const generation = this.gitOwner.generation(projectId);
367
+ if (state.headsGeneration !== generation) {
368
+ state.heads = undefined;
369
+ state.headsGeneration = generation;
370
+ }
75
371
  const id = (0, file_sync_service_1.projectFolderId)(projectId, 'git');
76
- const config = await this.files.folderConfiguration(id);
372
+ const config = receiver === 'home'
373
+ ? await this.files.folderConfiguration(id)
374
+ : await this.host.syncFolderConfiguration(remoteId, id);
77
375
  if (!active() || config.type !== 'receiveonly' || config.paused)
78
376
  return;
79
- const status = await this.files.status(id);
377
+ const status = receiver === 'home'
378
+ ? await this.files.status(id)
379
+ : await this.host.syncStatus(remoteId, id);
80
380
  if (!active() || status.state !== 'idle' || status.needTotalItems !== 0)
81
381
  return;
82
382
  state.warning = null;
83
383
  if (status.receiveOnlyChangedFiles > 0) {
84
- await this.files.revertLocalChanges(id);
384
+ if (receiver === 'home')
385
+ await this.files.revertLocalChanges(id);
386
+ else
387
+ await this.host.syncRevert(remoteId, id);
85
388
  return;
86
389
  }
87
390
  const root = await this.files.folderPath(projectId);
88
391
  const head = await this.git.mirroredHead(projectId, root);
89
- if (!active() || head === null || head === state.head)
392
+ if (!active() || head === null || head === state.heads?.[receiver])
90
393
  return;
91
- await this.git.refreshIndexFromHead(projectId, root);
92
- state.head = head;
394
+ if (receiver === 'home') {
395
+ await this.git.refreshIndexFromHead(projectId, root);
396
+ (state.heads ??= {}).home = head;
397
+ }
398
+ else {
399
+ const result = await this.host.refreshGitIndex(remoteId, projectId, state.heads?.vm ?? null);
400
+ if (!active())
401
+ return;
402
+ if (result.warning) {
403
+ state.warning = result.warning;
404
+ return;
405
+ }
406
+ if (result.refreshed && result.head !== null)
407
+ (state.heads ??= {}).vm = result.head;
408
+ }
93
409
  }
94
410
  catch (error) {
95
411
  state.warning =
@@ -111,54 +427,172 @@ let RemoteFileSyncService = class RemoteFileSyncService {
111
427
  const code = configurations.find((folder) => folder.kind === 'code');
112
428
  const homeId = this.files.device().deviceId;
113
429
  const peers = code?.config.devices.filter((device) => device.deviceID !== homeId);
114
- if (peers?.length !== 1)
430
+ if (!code || peers?.length !== 1)
115
431
  throw new Error('Expected one VM device in the code folder');
116
432
  const peerId = peers[0].deviceID;
117
- const [connected, statuses] = await Promise.all([
433
+ const [connected, statuses, vmStatus] = await Promise.all([
118
434
  this.files.isConnected(peerId),
119
435
  Promise.all(configurations.map(async (folder) => ({
120
436
  ...folder,
121
437
  status: await this.files.status(folder.id, folder.kind === 'code' ? peerId : undefined),
122
438
  }))),
439
+ this.host.syncStatus(state.remoteId, code.id).catch((error) => {
440
+ logger.warn({ error, projectId }, 'VM file sync status could not be read');
441
+ return null;
442
+ }),
123
443
  ]);
124
444
  if (!active())
125
445
  return;
126
446
  const observations = new Map();
127
447
  const warnings = [];
128
- const observe = (key, count, limit, message) => {
448
+ const observe = (key, count, limit, message, problem, bytes) => {
129
449
  if (count <= 0)
130
- return;
450
+ return 0;
131
451
  const previous = state.observations?.get(key);
132
- const since = !previous || count < previous.count ? now : previous.since;
133
- observations.set(key, { since, count });
134
- if (now - since >= limit)
135
- warnings.push(message(new Date(since).toTimeString().slice(0, 5)));
452
+ const since = !previous ||
453
+ count < previous.count ||
454
+ (bytes !== undefined && previous.bytes !== undefined && bytes < previous.bytes)
455
+ ? now
456
+ : previous.since;
457
+ observations.set(key, { since, count, bytes });
458
+ if (now - since < limit)
459
+ return 0;
460
+ warnings.push({ message: message(new Date(since).toTimeString().slice(0, 5)), problem });
461
+ return count;
136
462
  };
137
- observe('connection', connected ? 0 : 1, PROBLEM_GRACE_MS, (since) => `No file sync connection to the VM since ${since}; changes do not move between this PC and the VM.`);
138
- for (const { id, config, status } of statuses) {
139
- const error = folderError(config, status);
140
- observe(`error:${id}`, error ? 1 : 0, PROBLEM_GRACE_MS, (since) => `File sync at home has an error since ${since}: ${error}.`);
463
+ observe('connection', connected ? 0 : 1, PROBLEM_GRACE_MS, (since) => `No file sync connection to the VM since ${since}; changes do not move between this PC and the VM.`, 'connection');
464
+ const homeCode = statuses.find((folder) => folder.kind === 'code');
465
+ const vmCount = vmStatus
466
+ ? (vmStatus.errors ?? 0)
467
+ : (state.observations?.get('vm-failed')?.count ?? 0);
468
+ const vmFailed = observe('vm-failed', vmCount, PROBLEM_GRACE_MS, (since) => `${vmCount} files on the VM fail to sync since ${since}.`, 'failed-files');
469
+ const homeCount = homeCode.status.errors ?? 0;
470
+ const homeFailed = observe('home-failed', homeCount, PROBLEM_GRACE_MS, (since) => `${homeCount} files on this PC fail to sync since ${since}.`, 'failed-files');
471
+ for (const { id, kind, config, status } of statuses) {
472
+ const error = folderError(config, status, kind !== 'code');
473
+ observe(`error:${id}`, error ? 1 : 0, PROBLEM_GRACE_MS, (since) => `File sync at home has an error since ${since}: ${error}.`, !config.paused && !status.error?.trim() && (status.errors ?? 0) > 0
474
+ ? 'failed-files'
475
+ : 'error');
141
476
  }
142
477
  const stalled = `no progress for ${STALLED_MS / 60_000} minutes`;
143
478
  for (const { id, status } of statuses) {
144
- observe(`home:${id}`, status.needTotalItems, STALLED_MS, (since) => `This PC has not received ${status.needTotalItems} pending file sync items since ${since} (${stalled}).`);
479
+ observe(`home:${id}`, status.needTotalItems, STALLED_MS, (since) => `This PC has not received ${status.needTotalItems} pending file sync items since ${since} (${stalled}).`, 'stalled', status.needBytes);
145
480
  const { peer } = status;
146
481
  if (peer) {
147
- observe(`peer:${id}`, peer.needItems, STALLED_MS, (since) => `The VM has not received ${peer.needItems} pending file sync items from this PC since ${since} (${stalled}).`);
482
+ observe(`peer:${id}`, peer.needItems, STALLED_MS, (since) => `The VM has not received ${peer.needItems} pending file sync items from this PC since ${since} (${stalled}).`, 'stalled', peer.needBytes);
148
483
  }
149
484
  }
150
485
  state.observations = observations;
151
- state.checkWarning = warnings[0] ?? null;
486
+ state.checkWarning = warnings[0]?.message ?? null;
487
+ state.checkProblem = warnings[0]?.problem;
488
+ state.failed = homeFailed || vmFailed ? { home: homeFailed, vm: vmFailed } : undefined;
489
+ if (homeCount === 0 && vmCount === 0)
490
+ state.autoFixAttempt = undefined;
491
+ if (state.failed && active())
492
+ await this.fixAutomatically(projectId, state, { home: homeCount, vm: vmCount }, now, active);
152
493
  }
153
494
  catch (error) {
154
495
  if (!active())
155
496
  return;
497
+ state.checkProblem = 'error';
156
498
  state.checkWarning = 'DevChain could not read the file sync status. It will try again.';
157
499
  logger.warn({ error, projectId }, 'Remote file sync check failed');
158
500
  }
159
501
  }
502
+ async fixAutomatically(projectId, state, counts, now, active) {
503
+ if (!this.autoFix.get(projectId).enabled)
504
+ return;
505
+ const previous = state.autoFixAttempt;
506
+ if (previous &&
507
+ previous.home === counts.home &&
508
+ previous.vm === counts.vm &&
509
+ now - previous.at < STALLED_MS)
510
+ return;
511
+ state.autoFixAttempt = { ...counts, at: now };
512
+ try {
513
+ const revision = this.files.getIgnoresRevision(projectId);
514
+ const failed = await this.failures.failed(projectId);
515
+ if (!active() || !this.autoFix.get(projectId).enabled)
516
+ return;
517
+ await this.repairAutomatically(projectId, state, failed, now, active);
518
+ if (!active() || !this.autoFix.get(projectId).enabled)
519
+ return;
520
+ const groups = failed.groups.filter((group) => (0, sync_path_inspection_dto_1.isSelectableExclusion)(group) &&
521
+ group.selected &&
522
+ group.reasonKind === 'foreignOwner' &&
523
+ !group.home?.tracked &&
524
+ !group.vm?.tracked &&
525
+ group.patternChecksPassed === true);
526
+ const current = this.files.getIgnores(projectId);
527
+ const additions = groups
528
+ .flatMap((group) => group.patterns)
529
+ .filter((pattern) => !current.includes(pattern));
530
+ const list = [...new Set([...current, ...additions])];
531
+ if (additions.length === 0 || !file_sync_dto_1.IgnorePatternsSchema.safeParse(list).success)
532
+ return;
533
+ if (await this.hasOpenHold(projectId))
534
+ return;
535
+ if (!active())
536
+ return;
537
+ await this.saveIgnores(projectId, list, active, revision);
538
+ for (const side of ['home', 'vm']) {
539
+ const patterns = [
540
+ ...new Set(groups
541
+ .filter((group) => group.side === side)
542
+ .flatMap((group) => group.patterns)
543
+ .filter((pattern) => additions.includes(pattern))),
544
+ ];
545
+ if (patterns.length)
546
+ this.autoFix.record(projectId, {
547
+ at: new Date(now).toISOString(),
548
+ kind: 'exclude',
549
+ side,
550
+ patterns,
551
+ });
552
+ }
553
+ }
554
+ catch (error) {
555
+ logger.warn({ error, projectId }, 'Automatic file sync exclusions will retry');
556
+ }
557
+ }
558
+ async repairAutomatically(projectId, state, failed, now, active) {
559
+ const attempts = (state.repairAttempts ??= new Map());
560
+ const paths = (failed.vm?.entries ?? [])
561
+ .filter((entry) => entry.git?.state === 'repo' &&
562
+ entry.git.tracked === true &&
563
+ failed.groups.some((group) => group.chown && (0, sync_path_inspection_dto_1.within)(entry.path, group.path)) &&
564
+ now - (attempts.get(entry.path) ?? -Infinity) >= STALLED_MS)
565
+ .map((entry) => entry.path);
566
+ if (!paths.length || (await this.hasOpenHold(projectId)) || !active())
567
+ return;
568
+ const { rootPath } = await this.storage.getProject(projectId);
569
+ try {
570
+ if (!active() || !this.autoFix.get(projectId).enabled)
571
+ return;
572
+ for (const path of paths)
573
+ attempts.set(path, now);
574
+ const result = await this.host.syncChown(state.remoteId, {
575
+ root: rootPath,
576
+ items: paths.map((path) => ({ path, mode: 'automatic' })),
577
+ });
578
+ this.recordRepairs(projectId, result, now);
579
+ const repaired = (state.repairedAt ??= new Map());
580
+ for (const item of result.items.filter((item) => item.paths.length > 0)) {
581
+ const previous = repaired.get(item.path);
582
+ if (previous !== undefined && now - previous < REPEAT_REPAIR_MS)
583
+ (state.ownershipNotes ??= new Map()).set(item.path, now);
584
+ repaired.set(item.path, now);
585
+ }
586
+ if (result.items.some((item) => item.paths.length))
587
+ await this.retryPullErrors(projectId, 'vm', state.remoteId, active);
588
+ }
589
+ catch (error) {
590
+ logger.warn({ error, projectId }, 'Automatic VM ownership repair will retry');
591
+ }
592
+ }
160
593
  async move(projectId, state, active) {
161
594
  const { remoteId } = state;
595
+ const owner = this.gitOwner.get(projectId);
162
596
  const codeId = (0, file_sync_service_1.projectFolderId)(projectId, 'code');
163
597
  const gitId = (0, file_sync_service_1.projectFolderId)(projectId, 'git');
164
598
  if (state.step === 'create') {
@@ -173,22 +607,30 @@ let RemoteFileSyncService = class RemoteFileSyncService {
173
607
  return;
174
608
  await this.files.addPeer(host);
175
609
  await this.host.syncPeer(remoteId, home);
176
- await this.host.syncFolders(remoteId, {
610
+ const ensureVm = () => this.host.syncFolders(remoteId, {
177
611
  projectId,
178
612
  kind: 'git',
179
- type: 'sendonly',
613
+ type: owner === 'vm' ? 'sendonly' : 'receiveonly',
180
614
  peerDeviceId: home.deviceId,
181
615
  ignores: (0, file_sync_dto_1.gitIgnores)(true),
182
616
  paused: true,
183
617
  });
184
- await this.files.ensureFolder({
618
+ const ensureHome = () => this.files.ensureFolder({
185
619
  projectId,
186
620
  kind: 'git',
187
- type: 'receiveonly',
621
+ type: owner === 'home' ? 'sendonly' : 'receiveonly',
188
622
  peerDeviceId: host.deviceId,
189
623
  ignores: (0, file_sync_dto_1.gitIgnores)(true),
190
624
  paused: true,
191
625
  });
626
+ if (owner === 'vm') {
627
+ await ensureHome();
628
+ await ensureVm();
629
+ }
630
+ else {
631
+ await ensureVm();
632
+ await ensureHome();
633
+ }
192
634
  await this.host.syncFolderType(remoteId, gitId, { paused: false });
193
635
  await this.files.updateFolder(gitId, { paused: false });
194
636
  await Promise.all([this.host.syncScan(remoteId, gitId), this.files.rescan(gitId)]);
@@ -198,17 +640,27 @@ let RemoteFileSyncService = class RemoteFileSyncService {
198
640
  if (!active())
199
641
  return;
200
642
  if (state.step === 'settle') {
201
- const config = await this.files.folderConfiguration(gitId);
643
+ const config = owner === 'vm'
644
+ ? await this.files.folderConfiguration(gitId)
645
+ : await this.host.syncFolderConfiguration(remoteId, gitId);
202
646
  if (config.type !== 'receiveonly' || config.paused)
203
647
  throw new Error('Git share is not receiving');
204
- const [sender, receiver] = await Promise.all([
205
- this.host.syncStatus(remoteId, gitId, this.files.device().deviceId),
206
- this.files.status(gitId),
207
- ]);
648
+ const [sender, receiver] = await Promise.all(owner === 'vm'
649
+ ? [
650
+ this.host.syncStatus(remoteId, gitId, this.files.device().deviceId),
651
+ this.files.status(gitId),
652
+ ]
653
+ : [
654
+ this.files.status(gitId, (await this.host.syncDevice(remoteId)).deviceId),
655
+ this.host.syncStatus(remoteId, gitId),
656
+ ]);
208
657
  if (!active())
209
658
  return;
210
659
  if (receiver.state === 'idle' && receiver.receiveOnlyChangedFiles > 0) {
211
- await this.files.revertLocalChanges(gitId);
660
+ if (owner === 'vm')
661
+ await this.files.revertLocalChanges(gitId);
662
+ else
663
+ await this.host.syncRevert(remoteId, gitId);
212
664
  return;
213
665
  }
214
666
  if (!(0, file_sync_service_1.evaluateCompletion)(sender, receiver).done) {
@@ -229,15 +681,21 @@ let RemoteFileSyncService = class RemoteFileSyncService {
229
681
  if (!active())
230
682
  return;
231
683
  if (state.step === 'directions') {
232
- await this.host.syncFolderType(remoteId, codeId, { type: 'sendreceive' });
233
- await this.files.updateFolder(codeId, { type: 'sendreceive' });
684
+ await this.host.syncFolderType(remoteId, codeId, { type: 'sendreceive', paused: false });
685
+ await this.files.updateFolder(codeId, { type: 'sendreceive', paused: false });
234
686
  state.step = 'guard';
235
687
  }
236
688
  if (!active())
237
689
  return;
238
690
  if (state.step === 'guard') {
239
691
  if (state.hasGit) {
240
- state.guardWarning = await this.guard.install(projectId, remoteId);
692
+ state.guardWarning =
693
+ owner === 'vm'
694
+ ? await this.guard.install(projectId, remoteId)
695
+ : (await this.host.installGitGuard(remoteId, projectId, {
696
+ homeName: (0, node_os_1.hostname)(),
697
+ reason: 'pc-git',
698
+ })).warning;
241
699
  }
242
700
  state.step = 'done';
243
701
  state.warning = null;
@@ -248,19 +706,22 @@ exports.RemoteFileSyncService = RemoteFileSyncService;
248
706
  exports.RemoteFileSyncService = RemoteFileSyncService = __decorate([
249
707
  (0, common_1.Injectable)(),
250
708
  __param(3, (0, common_1.Inject)(remote_health_port_1.REMOTE_HEALTH_PORT)),
709
+ __param(7, (0, common_1.Inject)(storage_interface_1.STORAGE_SERVICE)),
251
710
  __metadata("design:paramtypes", [file_sync_service_1.FileSyncService,
252
711
  remote_host_client_1.RemoteHostClient,
253
712
  remote_bindings_service_1.RemoteBindingsService, Object, file_sync_managed_exclusions_store_1.FileSyncManagedExclusionsStore,
254
713
  git_service_1.GitService,
255
- home_git_guard_service_1.HomeGitGuardService])
714
+ home_git_guard_service_1.HomeGitGuardService, Object, file_sync_auto_fix_store_1.FileSyncAutoFixStore,
715
+ file_sync_failures_service_1.FileSyncFailuresService,
716
+ git_owner_store_1.GitOwnerStore])
256
717
  ], RemoteFileSyncService);
257
- function folderError(config, status) {
718
+ function folderError(config, status, includeFailedFiles = true) {
258
719
  if (config.paused)
259
720
  return 'folder is paused';
260
721
  const message = status.error?.trim();
261
722
  if (message)
262
723
  return message;
263
- if ((status.errors ?? 0) > 0)
724
+ if (includeFailedFiles && (status.errors ?? 0) > 0)
264
725
  return `${status.errors} files failed`;
265
726
  if (status.state === 'error')
266
727
  return 'folder is in an error state';