devchain-cli 0.24.0 → 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 (432) hide show
  1. package/README.md +1 -4
  2. package/dist/build-info.json +2 -2
  3. package/dist/cli.js +66 -11
  4. package/dist/host-install/devchain-host-bootstrap.tgz +0 -0
  5. package/dist/host-install/pins.json +31 -2
  6. package/dist/lib/exclusive-run.js +51 -3
  7. package/dist/lib/git-owner.js +284 -0
  8. package/dist/lib/host-install.js +19 -151
  9. package/dist/lib/local-api.js +175 -0
  10. package/dist/server/common/config/env.config.d.ts +3 -0
  11. package/dist/server/common/config/env.config.js +1 -0
  12. package/dist/server/common/config/env.config.js.map +1 -1
  13. package/dist/server/common/config/host-image.json +3 -3
  14. package/dist/server/common/constants/syncthing-markers.d.ts +1 -0
  15. package/dist/server/common/constants/syncthing-markers.js +6 -1
  16. package/dist/server/common/constants/syncthing-markers.js.map +1 -1
  17. package/dist/server/common/filesystem/vm-home-path.d.ts +11 -0
  18. package/dist/server/common/filesystem/vm-home-path.js +48 -0
  19. package/dist/server/common/filesystem/vm-home-path.js.map +1 -0
  20. package/dist/server/common/process-ids-env.d.ts +1 -0
  21. package/dist/server/common/process-ids-env.js +13 -0
  22. package/dist/server/common/process-ids-env.js.map +1 -0
  23. package/dist/server/common/test/api-test-app.d.ts +12 -0
  24. package/dist/server/common/test/api-test-app.js +76 -0
  25. package/dist/server/common/test/api-test-app.js.map +1 -0
  26. package/dist/server/common/test/app-bootstrap.helper.js +8 -1
  27. package/dist/server/common/test/app-bootstrap.helper.js.map +1 -1
  28. package/dist/server/common/test/fake-bootstrap.server.js +3 -2
  29. package/dist/server/common/test/fake-bootstrap.server.js.map +1 -1
  30. package/dist/server/common/test/fake-docker-engine.server.d.ts +24 -6
  31. package/dist/server/common/test/fake-docker-engine.server.js +123 -25
  32. package/dist/server/common/test/fake-docker-engine.server.js.map +1 -1
  33. package/dist/server/common/test/fake-proxmox.server.js +4 -4
  34. package/dist/server/common/test/fake-ssh.server.js +6 -6
  35. package/dist/server/common/test/fake-ssh.server.js.map +1 -1
  36. package/dist/server/common/test/test-database.helper.d.ts +6 -0
  37. package/dist/server/common/test/test-database.helper.js +29 -0
  38. package/dist/server/common/test/test-database.helper.js.map +1 -0
  39. package/dist/server/modules/cloud-tunnel/services/mobile-chat-rpc.service.d.ts +1 -0
  40. package/dist/server/modules/cloud-tunnel/services/mobile-chat-rpc.service.js +9 -3
  41. package/dist/server/modules/cloud-tunnel/services/mobile-chat-rpc.service.js.map +1 -1
  42. package/dist/server/modules/cloud-tunnel/services/mobile-transcript-projection.d.ts +6 -0
  43. package/dist/server/modules/cloud-tunnel/services/mobile-transcript-projection.js +88 -0
  44. package/dist/server/modules/cloud-tunnel/services/mobile-transcript-projection.js.map +1 -0
  45. package/dist/server/modules/cloud-tunnel/services/tunnel-event-forwarder.service.js +7 -1
  46. package/dist/server/modules/cloud-tunnel/services/tunnel-event-forwarder.service.js.map +1 -1
  47. package/dist/server/modules/core/controllers/docker-archive.d.ts +9 -2
  48. package/dist/server/modules/core/controllers/docker-archive.js +22 -6
  49. package/dist/server/modules/core/controllers/docker-archive.js.map +1 -1
  50. package/dist/server/modules/core/controllers/docker-engine.client.d.ts +2 -1
  51. package/dist/server/modules/core/controllers/docker-engine.client.js +57 -4
  52. package/dist/server/modules/core/controllers/docker-engine.client.js.map +1 -1
  53. package/dist/server/modules/core/controllers/docker-image-metadata.d.ts +248 -0
  54. package/dist/server/modules/core/controllers/docker-image-metadata.js +161 -0
  55. package/dist/server/modules/core/controllers/docker-image-metadata.js.map +1 -0
  56. package/dist/server/modules/core/controllers/docker-settings.d.ts +13 -2
  57. package/dist/server/modules/core/controllers/docker-settings.js +49 -11
  58. package/dist/server/modules/core/controllers/docker-settings.js.map +1 -1
  59. package/dist/server/modules/core/controllers/host-user-identity.d.ts +34 -0
  60. package/dist/server/modules/core/controllers/host-user-identity.js +23 -0
  61. package/dist/server/modules/core/controllers/host-user-identity.js.map +1 -0
  62. package/dist/server/modules/core/controllers/runtime.controller.d.ts +11 -4
  63. package/dist/server/modules/core/controllers/runtime.controller.js +2 -0
  64. package/dist/server/modules/core/controllers/runtime.controller.js.map +1 -1
  65. package/dist/server/modules/file-sync/build-exclusion-suggestions.d.ts +15 -0
  66. package/dist/server/modules/file-sync/build-exclusion-suggestions.js +402 -0
  67. package/dist/server/modules/file-sync/build-exclusion-suggestions.js.map +1 -0
  68. package/dist/server/modules/file-sync/file-sync-auto-fix.dto.d.ts +102 -0
  69. package/dist/server/modules/file-sync/file-sync-auto-fix.dto.js +22 -0
  70. package/dist/server/modules/file-sync/file-sync-auto-fix.dto.js.map +1 -0
  71. package/dist/server/modules/file-sync/file-sync-auto-fix.store.d.ts +12 -0
  72. package/dist/server/modules/file-sync/file-sync-auto-fix.store.js +73 -0
  73. package/dist/server/modules/file-sync/file-sync-auto-fix.store.js.map +1 -0
  74. package/dist/server/modules/file-sync/file-sync-conflicts.d.ts +3 -0
  75. package/dist/server/modules/file-sync/file-sync-conflicts.js +191 -0
  76. package/dist/server/modules/file-sync/file-sync-conflicts.js.map +1 -0
  77. package/dist/server/modules/file-sync/file-sync-ignores.store.d.ts +5 -1
  78. package/dist/server/modules/file-sync/file-sync-ignores.store.js +39 -8
  79. package/dist/server/modules/file-sync/file-sync-ignores.store.js.map +1 -1
  80. package/dist/server/modules/file-sync/file-sync.controller.d.ts +1 -3
  81. package/dist/server/modules/file-sync/file-sync.controller.js +5 -14
  82. package/dist/server/modules/file-sync/file-sync.controller.js.map +1 -1
  83. package/dist/server/modules/file-sync/file-sync.dto.d.ts +220 -1
  84. package/dist/server/modules/file-sync/file-sync.dto.js +70 -4
  85. package/dist/server/modules/file-sync/file-sync.dto.js.map +1 -1
  86. package/dist/server/modules/file-sync/file-sync.module.js +16 -1
  87. package/dist/server/modules/file-sync/file-sync.module.js.map +1 -1
  88. package/dist/server/modules/file-sync/file-sync.service.d.ts +20 -5
  89. package/dist/server/modules/file-sync/file-sync.service.js +173 -37
  90. package/dist/server/modules/file-sync/file-sync.service.js.map +1 -1
  91. package/dist/server/modules/file-sync/git-guard.dto.d.ts +61 -0
  92. package/dist/server/modules/file-sync/git-guard.dto.js +26 -0
  93. package/dist/server/modules/file-sync/git-guard.dto.js.map +1 -0
  94. package/dist/server/modules/file-sync/home-git-guard.service.d.ts +4 -2
  95. package/dist/server/modules/file-sync/home-git-guard.service.js +50 -25
  96. package/dist/server/modules/file-sync/home-git-guard.service.js.map +1 -1
  97. package/dist/server/modules/file-sync/host-git-guard.controller.d.ts +10 -0
  98. package/dist/server/modules/file-sync/host-git-guard.controller.js +64 -0
  99. package/dist/server/modules/file-sync/host-git-guard.controller.js.map +1 -0
  100. package/dist/server/modules/file-sync/host-git-index.controller.d.ts +9 -0
  101. package/dist/server/modules/file-sync/host-git-index.controller.js +67 -0
  102. package/dist/server/modules/file-sync/host-git-index.controller.js.map +1 -0
  103. package/dist/server/modules/file-sync/host-sync.controller.d.ts +15 -2
  104. package/dist/server/modules/file-sync/host-sync.controller.js +122 -14
  105. package/dist/server/modules/file-sync/host-sync.controller.js.map +1 -1
  106. package/dist/server/modules/file-sync/ignore-pattern-matcher.d.ts +30 -0
  107. package/dist/server/modules/file-sync/ignore-pattern-matcher.js +183 -0
  108. package/dist/server/modules/file-sync/ignore-pattern-matcher.js.map +1 -0
  109. package/dist/server/modules/file-sync/project-repository.d.ts +2 -0
  110. package/dist/server/modules/file-sync/project-repository.js +20 -0
  111. package/dist/server/modules/file-sync/project-repository.js.map +1 -0
  112. package/dist/server/modules/file-sync/sync-chown.dto.d.ts +99 -0
  113. package/dist/server/modules/file-sync/sync-chown.dto.js +46 -0
  114. package/dist/server/modules/file-sync/sync-chown.dto.js.map +1 -0
  115. package/dist/server/modules/file-sync/sync-chown.service.d.ts +12 -0
  116. package/dist/server/modules/file-sync/sync-chown.service.js +230 -0
  117. package/dist/server/modules/file-sync/sync-chown.service.js.map +1 -0
  118. package/dist/server/modules/file-sync/sync-path-inspection.dto.d.ts +626 -0
  119. package/dist/server/modules/file-sync/sync-path-inspection.dto.js +108 -0
  120. package/dist/server/modules/file-sync/sync-path-inspection.dto.js.map +1 -0
  121. package/dist/server/modules/file-sync/sync-path-inspector.d.ts +12 -0
  122. package/dist/server/modules/file-sync/sync-path-inspector.js +413 -0
  123. package/dist/server/modules/file-sync/sync-path-inspector.js.map +1 -0
  124. package/dist/server/modules/file-sync/syncthing-rest.client.d.ts +1 -1
  125. package/dist/server/modules/file-sync/syncthing-rest.client.js +9 -4
  126. package/dist/server/modules/file-sync/syncthing-rest.client.js.map +1 -1
  127. package/dist/server/modules/file-sync/testing/fake-file-sync.service.d.ts +28 -5
  128. package/dist/server/modules/file-sync/testing/fake-file-sync.service.js +68 -5
  129. package/dist/server/modules/file-sync/testing/fake-file-sync.service.js.map +1 -1
  130. package/dist/server/modules/file-sync/translate-git-ignore-rule.d.ts +3 -0
  131. package/dist/server/modules/file-sync/translate-git-ignore-rule.js +30 -0
  132. package/dist/server/modules/file-sync/translate-git-ignore-rule.js.map +1 -0
  133. package/dist/server/modules/hooks/dtos/hook-event.dto.d.ts +86 -0
  134. package/dist/server/modules/hooks/dtos/hook-event.dto.js +15 -1
  135. package/dist/server/modules/hooks/dtos/hook-event.dto.js.map +1 -1
  136. package/dist/server/modules/hooks/services/hooks-config.service.js +29 -0
  137. package/dist/server/modules/hooks/services/hooks-config.service.js.map +1 -1
  138. package/dist/server/modules/hooks/services/hooks.service.d.ts +5 -1
  139. package/dist/server/modules/hooks/services/hooks.service.js +27 -5
  140. package/dist/server/modules/hooks/services/hooks.service.js.map +1 -1
  141. package/dist/server/modules/hooks/services/pending-ask-user-question.service.d.ts +2 -2
  142. package/dist/server/modules/hooks/services/pending-ask-user-question.service.js +9 -12
  143. package/dist/server/modules/hooks/services/pending-ask-user-question.service.js.map +1 -1
  144. package/dist/server/modules/mcp/services/handlers/skill-tools.js +2 -2
  145. package/dist/server/modules/mcp/services/handlers/skill-tools.js.map +1 -1
  146. package/dist/server/modules/metrics/services/metrics.service.js +1 -1
  147. package/dist/server/modules/metrics/services/metrics.service.js.map +1 -1
  148. package/dist/server/modules/providers/adapters/claude.adapter.d.ts +3 -1
  149. package/dist/server/modules/providers/adapters/claude.adapter.js +4 -1
  150. package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
  151. package/dist/server/modules/remotes/connect-choices.controller.d.ts +9 -0
  152. package/dist/server/modules/remotes/connect-choices.controller.js +54 -0
  153. package/dist/server/modules/remotes/connect-choices.controller.js.map +1 -0
  154. package/dist/server/modules/remotes/connect-choices.dto.d.ts +52 -0
  155. package/dist/server/modules/remotes/connect-choices.dto.js +19 -0
  156. package/dist/server/modules/remotes/connect-choices.dto.js.map +1 -0
  157. package/dist/server/modules/remotes/connect-choices.store.d.ts +12 -0
  158. package/dist/server/modules/remotes/connect-choices.store.js +95 -0
  159. package/dist/server/modules/remotes/connect-choices.store.js.map +1 -0
  160. package/dist/server/modules/remotes/controllers/remotes.controller.d.ts +3 -0
  161. package/dist/server/modules/remotes/controllers/remotes.controller.js +9 -1
  162. package/dist/server/modules/remotes/controllers/remotes.controller.js.map +1 -1
  163. package/dist/server/modules/remotes/docker/docker-copy-back.d.ts +1 -0
  164. package/dist/server/modules/remotes/docker/docker-copy-back.js +70 -31
  165. package/dist/server/modules/remotes/docker/docker-copy-back.js.map +1 -1
  166. package/dist/server/modules/remotes/docker/docker-handoff.d.ts +5 -3
  167. package/dist/server/modules/remotes/docker/docker-handoff.js +221 -53
  168. package/dist/server/modules/remotes/docker/docker-handoff.js.map +1 -1
  169. package/dist/server/modules/remotes/docker/docker-handoff.store.d.ts +5 -0
  170. package/dist/server/modules/remotes/docker/docker-handoff.store.js.map +1 -1
  171. package/dist/server/modules/remotes/docker/docker-plan-fit.d.ts +4 -1
  172. package/dist/server/modules/remotes/docker/docker-plan-fit.js +19 -6
  173. package/dist/server/modules/remotes/docker/docker-plan-fit.js.map +1 -1
  174. package/dist/server/modules/remotes/docker/docker-plan-policy.d.ts +6 -2
  175. package/dist/server/modules/remotes/docker/docker-plan-policy.js +191 -38
  176. package/dist/server/modules/remotes/docker/docker-plan-policy.js.map +1 -1
  177. package/dist/server/modules/remotes/docker/docker-plan-source.service.d.ts +55 -6
  178. package/dist/server/modules/remotes/docker/docker-plan-source.service.js +284 -52
  179. package/dist/server/modules/remotes/docker/docker-plan-source.service.js.map +1 -1
  180. package/dist/server/modules/remotes/docker/docker-plan.controller.d.ts +2 -1
  181. package/dist/server/modules/remotes/docker/docker-plan.controller.js +15 -1
  182. package/dist/server/modules/remotes/docker/docker-plan.controller.js.map +1 -1
  183. package/dist/server/modules/remotes/docker/docker-plan.dto.d.ts +47 -1
  184. package/dist/server/modules/remotes/docker/docker-plan.dto.js +1 -0
  185. package/dist/server/modules/remotes/docker/docker-plan.dto.js.map +1 -1
  186. package/dist/server/modules/remotes/docker/docker-plan.service.d.ts +15 -6
  187. package/dist/server/modules/remotes/docker/docker-plan.service.js +83 -20
  188. package/dist/server/modules/remotes/docker/docker-plan.service.js.map +1 -1
  189. package/dist/server/modules/remotes/docker/docker-project-compose.d.ts +7 -0
  190. package/dist/server/modules/remotes/docker/docker-project-compose.js +30 -0
  191. package/dist/server/modules/remotes/docker/docker-project-compose.js.map +1 -0
  192. package/dist/server/modules/remotes/dtos/remote.dto.d.ts +2 -0
  193. package/dist/server/modules/remotes/dtos/remote.dto.js.map +1 -1
  194. package/dist/server/modules/remotes/git-owner.store.d.ts +13 -0
  195. package/dist/server/modules/remotes/git-owner.store.js +69 -0
  196. package/dist/server/modules/remotes/git-owner.store.js.map +1 -0
  197. package/dist/server/modules/remotes/host/host-docker.controller.d.ts +14 -1
  198. package/dist/server/modules/remotes/host/host-docker.controller.js +28 -9
  199. package/dist/server/modules/remotes/host/host-docker.controller.js.map +1 -1
  200. package/dist/server/modules/remotes/host/host-docker.dto.d.ts +640 -3
  201. package/dist/server/modules/remotes/host/host-docker.dto.js +66 -9
  202. package/dist/server/modules/remotes/host/host-docker.dto.js.map +1 -1
  203. package/dist/server/modules/remotes/host/host-docker.service.d.ts +18 -6
  204. package/dist/server/modules/remotes/host/host-docker.service.js +184 -81
  205. package/dist/server/modules/remotes/host/host-docker.service.js.map +1 -1
  206. package/dist/server/modules/remotes/host/host-ipv4-routes.d.ts +4 -0
  207. package/dist/server/modules/remotes/host/host-ipv4-routes.js +43 -0
  208. package/dist/server/modules/remotes/host/host-ipv4-routes.js.map +1 -0
  209. package/dist/server/modules/remotes/host-image.d.ts +1 -1
  210. package/dist/server/modules/remotes/host-image.js +1 -1
  211. package/dist/server/modules/remotes/host-install/host-install-block.d.ts +1 -0
  212. package/dist/server/modules/remotes/host-install/host-install-block.js +76 -2
  213. package/dist/server/modules/remotes/host-install/host-install-block.js.map +1 -1
  214. package/dist/server/modules/remotes/host-install/host-install.service.d.ts +0 -1
  215. package/dist/server/modules/remotes/host-install/host-install.service.js +9 -26
  216. package/dist/server/modules/remotes/host-install/host-install.service.js.map +1 -1
  217. package/dist/server/modules/remotes/operations/attach.operation.d.ts +5 -1
  218. package/dist/server/modules/remotes/operations/attach.operation.js +41 -5
  219. package/dist/server/modules/remotes/operations/attach.operation.js.map +1 -1
  220. package/dist/server/modules/remotes/operations/claim.operation.d.ts +1 -0
  221. package/dist/server/modules/remotes/operations/claim.operation.js +6 -1
  222. package/dist/server/modules/remotes/operations/claim.operation.js.map +1 -1
  223. package/dist/server/modules/remotes/operations/detach.operation.d.ts +4 -1
  224. package/dist/server/modules/remotes/operations/detach.operation.js +10 -2
  225. package/dist/server/modules/remotes/operations/detach.operation.js.map +1 -1
  226. package/dist/server/modules/remotes/operations/docker-import-inventory.store.d.ts +10 -0
  227. package/dist/server/modules/remotes/operations/docker-import-inventory.store.js +17 -0
  228. package/dist/server/modules/remotes/operations/docker-import-inventory.store.js.map +1 -1
  229. package/dist/server/modules/remotes/operations/file-sync-handoff.d.ts +30 -2
  230. package/dist/server/modules/remotes/operations/file-sync-handoff.js +498 -57
  231. package/dist/server/modules/remotes/operations/file-sync-handoff.js.map +1 -1
  232. package/dist/server/modules/remotes/operations/force-sync.operation.d.ts +39 -0
  233. package/dist/server/modules/remotes/operations/force-sync.operation.js +177 -0
  234. package/dist/server/modules/remotes/operations/force-sync.operation.js.map +1 -0
  235. package/dist/server/modules/remotes/operations/git-owner.dto.d.ts +83 -0
  236. package/dist/server/modules/remotes/operations/git-owner.dto.js +25 -0
  237. package/dist/server/modules/remotes/operations/git-owner.dto.js.map +1 -0
  238. package/dist/server/modules/remotes/operations/git-owner.operation.d.ts +35 -0
  239. package/dist/server/modules/remotes/operations/git-owner.operation.js +216 -0
  240. package/dist/server/modules/remotes/operations/git-owner.operation.js.map +1 -0
  241. package/dist/server/modules/remotes/operations/remote-host.client.d.ts +72 -8
  242. package/dist/server/modules/remotes/operations/remote-host.client.js +162 -28
  243. package/dist/server/modules/remotes/operations/remote-host.client.js.map +1 -1
  244. package/dist/server/modules/remotes/operations/remote-operation.dto.d.ts +19 -0
  245. package/dist/server/modules/remotes/operations/remote-operation.dto.js +5 -1
  246. package/dist/server/modules/remotes/operations/remote-operation.dto.js.map +1 -1
  247. package/dist/server/modules/remotes/operations/remote-operation.runner.d.ts +3 -1
  248. package/dist/server/modules/remotes/operations/remote-operation.runner.js +59 -21
  249. package/dist/server/modules/remotes/operations/remote-operation.runner.js.map +1 -1
  250. package/dist/server/modules/remotes/operations/remote-operation.types.d.ts +4 -1
  251. package/dist/server/modules/remotes/operations/remote-operation.types.js +4 -2
  252. package/dist/server/modules/remotes/operations/remote-operation.types.js.map +1 -1
  253. package/dist/server/modules/remotes/operations/remote-operations.controller.d.ts +5 -0
  254. package/dist/server/modules/remotes/operations/remote-operations.controller.js +56 -0
  255. package/dist/server/modules/remotes/operations/remote-operations.controller.js.map +1 -1
  256. package/dist/server/modules/remotes/operations/remote-operations.service.d.ts +16 -1
  257. package/dist/server/modules/remotes/operations/remote-operations.service.js +155 -5
  258. package/dist/server/modules/remotes/operations/remote-operations.service.js.map +1 -1
  259. package/dist/server/modules/remotes/operations/update-logins.operation.d.ts +1 -3
  260. package/dist/server/modules/remotes/operations/update-logins.operation.js +3 -18
  261. package/dist/server/modules/remotes/operations/update-logins.operation.js.map +1 -1
  262. package/dist/server/modules/remotes/ports/remote-health.port.d.ts +2 -0
  263. package/dist/server/modules/remotes/ports/remote-health.port.js.map +1 -1
  264. package/dist/server/modules/remotes/remotes.module.js +18 -0
  265. package/dist/server/modules/remotes/remotes.module.js.map +1 -1
  266. package/dist/server/modules/remotes/replica/__fixtures__/replica-seed.d.ts +2 -2
  267. package/dist/server/modules/remotes/replica/__fixtures__/replica-seed.js +2 -13
  268. package/dist/server/modules/remotes/replica/__fixtures__/replica-seed.js.map +1 -1
  269. package/dist/server/modules/remotes/services/remote-health.service.js +5 -0
  270. package/dist/server/modules/remotes/services/remote-health.service.js.map +1 -1
  271. package/dist/server/modules/remotes/sync/file-sync-failures.service.d.ts +18 -0
  272. package/dist/server/modules/remotes/sync/file-sync-failures.service.js +157 -0
  273. package/dist/server/modules/remotes/sync/file-sync-failures.service.js.map +1 -0
  274. package/dist/server/modules/remotes/sync/file-sync-pattern-preview.service.d.ts +14 -0
  275. package/dist/server/modules/remotes/sync/file-sync-pattern-preview.service.js +99 -0
  276. package/dist/server/modules/remotes/sync/file-sync-pattern-preview.service.js.map +1 -0
  277. package/dist/server/modules/remotes/sync/file-sync-suggestions.service.d.ts +15 -0
  278. package/dist/server/modules/remotes/sync/file-sync-suggestions.service.js +90 -0
  279. package/dist/server/modules/remotes/sync/file-sync-suggestions.service.js.map +1 -0
  280. package/dist/server/modules/remotes/sync/project-file-sync.controller.d.ts +29 -0
  281. package/dist/server/modules/remotes/sync/project-file-sync.controller.js +160 -0
  282. package/dist/server/modules/remotes/sync/project-file-sync.controller.js.map +1 -0
  283. package/dist/server/modules/remotes/sync/remote-file-sync.dto.d.ts +72 -0
  284. package/dist/server/modules/remotes/sync/remote-file-sync.dto.js +16 -0
  285. package/dist/server/modules/remotes/sync/remote-file-sync.dto.js.map +1 -0
  286. package/dist/server/modules/remotes/sync/remote-file-sync.service.d.ts +32 -1
  287. package/dist/server/modules/remotes/sync/remote-file-sync.service.js +499 -38
  288. package/dist/server/modules/remotes/sync/remote-file-sync.service.js.map +1 -1
  289. package/dist/server/modules/remotes/sync/remote-live-sync.service.d.ts +2 -0
  290. package/dist/server/modules/remotes/sync/remote-live-sync.service.js +26 -12
  291. package/dist/server/modules/remotes/sync/remote-live-sync.service.js.map +1 -1
  292. package/dist/server/modules/remotes/vm-user-identity.d.ts +29 -0
  293. package/dist/server/modules/remotes/vm-user-identity.js +45 -0
  294. package/dist/server/modules/remotes/vm-user-identity.js.map +1 -0
  295. package/dist/server/modules/runtime-context-capture/codex-plugin-profile-materializer.service.js +5 -1
  296. package/dist/server/modules/runtime-context-capture/codex-plugin-profile-materializer.service.js.map +1 -1
  297. package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.d.ts +5 -0
  298. package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js +9 -0
  299. package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js.map +1 -1
  300. package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.d.ts +1 -0
  301. package/dist/server/modules/session-reader/adapters/utils/coalesce-turns.js +1 -0
  302. package/dist/server/modules/session-reader/adapters/utils/coalesce-turns.js.map +1 -1
  303. package/dist/server/modules/session-reader/builders/semantic-step-extractor.js +1 -1
  304. package/dist/server/modules/session-reader/builders/semantic-step-extractor.js.map +1 -1
  305. package/dist/server/modules/session-reader/controllers/session-reader.controller.js +1 -1
  306. package/dist/server/modules/session-reader/controllers/session-reader.controller.js.map +1 -1
  307. package/dist/server/modules/session-reader/data/pricing.json +89 -12
  308. package/dist/server/modules/session-reader/dtos/unified-message.types.d.ts +1 -0
  309. package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.d.ts +3 -0
  310. package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js +10 -1
  311. package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js.map +1 -1
  312. package/dist/server/modules/session-reader/parsers/claude-turn-evidence.d.ts +11 -0
  313. package/dist/server/modules/session-reader/parsers/claude-turn-evidence.js +50 -0
  314. package/dist/server/modules/session-reader/parsers/claude-turn-evidence.js.map +1 -0
  315. package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js +8 -2
  316. package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js.map +1 -1
  317. package/dist/server/modules/session-reader/services/bounded-anchor-proof.d.ts +7 -0
  318. package/dist/server/modules/session-reader/services/bounded-anchor-proof.js +31 -2
  319. package/dist/server/modules/session-reader/services/bounded-anchor-proof.js.map +1 -1
  320. package/dist/server/modules/session-reader/services/session-cache.service.d.ts +8 -2
  321. package/dist/server/modules/session-reader/services/session-cache.service.js +80 -21
  322. package/dist/server/modules/session-reader/services/session-cache.service.js.map +1 -1
  323. package/dist/server/modules/session-reader/services/session-reader.service.d.ts +4 -0
  324. package/dist/server/modules/session-reader/services/session-reader.service.js +40 -8
  325. package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -1
  326. package/dist/server/modules/session-reader/services/transcript-cursor.d.ts +16 -4
  327. package/dist/server/modules/session-reader/services/transcript-cursor.js +65 -6
  328. package/dist/server/modules/session-reader/services/transcript-cursor.js.map +1 -1
  329. package/dist/server/modules/session-reader/services/transcript-serialization.js +2 -1
  330. package/dist/server/modules/session-reader/services/transcript-serialization.js.map +1 -1
  331. package/dist/server/modules/session-reader/services/transcript-turn-state.d.ts +4 -0
  332. package/dist/server/modules/session-reader/services/transcript-turn-state.js +20 -0
  333. package/dist/server/modules/session-reader/services/transcript-turn-state.js.map +1 -0
  334. package/dist/server/modules/session-reader/services/transcript-watcher.service.d.ts +4 -1
  335. package/dist/server/modules/session-reader/services/transcript-watcher.service.js +43 -18
  336. package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -1
  337. package/dist/server/modules/sessions/services/provider-launch-config/provider-launch-config.service.js +5 -0
  338. package/dist/server/modules/sessions/services/provider-launch-config/provider-launch-config.service.js.map +1 -1
  339. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.d.ts +36 -17
  340. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js +26 -9
  341. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js.map +1 -1
  342. package/dist/server/modules/sessions/services/session-runtime/ensure-mcp-readiness.d.ts +19 -0
  343. package/dist/server/modules/sessions/services/session-runtime/ensure-mcp-readiness.js +26 -0
  344. package/dist/server/modules/sessions/services/session-runtime/ensure-mcp-readiness.js.map +1 -0
  345. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js +10 -18
  346. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js.map +1 -1
  347. package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.d.ts +5 -1
  348. package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.js +20 -2
  349. package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.js.map +1 -1
  350. package/dist/server/modules/settings/local/delegates/skills-settings.delegate.d.ts +6 -0
  351. package/dist/server/modules/settings/local/delegates/skills-settings.delegate.js +26 -0
  352. package/dist/server/modules/settings/local/delegates/skills-settings.delegate.js.map +1 -1
  353. package/dist/server/modules/settings/services/settings.service.d.ts +3 -1
  354. package/dist/server/modules/settings/services/settings.service.js +6 -0
  355. package/dist/server/modules/settings/services/settings.service.js.map +1 -1
  356. package/dist/server/modules/skills/adapters/anthropic-skill-source.adapter.js +5 -5
  357. package/dist/server/modules/skills/adapters/anthropic-skill-source.adapter.js.map +1 -1
  358. package/dist/server/modules/skills/adapters/github-directory-skill-source.adapter.js +5 -5
  359. package/dist/server/modules/skills/adapters/github-directory-skill-source.adapter.js.map +1 -1
  360. package/dist/server/modules/skills/adapters/github-skill-source.base.js +4 -3
  361. package/dist/server/modules/skills/adapters/github-skill-source.base.js.map +1 -1
  362. package/dist/server/modules/skills/adapters/local-skill-source.adapter.js +5 -5
  363. package/dist/server/modules/skills/adapters/local-skill-source.adapter.js.map +1 -1
  364. package/dist/server/modules/skills/adapters/microsoft-skill-source.adapter.js +5 -5
  365. package/dist/server/modules/skills/adapters/microsoft-skill-source.adapter.js.map +1 -1
  366. package/dist/server/modules/skills/adapters/openai-skill-source.adapter.js +5 -5
  367. package/dist/server/modules/skills/adapters/openai-skill-source.adapter.js.map +1 -1
  368. package/dist/server/modules/skills/adapters/skill-source.adapter.d.ts +5 -0
  369. package/dist/server/modules/skills/adapters/skill-sync-temp-cleanup.d.ts +5 -0
  370. package/dist/server/modules/skills/adapters/skill-sync-temp-cleanup.js +75 -0
  371. package/dist/server/modules/skills/adapters/skill-sync-temp-cleanup.js.map +1 -0
  372. package/dist/server/modules/skills/adapters/trailofbits-skill-source.adapter.js +5 -5
  373. package/dist/server/modules/skills/adapters/trailofbits-skill-source.adapter.js.map +1 -1
  374. package/dist/server/modules/skills/adapters/vercel-skill-source.adapter.js +5 -5
  375. package/dist/server/modules/skills/adapters/vercel-skill-source.adapter.js.map +1 -1
  376. package/dist/server/modules/skills/controllers/skills.controller.js +2 -2
  377. package/dist/server/modules/skills/controllers/skills.controller.js.map +1 -1
  378. package/dist/server/modules/skills/services/skill-source-lifecycle.service.d.ts +3 -3
  379. package/dist/server/modules/skills/services/skill-source-lifecycle.service.js +9 -4
  380. package/dist/server/modules/skills/services/skill-source-lifecycle.service.js.map +1 -1
  381. package/dist/server/modules/skills/services/skill-sync.service.d.ts +3 -3
  382. package/dist/server/modules/skills/services/skill-sync.service.js +32 -6
  383. package/dist/server/modules/skills/services/skill-sync.service.js.map +1 -1
  384. package/dist/server/modules/skills/services/skill-sync.types.d.ts +3 -0
  385. package/dist/server/modules/storage/db/schema.d.ts +2 -2
  386. package/dist/server/modules/storage/db/schema.js +2 -0
  387. package/dist/server/modules/storage/db/schema.js.map +1 -1
  388. package/dist/server/modules/storage/models/domain.models.d.ts +1 -1
  389. package/dist/server/modules/storage/models/domain.models.js +2 -0
  390. package/dist/server/modules/storage/models/domain.models.js.map +1 -1
  391. package/dist/server/modules/teams/storage/teams.store.d.ts +1 -0
  392. package/dist/server/modules/teams/storage/teams.store.js +19 -3
  393. package/dist/server/modules/teams/storage/teams.store.js.map +1 -1
  394. package/dist/server/modules/terminal/gateways/terminal.gateway.d.ts +3 -1
  395. package/dist/server/modules/terminal/gateways/terminal.gateway.js +9 -4
  396. package/dist/server/modules/terminal/gateways/terminal.gateway.js.map +1 -1
  397. package/dist/server/modules/terminal/services/session-turn-signals.d.ts +18 -0
  398. package/dist/server/modules/terminal/services/session-turn-signals.js +6 -0
  399. package/dist/server/modules/terminal/services/session-turn-signals.js.map +1 -0
  400. package/dist/server/modules/terminal/services/terminal-activity.service.d.ts +32 -5
  401. package/dist/server/modules/terminal/services/terminal-activity.service.js +322 -40
  402. package/dist/server/modules/terminal/services/terminal-activity.service.js.map +1 -1
  403. package/dist/server/modules/terminal/services/terminal-io/lifecycle.js +15 -3
  404. package/dist/server/modules/terminal/services/terminal-io/lifecycle.js.map +1 -1
  405. package/dist/server/modules/terminal/terminal.module.js +2 -0
  406. package/dist/server/modules/terminal/terminal.module.js.map +1 -1
  407. package/dist/server/modules/terminal/utils/ansi-sanitizer.d.ts +0 -1
  408. package/dist/server/modules/terminal/utils/ansi-sanitizer.js +0 -4
  409. package/dist/server/modules/terminal/utils/ansi-sanitizer.js.map +1 -1
  410. package/dist/server/templates/teams-dev.json +14 -14
  411. package/dist/server/test-setup-node.js +4 -3
  412. package/dist/server/test-setup-node.js.map +1 -1
  413. package/dist/server/test-setup.js +7 -0
  414. package/dist/server/test-setup.js.map +1 -1
  415. package/dist/server/tsconfig.tsbuildinfo +1 -1
  416. package/dist/server/ui/assets/{ExternalRichEditor--kNXs-Eb.js → ExternalRichEditor-CIZPhssf.js} +24 -24
  417. package/dist/server/ui/assets/ReviewDetailPage-winyLSQ7.js +6 -0
  418. package/dist/server/ui/assets/ReviewsPage-BYKBReMp.js +6 -0
  419. package/dist/server/ui/assets/index-CiDJX6Hn.js +1191 -0
  420. package/dist/server/ui/assets/index-f1WOAqXB.css +32 -0
  421. package/dist/server/ui/assets/useReviewSubscription-v3Cto0y4.js +69 -0
  422. package/dist/server/ui/index.html +2 -2
  423. package/dist/templates/teams-dev.json +14 -14
  424. package/package.json +15 -1
  425. package/dist/server/modules/reviews/utils/notification-formatter.d.ts +0 -12
  426. package/dist/server/modules/reviews/utils/notification-formatter.js +0 -60
  427. package/dist/server/modules/reviews/utils/notification-formatter.js.map +0 -1
  428. package/dist/server/ui/assets/ReviewDetailPage-DMgP5fKn.js +0 -6
  429. package/dist/server/ui/assets/ReviewsPage-CkyFUJr6.js +0 -6
  430. package/dist/server/ui/assets/index-CVUaAlP4.css +0 -32
  431. package/dist/server/ui/assets/index-CqRufKpA.js +0 -994
  432. package/dist/server/ui/assets/useReviewSubscription-CU70CzwG.js +0 -69
package/README.md CHANGED
@@ -49,9 +49,7 @@ DevChain opens in your browser:
49
49
 
50
50
  ## Remote VMs
51
51
 
52
- <p align="center">
53
- <a href="https://devchain.cc/features/remote-vms.mp4"><img src=".github/assets/remote-vms-poster.jpg" alt="Remote VMs: watch the 42-second demo" width="100%"></a>
54
- </p>
52
+ https://github.com/user-attachments/assets/5ec85c19-f407-43bc-bb44-4da7f899702f
55
53
 
56
54
  Move a project to your own VM and keep working from the same app. The agents keep running when your laptop sleeps, and the mobile app can tell you when the work is done.
57
55
 
@@ -61,7 +59,6 @@ Move a project to your own VM and keep working from the same app. The agents kee
61
59
 
62
60
  Before you start:
63
61
 
64
- - Use a LAN or VPN between your PC and the VM. Your PC reaches every VM with an API key, over TLS pinned to the VM's own certificate ([details](https://devchain.cc/releases/0.24.0/#secure-connection-to-every-vm)).
65
62
  - Install [Syncthing v2](https://syncthing.net) on your PC. DevChain installs it on the VM.
66
63
  - For your own VM, use a dedicated headless VM: Ubuntu 22.04+ or Debian 12+, amd64, and at least 4 GiB of RAM.
67
64
  - To follow a VM's projects in the mobile app, sign the VM in to DevChain Cloud from the **Cloud** page.
@@ -1,5 +1,5 @@
1
1
  {
2
- "commit": "bc64d29d68a8d28a4b3bd7acf05592cfa067bab0",
2
+ "commit": "35809da25aca6b5f52514089cf15e83c6ffae5d4",
3
3
  "dirty": false,
4
- "builtAt": "2026-10-01T01:28:10.008Z"
4
+ "builtAt": "2026-10-07T00:16:05.341Z"
5
5
  }
package/dist/cli.js CHANGED
@@ -16,6 +16,8 @@ const { spawn, execSync, execFileSync } = require('child_process');
16
16
  const { pathToFileURL } = require('url');
17
17
  const { InteractiveCLI } = require('./lib/interactive-cli');
18
18
  const { runHostInstallCommand } = require('./lib/host-install');
19
+ const { runGitOwnerCommand } = require('./lib/git-owner');
20
+ const { getLocalApiBaseUrl } = require('./lib/local-api');
19
21
  const { runHostApiKeyReset } = require('./lib/host-api-key');
20
22
  const { QUEUE_NAME, runExclusive } = require('./lib/exclusive-run');
21
23
  const readline = require('readline');
@@ -347,6 +349,30 @@ async function ensureProvidersInDb(baseUrl, detected, log) {
347
349
 
348
350
  async function validateMcpForProviders(baseUrl, cli, opts, log, projectPath) {
349
351
  try {
352
+ // The ensure endpoint matches projectPath exactly against a stored project
353
+ // root, and a folder that is not a registered project can never be
354
+ // registered there — MCP is registered when a session launches or restores.
355
+ let effectiveProjectPath = projectPath;
356
+ if (projectPath) {
357
+ try {
358
+ const byPathRes = await fetchWithTimeout(
359
+ `${baseUrl}/api/projects/by-path?path=${encodeURIComponent(projectPath)}`,
360
+ {},
361
+ 2500,
362
+ );
363
+ if (byPathRes.ok) {
364
+ const project = await byPathRes.json();
365
+ // The stored rootPath avoids trailing-slash or normalization mismatches.
366
+ effectiveProjectPath = project?.rootPath ?? projectPath;
367
+ } else if (byPathRes.status === 404) {
368
+ log('info', 'Startup folder is not a registered project; skipping MCP validation (MCP is registered when a session launches)', { startupPath: projectPath });
369
+ return;
370
+ }
371
+ } catch {
372
+ // Lookup failed (network/timeout): proceed with the raw path as before.
373
+ }
374
+ }
375
+
350
376
  // Fetch all providers
351
377
  const res = await fetch(`${baseUrl}/api/providers`);
352
378
  if (!res.ok) {
@@ -374,7 +400,7 @@ async function validateMcpForProviders(baseUrl, cli, opts, log, projectPath) {
374
400
  const results = [];
375
401
  for (const provider of providers) {
376
402
  try {
377
- const body = projectPath ? JSON.stringify({ projectPath }) : JSON.stringify({});
403
+ const body = effectiveProjectPath ? JSON.stringify({ projectPath: effectiveProjectPath }) : JSON.stringify({});
378
404
  const ensureRes = await fetch(`${baseUrl}/api/providers/${provider.id}/mcp/ensure`, {
379
405
  method: 'POST',
380
406
  headers: { 'Content-Type': 'application/json' },
@@ -1388,6 +1414,34 @@ async function main(argv) {
1388
1414
  });
1389
1415
  });
1390
1416
 
1417
+ const git = program.command('git').description('Move Git control between this PC and the VM');
1418
+ for (const name of ['status', 'take', 'return']) {
1419
+ const command = git
1420
+ .command(name)
1421
+ .description(
1422
+ {
1423
+ status: 'Show Git control and any unfinished switch',
1424
+ take: 'Move Git control to this PC',
1425
+ return: 'Move Git control back to the VM',
1426
+ }[name],
1427
+ )
1428
+ .option('--project <path>', 'Project folder (defaults to the current folder)');
1429
+ if (name === 'take') {
1430
+ command.option('--force', "Stop this project's agent sessions on the VM before the switch");
1431
+ }
1432
+ command.action(async (options) => {
1433
+ process.exitCode = await runGitOwnerCommand(name, options, {
1434
+ getMachineRole,
1435
+ getLocalApiBaseUrl: () =>
1436
+ getLocalApiBaseUrl({
1437
+ readPidFile,
1438
+ isProcessRunning,
1439
+ resolveSharedModuleSpecifier,
1440
+ }),
1441
+ });
1442
+ });
1443
+ }
1444
+
1391
1445
  const host = program.command('host').description('Manage DevChain hosts');
1392
1446
 
1393
1447
  host
@@ -1400,7 +1454,10 @@ async function main(argv) {
1400
1454
  .option('--passphrase-stdin', 'Read the SSH key passphrase from one line of stdin')
1401
1455
  .option('--sudo-password-stdin', 'Read the optional sudo password from one line of stdin')
1402
1456
  .option('--projects <project-ids...>', 'Project ids to include in the disk estimate')
1403
- .option('--no-docker', 'Do not install Docker Engine and Compose on the VM (installed by default)')
1457
+ .option(
1458
+ '--no-docker',
1459
+ 'Do not install Docker Engine and Compose on the VM (installed by default)',
1460
+ )
1404
1461
  .option(
1405
1462
  '--provider-auth <provider=choice>',
1406
1463
  'Provider login choice: skip, generate, or reuse:<entry-id> (repeatable)',
@@ -1408,15 +1465,12 @@ async function main(argv) {
1408
1465
  )
1409
1466
  .action(async (options) => {
1410
1467
  const exitCode = await runHostInstallCommand(options, {
1411
- getLocalApiBaseUrl: async () => {
1412
- const pidData = readPidFile();
1413
- if (!pidData || !isProcessRunning(pidData.pid)) return null;
1414
- const { HostResolver } = await import(resolveSharedModuleSpecifier());
1415
- return HostResolver.buildInternalBaseUrl({
1416
- host: pidData.host || '127.0.0.1',
1417
- port: pidData.port,
1418
- });
1419
- },
1468
+ getLocalApiBaseUrl: () =>
1469
+ getLocalApiBaseUrl({
1470
+ readPidFile,
1471
+ isProcessRunning,
1472
+ resolveSharedModuleSpecifier,
1473
+ }),
1420
1474
  });
1421
1475
  process.exitCode = exitCode;
1422
1476
  });
@@ -1509,6 +1563,7 @@ module.exports = {
1509
1563
  main,
1510
1564
  __test__: {
1511
1565
  waitForHealth,
1566
+ validateMcpForProviders,
1512
1567
  runHostPreflightChecks,
1513
1568
  getDevUiConfig,
1514
1569
  getPreferredDevApiPort,
@@ -5,7 +5,7 @@
5
5
  "bootstrap": {
6
6
  "package": "devchain-host-bootstrap",
7
7
  "version": "0.1.0",
8
- "sha256": "724d4664ff9bc77d5d6be21de949022cb8e3a01cbb8ad525fef971136db65f96"
8
+ "sha256": "647a36e15832c854515835132b80892fffebe4fb0957938541135b0481a39040"
9
9
  },
10
10
  "aptPackages": [
11
11
  "qemu-guest-agent",
@@ -17,6 +17,10 @@
17
17
  "build-essential",
18
18
  "python3",
19
19
  "jq",
20
+ "openssl",
21
+ "procps"
22
+ ],
23
+ "toolPackages": [
20
24
  "ripgrep",
21
25
  "python-is-python3",
22
26
  "python3-pip",
@@ -26,6 +30,31 @@
26
30
  "bsdextrautils",
27
31
  "rsync",
28
32
  "unzip",
29
- "openssl"
33
+ "screen",
34
+ "psmisc",
35
+ "time",
36
+ "lsof",
37
+ "strace",
38
+ "htop",
39
+ "less",
40
+ "nano",
41
+ "vim-tiny",
42
+ "tree",
43
+ "zip",
44
+ "net-tools",
45
+ "iputils-ping",
46
+ "inetutils-telnet",
47
+ "netcat-openbsd",
48
+ "bind9-dnsutils",
49
+ "traceroute",
50
+ "mtr-tiny",
51
+ "tcpdump",
52
+ "socat",
53
+ "fd-find",
54
+ "universal-ctags",
55
+ "gawk",
56
+ "shellcheck",
57
+ "cloc",
58
+ "git-lfs"
30
59
  ]
31
60
  }
@@ -1,11 +1,23 @@
1
1
  const fs = require("node:fs");
2
- const { spawn } = require("node:child_process");
2
+ const { spawn, spawnSync } = require("node:child_process");
3
3
  const { randomBytes } = require("node:crypto");
4
4
  const { homedir, constants } = require("node:os");
5
- const { join, resolve } = require("node:path");
5
+ const { delimiter, join, resolve } = require("node:path");
6
6
 
7
7
  const QUEUE_NAME = /^[a-z0-9][a-z0-9-]{0,63}$/;
8
8
  const SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
9
+ // systemd-run execs the command in place: pid, process group, stdio and exit
10
+ // code stay the same. systemd 258 and later expand $VAR in the arguments
11
+ // unless told not to.
12
+ const SCOPE_ARGS = [
13
+ "--user",
14
+ "--scope",
15
+ "--quiet",
16
+ "--collect",
17
+ "--expand-environment=no",
18
+ "--",
19
+ ];
20
+ let scopeAvailable;
9
21
 
10
22
  function isPid(value) {
11
23
  return Number.isInteger(value) && value > 0;
@@ -150,13 +162,49 @@ function formatWait(ms) {
150
162
  return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
151
163
  }
152
164
 
165
+ function findExecutable(command, env) {
166
+ const candidates = command.includes("/")
167
+ ? [resolve(command)]
168
+ : (env.PATH || "")
169
+ .split(delimiter)
170
+ .filter(Boolean)
171
+ .map((dir) => join(dir, command));
172
+ return candidates.find((file) => {
173
+ try {
174
+ fs.accessSync(file, fs.constants.X_OK);
175
+ return fs.statSync(file).isFile();
176
+ } catch {
177
+ return false;
178
+ }
179
+ });
180
+ }
181
+
182
+ // In a user scope of its own, a command that systemd-oomd kills does not take
183
+ // the caller's scope down with it, e.g. an agent's tmux pane.
184
+ function scopedSpawnArgs(command, args, env) {
185
+ if (process.platform !== "linux") return [command, args];
186
+ const executable = findExecutable(command, env);
187
+ // An unknown command spawns directly, so its start error still names it.
188
+ if (!executable) return [command, args];
189
+ scopeAvailable ??=
190
+ spawnSync("systemd-run", [...SCOPE_ARGS, "true"], {
191
+ env,
192
+ stdio: "ignore",
193
+ timeout: 5000,
194
+ }).status === 0;
195
+ return scopeAvailable
196
+ ? ["systemd-run", [...SCOPE_ARGS, executable, ...args]]
197
+ : [command, args];
198
+ }
199
+
153
200
  async function spawnAndForward(
154
201
  { command, args, env, pollMs, name },
155
202
  state,
156
203
  onSpawn,
157
204
  ) {
158
205
  if (state.signal) return signalCode(state.signal);
159
- const child = spawn(command, args, { env, stdio: "inherit", detached: true });
206
+ const [file, argv] = scopedSpawnArgs(command, args, env);
207
+ const child = spawn(file, argv, { env, stdio: "inherit", detached: true });
160
208
  state.pgid = child.pid || null;
161
209
  const result = new Promise((done) => {
162
210
  child.once("error", (error) => {
@@ -0,0 +1,284 @@
1
+ const { dirname, resolve } = require("node:path");
2
+ const {
3
+ LocalApiHttpError,
4
+ POLL_INTERVAL_MS,
5
+ getOperation,
6
+ printOperationSteps,
7
+ requestJson,
8
+ writeLine,
9
+ } = require("./local-api");
10
+
11
+ // The server checks the VM before it starts a switch; each check can take 15 seconds.
12
+ const START_TIMEOUT_MS = 120_000;
13
+
14
+ class GitCommandRefusedError extends Error {}
15
+
16
+ async function findProject(fetchImpl, baseUrl, path) {
17
+ let candidate = resolve(path);
18
+ for (;;) {
19
+ try {
20
+ const project = await requestJson(
21
+ fetchImpl,
22
+ baseUrl,
23
+ `/api/projects/by-path?path=${encodeURIComponent(candidate)}`,
24
+ );
25
+ if (!project || typeof project.id !== "string") {
26
+ throw new Error("DevChain returned an invalid project.");
27
+ }
28
+ return project;
29
+ } catch (error) {
30
+ if (!(error instanceof LocalApiHttpError) || error.status !== 404)
31
+ throw error;
32
+ }
33
+ const parent = dirname(candidate);
34
+ if (parent === candidate) {
35
+ throw new GitCommandRefusedError(
36
+ `No registered project contains ${resolve(path)}.`,
37
+ );
38
+ }
39
+ candidate = parent;
40
+ }
41
+ }
42
+
43
+ function recoveryCommand(owner, force, projectPath) {
44
+ const command =
45
+ owner === "home"
46
+ ? `devchain git take${force ? " --force" : ""}`
47
+ : "devchain git return";
48
+ return projectPath
49
+ ? `${command} --project '${projectPath.replace(/'/g, "'\\''")}'`
50
+ : command;
51
+ }
52
+
53
+ function ownerMessage(owner, remoteName, already = false) {
54
+ const side = owner === "home" ? "this PC" : `the VM '${remoteName}'`;
55
+ return `Git for this project is ${already ? "already " : ""}on ${side}.`;
56
+ }
57
+
58
+ function printStatus(status, stdout, projectPath) {
59
+ writeLine(
60
+ stdout,
61
+ status.connected
62
+ ? ownerMessage(status.owner, status.remoteName)
63
+ : "This project is not connected to a VM.",
64
+ );
65
+ if (!status.open) return;
66
+ const open = status.open;
67
+ writeLine(stdout, `Git switch ${open.operationId}: ${open.state}.`);
68
+ writeLine(
69
+ stdout,
70
+ `Target: ${open.owner === "home" ? "this PC" : `the VM '${status.remoteName}'`}.`,
71
+ );
72
+ if (open.step) writeLine(stdout, `Step: ${open.step}.`);
73
+ if (open.error)
74
+ writeLine(
75
+ stdout,
76
+ `Error: ${open.error.message}${open.error.code ? ` (${open.error.code})` : ""}`,
77
+ );
78
+ writeLine(
79
+ stdout,
80
+ `To finish the switch, run \`${recoveryCommand(open.owner, open.force, projectPath)}\`.`,
81
+ );
82
+ }
83
+
84
+ function printDetails(operation, seen, stderr) {
85
+ const details = operation.details ?? {};
86
+ const emit = (line) => {
87
+ if (seen.has(line)) return;
88
+ seen.add(line);
89
+ writeLine(stderr, line);
90
+ };
91
+ const unknownAgents = details.unknownAgents ?? [];
92
+ if (unknownAgents.length > 0)
93
+ emit(
94
+ "These agents on the VM have an unknown state. They do not block the switch:",
95
+ );
96
+ for (const agent of unknownAgents) {
97
+ emit(` - ${agent.agentName} (state unknown)`);
98
+ }
99
+ for (const warning of [details.guardWarning, details.vmGuardWarning]) {
100
+ if (warning) emit(`WARNING: ${warning}`);
101
+ }
102
+ for (const [field, side] of [
103
+ ["pcGuardRemove", "PC"],
104
+ ["vmGuardRemove", "VM"],
105
+ ]) {
106
+ const result = details[field];
107
+ if (!result) continue;
108
+ if (result.warning) emit(`WARNING: ${result.warning}`);
109
+ if (result.indexRefreshed !== true)
110
+ emit(`WARNING: The ${side} Git index was not rebuilt.`);
111
+ }
112
+ }
113
+
114
+ function printBusyAgents(error, remoteName, context, projectPath) {
115
+ const agents = error.details?.agents ?? [];
116
+ writeLine(
117
+ context.stderr,
118
+ `Git stays on the VM '${remoteName}': ${agents.length} ${agents.length === 1 ? "agent is" : "agents are"} working in this project.`,
119
+ );
120
+ for (const agent of agents) {
121
+ const elapsed = context.wallNow() - Date.parse(agent.since);
122
+ const minutes = Number.isFinite(elapsed)
123
+ ? Math.max(0, Math.floor(elapsed / 60_000))
124
+ : null;
125
+ const duration =
126
+ minutes === null
127
+ ? "time unknown"
128
+ : minutes < 1
129
+ ? "less than 1 min"
130
+ : `${minutes} min`;
131
+ const state =
132
+ agent.state === "busy" ? `busy for ${duration}` : `starting, ${duration}`;
133
+ writeLine(context.stderr, ` - ${agent.agentName} (${state})`);
134
+ }
135
+ writeLine(
136
+ context.stderr,
137
+ `Wait until they finish, then run \`${recoveryCommand("home", false, projectPath)}\` again.`,
138
+ );
139
+ writeLine(
140
+ context.stderr,
141
+ `To stop this project's agent sessions on the VM now, run \`${recoveryCommand("home", true, projectPath)}\`.`,
142
+ );
143
+ }
144
+
145
+ async function runGitOwnerCommand(command, options = {}, dependencies = {}) {
146
+ const stdout = dependencies.stdout ?? process.stdout;
147
+ const stderr = dependencies.stderr ?? process.stderr;
148
+ const fetchImpl = dependencies.fetch ?? globalThis.fetch;
149
+ const context = {
150
+ stdout: stderr,
151
+ stderr,
152
+ secrets: [],
153
+ now: dependencies.now ?? (() => performance.now()),
154
+ wallNow: dependencies.wallNow ?? Date.now,
155
+ sleep:
156
+ dependencies.sleep ??
157
+ ((ms) => new Promise((done) => setTimeout(done, ms))),
158
+ };
159
+ let status;
160
+ try {
161
+ if (dependencies.getMachineRole?.() === "remote VM") {
162
+ throw new GitCommandRefusedError(
163
+ "Run this command on the PC that connected this project.",
164
+ );
165
+ }
166
+ if (
167
+ !["status", "take", "return"].includes(command) ||
168
+ (command !== "take" && options.force)
169
+ ) {
170
+ throw new GitCommandRefusedError(
171
+ "Use devchain git status, take [--force], or return.",
172
+ );
173
+ }
174
+ const baseUrl = await dependencies.getLocalApiBaseUrl?.();
175
+ if (!baseUrl) {
176
+ writeLine(stderr, "Start DevChain first (devchain start).");
177
+ return 1;
178
+ }
179
+ const project = await findProject(
180
+ fetchImpl,
181
+ baseUrl,
182
+ options.project ?? dependencies.cwd ?? process.cwd(),
183
+ );
184
+ status = await requestJson(
185
+ fetchImpl,
186
+ baseUrl,
187
+ `/api/remotes/git-owner?projectId=${encodeURIComponent(project.id)}`,
188
+ );
189
+ if (command === "status") {
190
+ printStatus(status, stdout, options.project);
191
+ return 0;
192
+ }
193
+ if (!status.connected || !status.remoteId) {
194
+ throw new GitCommandRefusedError(
195
+ "This project is not connected to a VM.",
196
+ );
197
+ }
198
+ const owner = command === "take" ? "home" : "vm";
199
+ let operation = await requestJson(
200
+ fetchImpl,
201
+ baseUrl,
202
+ `/api/remotes/${encodeURIComponent(status.remoteId)}/git-owner`,
203
+ {
204
+ method: "POST",
205
+ body: { projectId: project.id, owner, force: options.force === true },
206
+ timeoutMs: START_TIMEOUT_MS,
207
+ },
208
+ );
209
+ if (operation?.changed === false) {
210
+ if (operation.cancelledOperationId) {
211
+ writeLine(
212
+ stdout,
213
+ `Cancelled the failed switch ${operation.cancelledOperationId}.`,
214
+ );
215
+ }
216
+ writeLine(stdout, ownerMessage(operation.owner, status.remoteName, true));
217
+ return 0;
218
+ }
219
+ const steps = new Map();
220
+ const details = new Set();
221
+ let announced = false;
222
+ for (;;) {
223
+ if (
224
+ !operation ||
225
+ typeof operation.id !== "string" ||
226
+ !Array.isArray(operation.steps) ||
227
+ !["running", "failed", "done", "cancelled"].includes(operation.state)
228
+ ) {
229
+ throw new Error("DevChain returned an invalid Git switch operation.");
230
+ }
231
+ const target = operation.details?.owner ?? owner;
232
+ const repeat = recoveryCommand(
233
+ target,
234
+ operation.details?.force === true,
235
+ options.project,
236
+ );
237
+ context.recoveryHint = `Run \`${repeat}\` again to follow or retry operation ${operation.id}.`;
238
+ if (!announced) {
239
+ writeLine(stderr, `Git switch started (operation ${operation.id}).`);
240
+ announced = true;
241
+ }
242
+ printOperationSteps(operation, steps, context);
243
+ printDetails(operation, details, stderr);
244
+ if (operation.state === "done") {
245
+ writeLine(stdout, ownerMessage(target, status.remoteName));
246
+ return 0;
247
+ }
248
+ if (operation.state === "failed" || operation.state === "cancelled") {
249
+ const failed = operation.steps.find((step) => step.state === "failed");
250
+ writeLine(
251
+ stderr,
252
+ failed?.error?.message ?? `Git switch ${operation.state}.`,
253
+ );
254
+ writeLine(stderr, context.recoveryHint);
255
+ return 1;
256
+ }
257
+ await context.sleep(POLL_INTERVAL_MS);
258
+ operation = await getOperation(fetchImpl, baseUrl, operation.id, context);
259
+ }
260
+ } catch (error) {
261
+ const code = error.details?.code ?? error.code;
262
+ if (code === "GIT_TAKE_AGENTS_BUSY") {
263
+ printBusyAgents(error, status?.remoteName, context, options.project);
264
+ return 2;
265
+ }
266
+ writeLine(stderr, error.message ?? "Git command failed.");
267
+ if (
268
+ code === "REMOTE_OPERATION_CANCEL_FAILED" ||
269
+ (error instanceof LocalApiHttpError && error.status >= 500)
270
+ ) {
271
+ writeLine(stderr, "Run the same command again.");
272
+ return 1;
273
+ }
274
+ if (
275
+ error instanceof GitCommandRefusedError ||
276
+ (error instanceof LocalApiHttpError &&
277
+ [400, 403, 404, 409, 422].includes(error.status))
278
+ )
279
+ return 2;
280
+ return 1;
281
+ }
282
+ }
283
+
284
+ module.exports = { runGitOwnerCommand };