@automaton-labs/aib 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (477) hide show
  1. package/README.md +36 -257
  2. package/config/agent-dx-policy.default.json +31 -0
  3. package/config/control-plane-client.default.json +3 -0
  4. package/dist/agent-dx/agent-dx.js +805 -0
  5. package/dist/agent-dx/presentation.js +32 -0
  6. package/dist/alias-advisor/analyze.js +982 -0
  7. package/dist/alias-advisor/exact-text-cost.js +74 -0
  8. package/dist/alias-advisor/lexical.js +240 -0
  9. package/dist/alias-advisor/model.js +2 -0
  10. package/dist/alias-advisor/path-identity.js +49 -0
  11. package/dist/alias-advisor/ranking.js +171 -0
  12. package/dist/alias-advisor/token-estimator.js +85 -0
  13. package/dist/bin/aib.js +5 -1
  14. package/dist/bin/cli.js +811 -4
  15. package/dist/commands/add-missing-imports-preview-cache.js +99 -2
  16. package/dist/commands/add-missing-imports.js +241 -2
  17. package/dist/commands/apply-module-plan.js +1319 -3
  18. package/dist/commands/background-command.js +378 -2
  19. package/dist/commands/captured-input.js +74 -1
  20. package/dist/commands/config-command.js +314 -2
  21. package/dist/commands/config-output.js +357 -10
  22. package/dist/commands/control-plane-command.js +183 -1
  23. package/dist/commands/declaration-command.js +1209 -7
  24. package/dist/commands/declaration-execution-cache.js +79 -1
  25. package/dist/commands/declaration-format.js +221 -1
  26. package/dist/commands/declaration-preview-cache.js +114 -2
  27. package/dist/commands/diagnostics-command.js +70 -3
  28. package/dist/commands/doctor.js +137 -3
  29. package/dist/commands/execution-command.js +117 -1
  30. package/dist/commands/exports-star-collapse-command.js +178 -0
  31. package/dist/commands/exports-star-command.js +252 -0
  32. package/dist/commands/exports-star-preview-cache.js +84 -0
  33. package/dist/commands/feedback-command.js +176 -3
  34. package/dist/commands/file-move-to-dir-execution-cache.js +92 -1
  35. package/dist/commands/file-move-to-dir-preview-cache.js +125 -2
  36. package/dist/commands/file-move-to-dir-worker.js +54 -0
  37. package/dist/commands/file-move-to-dir.js +1073 -5
  38. package/dist/commands/file-mutation-runtime.js +102 -1
  39. package/dist/commands/file-mutation-worker-apply.js +180 -0
  40. package/dist/commands/file-refactor-batch-execution-cache.js +90 -1
  41. package/dist/commands/file-refactor-batch-preview-cache.js +125 -2
  42. package/dist/commands/file-refactor-batch-worker.js +54 -0
  43. package/dist/commands/file-refactor-batch.js +1154 -5
  44. package/dist/commands/file-rename-execution-cache.js +92 -1
  45. package/dist/commands/file-rename-preview-cache.js +125 -2
  46. package/dist/commands/file-rename-worker.js +54 -0
  47. package/dist/commands/file-rename.js +1053 -5
  48. package/dist/commands/file-text-edit-executor.js +205 -0
  49. package/dist/commands/find-importers.js +52 -1
  50. package/dist/commands/find-unused-imports.js +55 -1
  51. package/dist/commands/generate-docs-command.js +244 -6
  52. package/dist/commands/help-command.js +460 -7
  53. package/dist/commands/import-specifier-policy.js +391 -0
  54. package/dist/commands/imports-command.js +68 -1
  55. package/dist/commands/imports-rebase.js +1138 -0
  56. package/dist/commands/init-skill-usage.js +92 -3
  57. package/dist/commands/init-workspace.js +30 -1
  58. package/dist/commands/inspect-alias-candidates.js +413 -0
  59. package/dist/commands/inspect-cycles.js +811 -5
  60. package/dist/commands/inspect-direct-usages.js +98 -0
  61. package/dist/commands/inspect-duplicates-format.js +229 -0
  62. package/dist/commands/inspect-format-primitives.js +127 -0
  63. package/dist/commands/inspect-format.js +1590 -18
  64. package/dist/commands/inspect-graph.js +1553 -9
  65. package/dist/commands/inspect-imports.js +339 -2
  66. package/dist/commands/inspect-mixed-format.js +524 -0
  67. package/dist/commands/inspect-module-plan-format.js +216 -0
  68. package/dist/commands/inspect-module-plan-serialization.js +402 -0
  69. package/dist/commands/inspect-reference-path-format.js +157 -0
  70. package/dist/commands/inspect-request-values.js +599 -0
  71. package/dist/commands/inspect-selector-kind.js +21 -0
  72. package/dist/commands/inspect-selector.js +50 -0
  73. package/dist/commands/inspect-symbol-format.js +90 -0
  74. package/dist/commands/inspect-topology.js +435 -0
  75. package/dist/commands/inspect-tree.js +827 -5
  76. package/dist/commands/inspect-usages-detail.js +793 -0
  77. package/dist/commands/inspect-usages-evidence.js +31 -0
  78. package/dist/commands/inspect-usages-format.js +367 -0
  79. package/dist/commands/inspect.js +1450 -16
  80. package/dist/commands/internal-command.js +64 -0
  81. package/dist/commands/json-file-input.js +60 -1
  82. package/dist/commands/module-plan-cache.js +156 -2
  83. package/dist/commands/module-plan-standalone.js +208 -0
  84. package/dist/commands/module-plan-timeout.js +77 -1
  85. package/dist/commands/move-preview-cache.js +101 -2
  86. package/dist/commands/mutate-aggregate-output.js +996 -0
  87. package/dist/commands/mutate-child-execution.js +374 -0
  88. package/dist/commands/mutate-command.js +1394 -0
  89. package/dist/commands/mutate-declaration-output.js +313 -0
  90. package/dist/commands/mutate-domain-adapters.js +638 -0
  91. package/dist/commands/mutate-execution-cache.js +114 -0
  92. package/dist/commands/mutate-execution-status.js +598 -0
  93. package/dist/commands/mutate-failure-output.js +531 -0
  94. package/dist/commands/mutate-file-view.js +151 -0
  95. package/dist/commands/mutate-object-output.js +91 -0
  96. package/dist/commands/mutate-output-values.js +97 -0
  97. package/dist/commands/mutate-preview-cache.js +86 -0
  98. package/dist/commands/mutate-response-replay.js +153 -0
  99. package/dist/commands/mutate-result-projection.js +818 -0
  100. package/dist/commands/mutation-eta.js +28 -0
  101. package/dist/commands/mutation-execution-cache.js +140 -3
  102. package/dist/commands/mutation-recovery-journal.js +195 -0
  103. package/dist/commands/normalize-imports.js +1120 -4
  104. package/dist/commands/object-pack-command.js +546 -0
  105. package/dist/commands/object-pack-preview-cache.js +88 -0
  106. package/dist/commands/observability-command.js +47 -3
  107. package/dist/commands/ota-command.js +403 -2
  108. package/dist/commands/print-config.js +10 -1
  109. package/dist/commands/quick-read.js +1113 -11
  110. package/dist/commands/refactor-batch-builder.js +265 -1
  111. package/dist/commands/refactor-batch-execution-cache.js +85 -1
  112. package/dist/commands/refactor-batch-preview-cache.js +96 -2
  113. package/dist/commands/refactor-batch.js +461 -4
  114. package/dist/commands/rename-entities.js +794 -4
  115. package/dist/commands/rename-execution-cache.js +49 -1
  116. package/dist/commands/rename-input.js +22 -1
  117. package/dist/commands/rename-polling-policy.js +68 -0
  118. package/dist/commands/rename-preview-cache.js +96 -2
  119. package/dist/commands/result-view.js +993 -7
  120. package/dist/commands/reveal-command.js +445 -5
  121. package/dist/commands/selector-suggestions.js +93 -0
  122. package/dist/commands/session-workspace.js +1836 -31
  123. package/dist/commands/shared.js +2 -1
  124. package/dist/commands/skills-command.js +315 -0
  125. package/dist/commands/sync-command.js +59 -2
  126. package/dist/commands/worker-command.js +173 -1
  127. package/dist/commands/workspace-cache.js +55 -1
  128. package/dist/config/defaults.js +26 -1
  129. package/dist/config/env-vars.js +77 -1
  130. package/dist/config/glob-match.js +44 -1
  131. package/dist/config/import-rules.js +165 -1
  132. package/dist/config/local-config.js +293 -3
  133. package/dist/config/mutation-comments.js +51 -1
  134. package/dist/config/path-aliases.js +165 -1
  135. package/dist/config/path-display.js +84 -1
  136. package/dist/config/product-storage.js +37 -1
  137. package/dist/config/request-env.js +21 -0
  138. package/dist/config/request-routing-context.js +23 -0
  139. package/dist/config/resolve-command-alias.js +74 -1
  140. package/dist/config/resolve.js +24 -1
  141. package/dist/config/tsconfig-discovery.js +113 -1
  142. package/dist/config/typescript-preferences.js +131 -0
  143. package/dist/config/workspace-root.js +310 -3
  144. package/dist/config/workspace-state.js +254 -1
  145. package/dist/content/content-bundle.js +88 -1
  146. package/dist/diagnostics/module-plan-timeline.js +75 -2
  147. package/dist/diagnostics/readiness-text.js +45 -8
  148. package/dist/dsl/aib-dsl.js +838 -1
  149. package/dist/dsl/declaration-dsl.js +264 -1
  150. package/dist/dsl/file-move-to-dir-dsl.js +123 -1
  151. package/dist/dsl/file-refactor-batch-dsl.js +203 -1
  152. package/dist/dsl/file-rename-dsl.js +135 -1
  153. package/dist/dsl/mutate-dsl.js +746 -0
  154. package/dist/help/bootstrap.bash.md +176 -341
  155. package/dist/help/bootstrap.md +175 -340
  156. package/dist/help/bootstrap.pwsh.md +176 -341
  157. package/dist/help/docs/basics.md +1 -1
  158. package/dist/help/docs/config-setup.import-normalize.md +65 -65
  159. package/dist/help/docs/config-setup.import-rules.md +6 -5
  160. package/dist/help/docs/first-setup.md +6 -5
  161. package/dist/help/docs/help-protocol.md +4 -3
  162. package/dist/help/docs/import.base.md +38 -0
  163. package/dist/help/docs/import.rebase.md +85 -0
  164. package/dist/help/docs/inspect.aliasCandidates.md +39 -0
  165. package/dist/help/docs/inspect.code.md +1 -7
  166. package/dist/help/docs/inspect.file.md +10 -19
  167. package/dist/help/docs/inspect.graph.md +3 -17
  168. package/dist/help/{dsl/docs/inspect.md → docs/inspect.shared.md} +13 -12
  169. package/dist/help/docs/modulePlan.md +24 -28
  170. package/dist/help/docs/move.md +19 -22
  171. package/dist/help/{dsl/topics/file.move.md → docs/moveToDir.md} +36 -34
  172. package/dist/help/docs/mutate.mixed.md +34 -0
  173. package/dist/help/docs/mutation.shared.md +55 -0
  174. package/dist/help/docs/patterns.md +3 -12
  175. package/dist/help/docs/qr.md +12 -6
  176. package/dist/help/docs/quick-map.md +36 -24
  177. package/dist/help/docs/recipe.file-layout.md +10 -10
  178. package/dist/help/docs/recipe.import-boundary.md +92 -0
  179. package/dist/help/docs/recipe.safe-artifact-probing.md +68 -0
  180. package/dist/help/docs/recipe.unfamiliar-area.md +64 -0
  181. package/dist/help/docs/recipes.md +20 -0
  182. package/dist/help/docs/rename.md +19 -27
  183. package/dist/help/docs/renameFile.md +36 -0
  184. package/dist/help/docs/rules.md +26 -0
  185. package/dist/help/dsl/bootstrap.bash.md +176 -341
  186. package/dist/help/dsl/bootstrap.md +175 -340
  187. package/dist/help/dsl/bootstrap.pwsh.md +176 -341
  188. package/dist/help/dsl/docs/basics.md +1 -1
  189. package/dist/help/dsl/docs/config-setup.import-normalize.md +65 -65
  190. package/dist/help/dsl/docs/config-setup.import-rules.md +6 -5
  191. package/dist/help/dsl/docs/first-setup.md +6 -5
  192. package/dist/help/dsl/docs/help-protocol.md +4 -3
  193. package/dist/help/dsl/docs/import.base.md +38 -0
  194. package/dist/help/dsl/docs/import.rebase.md +85 -0
  195. package/dist/help/dsl/docs/inspect.aliasCandidates.md +39 -0
  196. package/dist/help/dsl/docs/inspect.code.md +1 -7
  197. package/dist/help/dsl/docs/inspect.file.md +10 -19
  198. package/dist/help/dsl/docs/inspect.graph.md +3 -17
  199. package/dist/help/{docs/inspect.md → dsl/docs/inspect.shared.md} +13 -12
  200. package/dist/help/dsl/docs/modulePlan.md +24 -28
  201. package/dist/help/dsl/docs/move.md +19 -22
  202. package/dist/help/{topics/file.move.md → dsl/docs/moveToDir.md} +36 -34
  203. package/dist/help/dsl/docs/mutate.mixed.md +34 -0
  204. package/dist/help/dsl/docs/mutation.shared.md +55 -0
  205. package/dist/help/dsl/docs/patterns.md +3 -12
  206. package/dist/help/dsl/docs/qr.md +12 -6
  207. package/dist/help/dsl/docs/quick-map.md +36 -24
  208. package/dist/help/dsl/docs/recipe.file-layout.md +10 -10
  209. package/dist/help/dsl/docs/recipe.import-boundary.md +92 -0
  210. package/dist/help/dsl/docs/recipe.safe-artifact-probing.md +68 -0
  211. package/dist/help/dsl/docs/recipe.unfamiliar-area.md +64 -0
  212. package/dist/help/dsl/docs/recipes.md +20 -0
  213. package/dist/help/dsl/docs/rename.md +19 -27
  214. package/dist/help/dsl/docs/renameFile.md +36 -0
  215. package/dist/help/dsl/docs/rules.md +26 -0
  216. package/dist/help/dsl/full.md +175 -340
  217. package/dist/help/dsl/help-index.md +48 -43
  218. package/dist/help/dsl/snippets/agents.md +2 -2
  219. package/dist/help/dsl/tokens/qr.inline.examples.md +1 -1
  220. package/dist/help/dsl/topics/basics.md +1 -1
  221. package/dist/help/dsl/topics/config-setup.import-normalize.md +65 -65
  222. package/dist/help/dsl/topics/config-setup.import-rules.md +6 -5
  223. package/dist/help/dsl/topics/first-setup.md +6 -5
  224. package/dist/help/dsl/topics/help-protocol.md +4 -3
  225. package/dist/help/dsl/topics/import.base.md +36 -0
  226. package/dist/help/dsl/topics/import.rebase.md +83 -0
  227. package/dist/help/dsl/topics/inspect.aliasCandidates.md +37 -0
  228. package/dist/help/dsl/topics/inspect.code.md +1 -7
  229. package/dist/help/dsl/topics/inspect.file.md +10 -19
  230. package/dist/help/dsl/topics/inspect.graph.md +3 -17
  231. package/dist/help/dsl/topics/{inspect.md → inspect.shared.md} +13 -12
  232. package/dist/help/dsl/topics/modulePlan.md +21 -26
  233. package/dist/help/dsl/topics/move.md +17 -18
  234. package/dist/help/{docs/file.move.md → dsl/topics/moveToDir.md} +33 -35
  235. package/dist/help/dsl/topics/mutate.mixed.md +32 -0
  236. package/dist/help/dsl/topics/mutation.shared.md +53 -0
  237. package/dist/help/dsl/topics/patterns.md +3 -12
  238. package/dist/help/dsl/topics/qr.md +12 -6
  239. package/dist/help/dsl/topics/quick-map.md +36 -24
  240. package/dist/help/dsl/topics/recipe.file-layout.md +10 -10
  241. package/dist/help/dsl/topics/recipe.import-boundary.md +90 -0
  242. package/dist/help/dsl/topics/recipe.safe-artifact-probing.md +66 -0
  243. package/dist/help/dsl/topics/recipe.unfamiliar-area.md +62 -0
  244. package/dist/help/dsl/topics/recipes.md +18 -0
  245. package/dist/help/dsl/topics/rename.md +15 -15
  246. package/dist/help/dsl/topics/renameFile.md +34 -0
  247. package/dist/help/dsl/topics/rules.md +24 -0
  248. package/dist/help/full.md +175 -340
  249. package/dist/help/help-index.md +48 -43
  250. package/dist/help/help-meta.json +98 -48
  251. package/dist/help/index.md +52 -20
  252. package/dist/help/snippets/agents.md +2 -2
  253. package/dist/help/tokens/qr.inline.examples.md +1 -1
  254. package/dist/help/topics/basics.md +1 -1
  255. package/dist/help/topics/config-setup.import-normalize.md +65 -65
  256. package/dist/help/topics/config-setup.import-rules.md +6 -5
  257. package/dist/help/topics/first-setup.md +6 -5
  258. package/dist/help/topics/help-protocol.md +4 -3
  259. package/dist/help/topics/import.base.md +36 -0
  260. package/dist/help/topics/import.rebase.md +83 -0
  261. package/dist/help/topics/inspect.aliasCandidates.md +37 -0
  262. package/dist/help/topics/inspect.code.md +1 -7
  263. package/dist/help/topics/inspect.file.md +10 -19
  264. package/dist/help/topics/inspect.graph.md +3 -17
  265. package/dist/help/topics/{inspect.md → inspect.shared.md} +13 -12
  266. package/dist/help/topics/modulePlan.md +21 -26
  267. package/dist/help/topics/move.md +17 -18
  268. package/dist/help/{dsl/docs/file.move.md → topics/moveToDir.md} +33 -35
  269. package/dist/help/topics/mutate.mixed.md +32 -0
  270. package/dist/help/topics/mutation.shared.md +53 -0
  271. package/dist/help/topics/patterns.md +3 -12
  272. package/dist/help/topics/qr.md +12 -6
  273. package/dist/help/topics/quick-map.md +36 -24
  274. package/dist/help/topics/recipe.file-layout.md +10 -10
  275. package/dist/help/topics/recipe.import-boundary.md +90 -0
  276. package/dist/help/topics/recipe.safe-artifact-probing.md +66 -0
  277. package/dist/help/topics/recipe.unfamiliar-area.md +62 -0
  278. package/dist/help/topics/recipes.md +18 -0
  279. package/dist/help/topics/rename.md +15 -15
  280. package/dist/help/topics/renameFile.md +34 -0
  281. package/dist/help/topics/rules.md +24 -0
  282. package/dist/host/capabilities.js +56 -1
  283. package/dist/imports/standalone-import-analysis.js +602 -0
  284. package/dist/imports/standalone-import-model.js +2 -0
  285. package/dist/imports/standalone-import-process.js +66 -0
  286. package/dist/imports/standalone-import-worker.js +27 -0
  287. package/dist/imports/standalone-ts-project.js +147 -0
  288. package/dist/inspect/inspect-duplicates-service.js +526 -0
  289. package/dist/inspect/inspect-reverse-module-closure.js +338 -0
  290. package/dist/inspect/inspect-source-ranges.js +82 -0
  291. package/dist/inspect/inspect-source-shape.js +649 -0
  292. package/dist/inspect/standalone-inspect-analysis.js +319 -0
  293. package/dist/inspect/standalone-inspect-model.js +9 -0
  294. package/dist/inspect/standalone-inspect-process.js +55 -0
  295. package/dist/inspect/standalone-inspect-usages.js +274 -0
  296. package/dist/inspect/standalone-inspect-worker.js +13 -0
  297. package/dist/metrics/central-metrics.js +101 -2
  298. package/dist/mutation/concrete-edit-plan.js +157 -0
  299. package/dist/mutation/exports-star-collapse-preview.js +361 -0
  300. package/dist/mutation/exports-star-expand-apply.js +373 -0
  301. package/dist/mutation/exports-star-expand-preview.js +807 -0
  302. package/dist/mutation/file-impact-scan.js +182 -0
  303. package/dist/mutation/file-mutation-discovery.js +674 -0
  304. package/dist/mutation/file-mutation-eta.js +99 -0
  305. package/dist/mutation/file-mutation-standalone-apply.js +584 -0
  306. package/dist/mutation/file-mutation-standalone-planner.js +500 -0
  307. package/dist/mutation/file-mutation-tsserver-session.js +473 -0
  308. package/dist/mutation/mutation-estimate.js +45 -0
  309. package/dist/mutation/mutation-plan.js +467 -0
  310. package/dist/mutation/object-mutation-foundation.js +2 -0
  311. package/dist/mutation/object-mutation-partition.js +148 -0
  312. package/dist/mutation/object-pack-semantic-apply.js +507 -0
  313. package/dist/mutation/object-pack-semantic-preview.js +984 -0
  314. package/dist/mutation/object-unpack-semantic-preview.js +993 -0
  315. package/dist/mutation/package-manifest-impact.js +176 -0
  316. package/dist/mutation/rename-owner-worker-child.js +37 -0
  317. package/dist/mutation/rename-owner-worker.js +122 -0
  318. package/dist/mutation/runtime-cycle-query.js +66 -0
  319. package/dist/mutation/semantic-execution-progress.js +63 -0
  320. package/dist/mutation/semantic-export-index.js +474 -0
  321. package/dist/mutation/semantic-impact-scan.js +434 -0
  322. package/dist/mutation/semantic-model.js +2 -0
  323. package/dist/mutation/semantic-move-best-effort-planner.js +209 -0
  324. package/dist/mutation/semantic-move-config-worker-child.js +61 -0
  325. package/dist/mutation/semantic-move-config-worker.js +449 -0
  326. package/dist/mutation/semantic-move-declarations.js +94 -0
  327. package/dist/mutation/semantic-move-metrics.js +119 -0
  328. package/dist/mutation/semantic-move-model.js +2 -0
  329. package/dist/mutation/semantic-move-owner-authority.js +122 -0
  330. package/dist/mutation/semantic-move-owner-candidates.js +300 -0
  331. package/dist/mutation/semantic-move-owner-divergence-harness.js +474 -0
  332. package/dist/mutation/semantic-move-owner-divergence-runner.js +162 -0
  333. package/dist/mutation/semantic-move-plan-hash.js +56 -0
  334. package/dist/mutation/semantic-move-policy.js +360 -0
  335. package/dist/mutation/semantic-move-process.js +218 -0
  336. package/dist/mutation/semantic-move-provider-diagnostics.js +27 -0
  337. package/dist/mutation/semantic-move-standalone-engine.js +919 -0
  338. package/dist/mutation/semantic-move-standalone-planner.js +1553 -0
  339. package/dist/mutation/semantic-move-transaction.js +639 -0
  340. package/dist/mutation/semantic-mutation-engine.js +478 -0
  341. package/dist/mutation/semantic-mutation-runtime.js +521 -0
  342. package/dist/mutation/semantic-planner-retry.js +26 -0
  343. package/dist/mutation/semantic-program-roots.js +69 -0
  344. package/dist/mutation/semantic-rename-commonjs.js +229 -0
  345. package/dist/mutation/semantic-rename-config-worker-child.js +46 -0
  346. package/dist/mutation/semantic-rename-config-worker.js +498 -0
  347. package/dist/mutation/semantic-rename-locations.js +192 -0
  348. package/dist/mutation/semantic-rename-model.js +2 -0
  349. package/dist/mutation/semantic-rename-process.js +210 -0
  350. package/dist/mutation/semantic-rename-standalone-engine.js +302 -0
  351. package/dist/mutation/semantic-rename-standalone-planner.js +567 -0
  352. package/dist/mutation/semantic-rename-transaction.js +333 -0
  353. package/dist/mutation/semantic-unused-import-cleanup.js +248 -0
  354. package/dist/mutation/semantic-worker-retry.js +40 -0
  355. package/dist/mutation/semantic-workspace.js +804 -0
  356. package/dist/mutation/targeted-import-canonicalizer.js +359 -0
  357. package/dist/observability/config.js +206 -1
  358. package/dist/observability/context.js +59 -1
  359. package/dist/observability/failure-snapshot.js +124 -2
  360. package/dist/observability/recent.js +63 -2
  361. package/dist/payloads/read-stdin-json.js +212 -1
  362. package/dist/rg-wrapper/core.js +714 -6
  363. package/dist/rg-wrapper/options.js +125 -1
  364. package/dist/rtk-wrapper/core.js +144 -2
  365. package/dist/rtk-wrapper/git-safety.js +203 -0
  366. package/dist/rtk-wrapper/output-adapter.js +106 -0
  367. package/dist/runtime/buffered-process.js +114 -0
  368. package/dist/runtime/bundled-node.js +97 -1
  369. package/dist/runtime/command-lifecycle.js +2 -0
  370. package/dist/runtime/input-source.js +238 -1
  371. package/dist/runtime/node-cli-identity.js +37 -0
  372. package/dist/runtime/run-command.js +191 -1
  373. package/dist/selectors/parse-entities.js +84 -1
  374. package/dist/semantic/semantic-context-pool.js +830 -0
  375. package/dist/semantic/semantic-context-provider.js +416 -0
  376. package/dist/semantic/semantic-resident-admission.js +38 -0
  377. package/dist/semantic/semantic-worker-child.js +40 -0
  378. package/dist/semantic/semantic-worker-manager.js +460 -0
  379. package/dist/semantic/semantic-worker-protocol.js +2 -0
  380. package/dist/semantic/semantic-worker-runtime.js +38 -0
  381. package/dist/semantic/workspace-semantic-context-pools.js +136 -0
  382. package/dist/session/client.js +359 -1
  383. package/dist/session/daemon-state.js +185 -2
  384. package/dist/session/external-tool-coordinator.js +88 -0
  385. package/dist/session/external-tool-request.js +52 -0
  386. package/dist/session/lane-owner.js +116 -0
  387. package/dist/session/paths.js +71 -1
  388. package/dist/session/runtime-lifecycle.js +225 -0
  389. package/dist/session/runtime-shutdown.js +95 -0
  390. package/dist/session/server.js +1194 -7
  391. package/dist/session/session-health.js +27 -0
  392. package/dist/shared/agent-text.js +29 -1
  393. package/dist/shared/errors.js +496 -30
  394. package/dist/shared/event-loop.js +6 -1
  395. package/dist/shared/hints.js +37 -1
  396. package/dist/shared/metrics.js +43 -1
  397. package/dist/shared/presentation.js +69 -4
  398. package/dist/shared/stdin-examples.js +77 -1
  399. package/dist/shared/stdout.js +6 -2
  400. package/dist/shared/types.js +23 -1
  401. package/dist/tool-runtime/runtime-tools.js +120 -1
  402. package/dist/topology/availability.js +663 -0
  403. package/dist/topology/cache.js +130 -0
  404. package/dist/topology/candidates.js +372 -0
  405. package/dist/topology/core.js +903 -0
  406. package/dist/topology/dependency-snapshot.js +189 -0
  407. package/dist/topology/model.js +2 -0
  408. package/dist/topology/scope.js +100 -0
  409. package/dist/topology/ts-oracle-batch.js +529 -0
  410. package/dist/topology/ts-oracle-host.js +69 -0
  411. package/dist/topology/ts-oracle-model.js +2 -0
  412. package/dist/topology/ts-oracle.js +224 -0
  413. package/dist/topology/tsconfig-loader.js +105 -0
  414. package/dist/topology/workspace-project-index.js +542 -0
  415. package/dist/tracing/config.js +192 -2
  416. package/dist/tracing/failure-summary.js +100 -0
  417. package/dist/tracing/trace.js +318 -5
  418. package/dist/tsc-wrapper/core.js +697 -0
  419. package/dist/workers/control-plane/aib-control-plane-worker.cjs +32 -32
  420. package/dist/workers/control-plane/client-config.default.json +3 -3
  421. package/dist/workers/lexical-worker-child.js +27 -0
  422. package/dist/workers/lexical-worker-manager.js +394 -0
  423. package/dist/workers/lexical-worker-protocol.js +2 -0
  424. package/dist/workers/lexical-worker-runtime.js +25 -0
  425. package/dist/workers/owned-one-shot-process.js +115 -0
  426. package/dist/workers/product-storage.js +23 -1
  427. package/dist/workers/registry.js +241 -1
  428. package/package.json +8 -13
  429. package/scripts/postinstall.cjs +29 -29
  430. package/scripts/provision-runtime.cjs +411 -459
  431. package/dist/client/http-client.js +0 -1
  432. package/dist/client/transport-options.js +0 -1
  433. package/dist/client/websocket-client.js +0 -1
  434. package/dist/commands/entity-resolution.js +0 -1
  435. package/dist/commands/file-command.js +0 -1
  436. package/dist/commands/host-command.js +0 -1
  437. package/dist/commands/inspect-symbol-query.js +0 -1
  438. package/dist/commands/list-instances.js +0 -1
  439. package/dist/commands/module-plan-command.js +0 -1
  440. package/dist/commands/move-command.js +0 -1
  441. package/dist/commands/move-to-file.js +0 -2
  442. package/dist/commands/ping.js +0 -1
  443. package/dist/commands/refactor-command.js +0 -1
  444. package/dist/commands/rename-command.js +0 -1
  445. package/dist/commands/runtime-command.js +0 -4
  446. package/dist/commands/runtime-info.js +0 -1
  447. package/dist/commands/validate-module-plan.js +0 -2
  448. package/dist/compatibility/policy.js +0 -1
  449. package/dist/config/auto-start-ide.js +0 -1
  450. package/dist/discovery/registry.js +0 -1
  451. package/dist/discovery/select-instance.js +0 -1
  452. package/dist/help/docs/file.rename.md +0 -35
  453. package/dist/help/docs/mutation.md +0 -60
  454. package/dist/help/docs/refactor.batch.md +0 -44
  455. package/dist/help/dsl/docs/file.rename.md +0 -35
  456. package/dist/help/dsl/docs/mutation.md +0 -60
  457. package/dist/help/dsl/docs/refactor.batch.md +0 -44
  458. package/dist/help/dsl/topics/file.rename.md +0 -33
  459. package/dist/help/dsl/topics/mutation.md +0 -58
  460. package/dist/help/dsl/topics/refactor.batch.md +0 -33
  461. package/dist/help/topics/file.rename.md +0 -33
  462. package/dist/help/topics/mutation.md +0 -58
  463. package/dist/help/topics/refactor.batch.md +0 -33
  464. package/dist/ide-launch/common.cjs +0 -162
  465. package/dist/managed-host/extension-vsix-resolver.js +0 -1
  466. package/dist/managed-host/manage-serve-web-host.cjs +0 -141
  467. package/dist/managed-host/serve-web-autostart.js +0 -1
  468. package/dist/managed-host/serve-web-host.cjs +0 -2115
  469. package/dist/runtime/managed-runtime-provisioning.js +0 -1
  470. package/dist/shared/diagnostic-catalog.js +0 -1
  471. package/dist/shared/diagnostics.js +0 -1
  472. package/dist/shared/operations.js +0 -1
  473. package/dist/shared/protocol.js +0 -1
  474. package/dist/shared/routes.js +0 -1
  475. package/extension/vscode-refactor-bridge-extension.vsix +0 -0
  476. package/runtimes/launcher/linux-x64/aib +0 -0
  477. package/runtimes/launcher/win-x64/aib.exe +0 -0
@@ -1,2115 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const crypto = require("node:crypto");
4
- const fs = require("node:fs");
5
- const net = require("node:net");
6
- const os = require("node:os");
7
- const path = require("node:path");
8
- const { spawn, spawnSync } = require("node:child_process");
9
- const {
10
- defaultFixtureDir,
11
- defaultVsixPath,
12
- prepareSpawnCommand,
13
- readRegistryEntries,
14
- resolveIdeCommand,
15
- rootDir,
16
- run,
17
- sleep,
18
- timestampForPath,
19
- writeJson
20
- } = require("../ide-launch/common.cjs");
21
-
22
- const managedRootDir = path.join(os.tmpdir(), "aib", "managed-hosts");
23
- const defaultRunsDir = path.join(rootDir, ".tmp", "managed-host-runs");
24
- const repoRootDir = findRepoRoot(rootDir);
25
- const extensionPackageDir = repoRootDir ? path.join(repoRootDir, "packages", "extension") : null;
26
- const repoDefaultVsixPath = repoRootDir ? path.join(repoRootDir, ".tmp", "vscode-refactor-bridge-extension.vsix") : null;
27
-
28
- function normalizeForId(value) {
29
- return path.resolve(String(value)).replace(/[\\/]+$/, "").toLowerCase();
30
- }
31
-
32
- function normalizeForCompare(value) {
33
- return path.normalize(String(value)).replace(/[\\/]+$/, "").toLowerCase();
34
- }
35
-
36
- function hashShort(value) {
37
- return crypto.createHash("sha1").update(value).digest("hex").slice(0, 16);
38
- }
39
-
40
- function findRepoRoot(startDir) {
41
- let current = path.resolve(startDir);
42
- while (true) {
43
- if (fs.existsSync(path.join(current, "packages", "extension", "package.json"))) {
44
- return current;
45
- }
46
- const parent = path.dirname(current);
47
- if (parent === current) {
48
- return null;
49
- }
50
- current = parent;
51
- }
52
- }
53
-
54
- function fingerprintFile(filePath) {
55
- if (!filePath || !fs.existsSync(filePath)) {
56
- return null;
57
- }
58
-
59
- const stat = fs.statSync(filePath);
60
- const hash = crypto.createHash("sha1").update(fs.readFileSync(filePath)).digest("hex");
61
- return {
62
- path: path.resolve(filePath),
63
- hash,
64
- shortHash: hash.slice(0, 12),
65
- size: stat.size,
66
- mtimeMs: stat.mtimeMs
67
- };
68
- }
69
-
70
- function fingerprintsMatch(left, right) {
71
- if (!left || !right) {
72
- return false;
73
- }
74
-
75
- return left.hash === right.hash && left.size === right.size;
76
- }
77
-
78
- function newestFileMtimeMs(filePath) {
79
- if (!filePath || !fs.existsSync(filePath)) {
80
- return null;
81
- }
82
-
83
- const stat = fs.statSync(filePath);
84
- if (stat.isFile()) {
85
- return stat.mtimeMs;
86
- }
87
-
88
- if (!stat.isDirectory()) {
89
- return null;
90
- }
91
-
92
- let newest = null;
93
- for (const entry of fs.readdirSync(filePath, { withFileTypes: true })) {
94
- const childNewest = newestFileMtimeMs(path.join(filePath, entry.name));
95
- if (childNewest !== null && (newest === null || childNewest > newest)) {
96
- newest = childNewest;
97
- }
98
- }
99
- return newest;
100
- }
101
-
102
- function getExtensionVsixFreshness(vsix) {
103
- const resolvedVsix = path.resolve(vsix);
104
- const knownDevVsixPaths = [defaultVsixPath, repoDefaultVsixPath].filter(Boolean);
105
- if (!extensionPackageDir || !knownDevVsixPaths.some((knownPath) => normalizeForCompare(resolvedVsix) === normalizeForCompare(knownPath))) {
106
- return {
107
- supported: false,
108
- reason: "non-default-vsix",
109
- vsix: resolvedVsix
110
- };
111
- }
112
-
113
- const inputPaths = [
114
- path.join(extensionPackageDir, "dist"),
115
- path.join(extensionPackageDir, "extension.js"),
116
- path.join(extensionPackageDir, "package.json"),
117
- path.join(extensionPackageDir, "README.md"),
118
- path.join(extensionPackageDir, ".vscodeignore")
119
- ];
120
- const inputMtimes = inputPaths
121
- .map((inputPath) => ({ path: inputPath, mtimeMs: newestFileMtimeMs(inputPath) }))
122
- .filter((entry) => entry.mtimeMs !== null);
123
- const newestInput = inputMtimes.reduce((best, entry) => {
124
- if (!best || entry.mtimeMs > best.mtimeMs) {
125
- return entry;
126
- }
127
- return best;
128
- }, null);
129
- const vsixMtimeMs = fs.existsSync(resolvedVsix) ? fs.statSync(resolvedVsix).mtimeMs : null;
130
- const stale = vsixMtimeMs === null || (newestInput !== null && newestInput.mtimeMs > vsixMtimeMs + 1000);
131
-
132
- return {
133
- supported: true,
134
- stale,
135
- vsix: resolvedVsix,
136
- vsixMtimeMs,
137
- newestInput
138
- };
139
- }
140
-
141
- function packageExtensionVsix() {
142
- const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
143
- return runCommand(npmCommand, ["--silent", "--prefix", "packages/extension", "run", "package:vsix"], {
144
- cwd: repoRootDir ?? rootDir,
145
- timeoutMs: 120_000
146
- });
147
- }
148
-
149
- function buildHostId(options) {
150
- const hostKind = options.hostKind ?? "vscode-serve-web";
151
- return `${hostKind}-${hashShort(normalizeForId(options.fixture ?? defaultFixtureDir))}`;
152
- }
153
-
154
- function getHostDir(hostId) {
155
- return path.join(managedRootDir, hostId);
156
- }
157
-
158
- function getMetadataPath(hostId) {
159
- return path.join(getHostDir(hostId), "metadata.json");
160
- }
161
-
162
- function getWatchdogPath(hostId) {
163
- return path.join(getHostDir(hostId), "watchdog.json");
164
- }
165
-
166
- function getLockDir(hostId) {
167
- return path.join(getHostDir(hostId), "lock");
168
- }
169
-
170
- function ensureDir(dirPath) {
171
- fs.mkdirSync(dirPath, { recursive: true });
172
- }
173
-
174
- function writeText(filePath, value) {
175
- ensureDir(path.dirname(filePath));
176
- fs.writeFileSync(filePath, value);
177
- }
178
-
179
- function appendText(filePath, value) {
180
- ensureDir(path.dirname(filePath));
181
- fs.appendFileSync(filePath, value);
182
- }
183
-
184
- function writeJsonAtomic(filePath, payload) {
185
- ensureDir(path.dirname(filePath));
186
- const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
187
- try {
188
- fs.writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`);
189
- fs.renameSync(tempPath, filePath);
190
- } catch (error) {
191
- try {
192
- fs.rmSync(tempPath, { force: true });
193
- } catch {
194
- // Best-effort cleanup only.
195
- }
196
- throw error;
197
- }
198
- }
199
-
200
- function tail(text, maxLength = 4000) {
201
- return text.length > maxLength ? text.slice(text.length - maxLength) : text;
202
- }
203
-
204
- function readTextIfExists(filePath) {
205
- try {
206
- return fs.readFileSync(filePath, "utf8");
207
- } catch {
208
- return "";
209
- }
210
- }
211
-
212
- function readRecentLogText(root, maxFiles = 12, maxLength = 4000) {
213
- if (!fs.existsSync(root)) {
214
- return "";
215
- }
216
- const files = [];
217
- collectFiles(root, files, 5);
218
- return files
219
- .filter((filePath) => filePath.endsWith(".log") || filePath.endsWith(".txt"))
220
- .sort((left, right) => {
221
- const leftTime = fs.statSync(left).mtimeMs;
222
- const rightTime = fs.statSync(right).mtimeMs;
223
- return rightTime - leftTime;
224
- })
225
- .slice(0, maxFiles)
226
- .map((filePath) => readTextIfExists(filePath))
227
- .join("\n")
228
- .slice(-maxLength);
229
- }
230
-
231
- function collectFiles(root, files, maxDepth) {
232
- if (maxDepth < 0) {
233
- return;
234
- }
235
- let entries;
236
- try {
237
- entries = fs.readdirSync(root, { withFileTypes: true });
238
- } catch {
239
- return;
240
- }
241
- for (const entry of entries) {
242
- const entryPath = path.join(root, entry.name);
243
- if (entry.isFile()) {
244
- files.push(entryPath);
245
- } else if (entry.isDirectory()) {
246
- collectFiles(entryPath, files, maxDepth - 1);
247
- }
248
- }
249
- }
250
-
251
- function isProcessAlive(pid) {
252
- if (!Number.isInteger(pid) || pid <= 0) {
253
- return false;
254
- }
255
-
256
- try {
257
- process.kill(pid, 0);
258
- return true;
259
- } catch {
260
- return false;
261
- }
262
- }
263
-
264
- function workspaceMatchesFixture(entry, fixture) {
265
- const expected = normalizeForCompare(fixture);
266
- return (entry.workspaceFolders ?? []).some(
267
- (workspaceFolder) => normalizeForCompare(workspaceFolder) === expected
268
- );
269
- }
270
-
271
- function resolveChromePath(explicitPath) {
272
- const candidates = [
273
- explicitPath,
274
- findPackagedBrowserRuntime(),
275
- "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
276
- "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
277
- "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
278
- "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
279
- "google-chrome",
280
- "google-chrome-stable",
281
- "chromium",
282
- "chromium-browser",
283
- "microsoft-edge"
284
- ].filter(Boolean);
285
-
286
- return candidates.find((candidate) => commandOrFileExists(candidate)) ?? null;
287
- }
288
-
289
- function findPackagedBrowserRuntime() {
290
- const roots = [
291
- path.join(rootDir, "runtimes", "browser"),
292
- path.join(rootDir, "runtimes", "chrome"),
293
- path.join(rootDir, "runtimes", "chromium")
294
- ];
295
- const executableNames = process.platform === "win32"
296
- ? new Set(["chrome.exe", "chromium.exe", "msedge.exe"])
297
- : new Set(["chrome", "chromium"]);
298
- for (const runtimeRoot of roots) {
299
- const found = findFirstExecutable(runtimeRoot, executableNames, 5);
300
- if (found) {
301
- return found;
302
- }
303
- }
304
- return null;
305
- }
306
-
307
- function findFirstExecutable(root, executableNames, maxDepth) {
308
- if (!fs.existsSync(root) || maxDepth < 0) {
309
- return null;
310
- }
311
- let entries;
312
- try {
313
- entries = fs.readdirSync(root, { withFileTypes: true });
314
- } catch {
315
- return null;
316
- }
317
- for (const entry of entries) {
318
- const entryPath = path.join(root, entry.name);
319
- if (entry.isFile() && executableNames.has(entry.name) && commandOrFileExists(entryPath)) {
320
- return entryPath;
321
- }
322
- }
323
- for (const entry of entries) {
324
- if (!entry.isDirectory()) {
325
- continue;
326
- }
327
- const found = findFirstExecutable(path.join(root, entry.name), executableNames, maxDepth - 1);
328
- if (found) {
329
- return found;
330
- }
331
- }
332
- return null;
333
- }
334
-
335
- function commandOrFileExists(candidate) {
336
- if (candidate.includes("\\") || candidate.includes("/") || /^[A-Za-z]:/.test(candidate)) {
337
- return fs.existsSync(candidate);
338
- }
339
-
340
- const escaped = String(candidate).replace(/"/g, '\\"');
341
- const result = process.platform === "win32"
342
- ? spawnSync("where.exe", [candidate], { encoding: "utf8", windowsHide: true, stdio: ["ignore", "pipe", "pipe"] })
343
- : spawnSync("sh", ["-lc", `command -v "${escaped}"`], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
344
- return result.status === 0;
345
- }
346
-
347
- function ideCommandEnv() {
348
- return {
349
- ...process.env,
350
- DONT_PROMPT_WSL_INSTALL: process.env.DONT_PROMPT_WSL_INSTALL ?? "1"
351
- };
352
- }
353
-
354
- function makeCommandSpec(command, prefixArgs = [], source = "command") {
355
- return {
356
- command,
357
- prefixArgs,
358
- source,
359
- displayCommand: [command, ...prefixArgs].join(" ")
360
- };
361
- }
362
-
363
- function resolvePackagedNodeRuntime() {
364
- const envNodePath = process.env.AIB_NODE_PATH;
365
- if (envNodePath && commandOrFileExists(envNodePath)) {
366
- return { command: path.resolve(envNodePath), source: "env-node" };
367
- }
368
-
369
- const platformDir = nodeRuntimePlatformDir();
370
- if (!platformDir) {
371
- return { command: process.execPath, source: "process-node" };
372
- }
373
-
374
- const candidateRoots = [
375
- path.join(rootDir, "runtimes", "node", platformDir),
376
- path.join(rootDir, "packages", "cli", "runtimes", "node", platformDir)
377
- ];
378
- for (const candidateRoot of candidateRoots) {
379
- const candidate = process.platform === "win32"
380
- ? path.join(candidateRoot, "node.exe")
381
- : path.join(candidateRoot, "bin", "node");
382
- if (fs.existsSync(candidate)) {
383
- return { command: candidate, source: "packaged-node" };
384
- }
385
- }
386
-
387
- return { command: process.execPath, source: "process-node" };
388
- }
389
-
390
- function nodeRuntimePlatformDir() {
391
- const arch = process.arch;
392
- if (process.platform === "win32" && arch === "x64") {
393
- return "win-x64";
394
- }
395
- if (process.platform === "linux" && arch === "x64") {
396
- return "linux-x64";
397
- }
398
- if (process.platform === "linux" && arch === "arm64") {
399
- return "linux-arm64";
400
- }
401
- if (process.platform === "darwin" && arch === "x64") {
402
- return "darwin-x64";
403
- }
404
- if (process.platform === "darwin" && arch === "arm64") {
405
- return "darwin-arm64";
406
- }
407
- return null;
408
- }
409
-
410
- function commandDisplayName(commandSpec) {
411
- return typeof commandSpec === "string" ? commandSpec : commandSpec.displayCommand;
412
- }
413
-
414
- function commandExecutable(commandSpec) {
415
- return typeof commandSpec === "string" ? commandSpec : commandSpec.command;
416
- }
417
-
418
- function commandArgs(commandSpec, args) {
419
- return typeof commandSpec === "string" ? args : [...commandSpec.prefixArgs, ...args];
420
- }
421
-
422
- function runCommand(commandSpec, args, options = {}) {
423
- return run(commandExecutable(commandSpec), commandArgs(commandSpec, args), options);
424
- }
425
-
426
- function startLoggedCommand(commandSpec, args, options) {
427
- return startLoggedProcess(commandExecutable(commandSpec), commandArgs(commandSpec, args), options);
428
- }
429
-
430
- function resolvePackagedCodeServerCommand() {
431
- const nodeRuntime = resolvePackagedNodeRuntime();
432
- const candidates = [
433
- {
434
- packageRoot: rootDir,
435
- entryPath: path.join(rootDir, "runtimes", "code-server", "node_modules", "code-server", "out", "node", "entry.js")
436
- },
437
- {
438
- packageRoot: path.join(rootDir, "packages", "cli"),
439
- entryPath: path.join(rootDir, "packages", "cli", "runtimes", "code-server", "node_modules", "code-server", "out", "node", "entry.js")
440
- }
441
- ];
442
- for (const { packageRoot, entryPath } of candidates) {
443
- if (fs.existsSync(entryPath) && managedRuntimePlatformStatus(packageRoot).ok) {
444
- return makeCommandSpec(nodeRuntime.command, [entryPath], nodeRuntime.source === "packaged-node"
445
- ? "packaged-code-server-entry-bundled-node"
446
- : "packaged-code-server-entry");
447
- }
448
- }
449
- return null;
450
- }
451
-
452
- function managedRuntimePlatformStatus(packageRoot) {
453
- const markerPath = path.join(packageRoot, "runtimes", ".aib-runtime-platform.json");
454
- const expectedPlatformKey = `${process.platform}-${process.arch}`;
455
- if (!fs.existsSync(markerPath)) {
456
- return { ok: true, reason: "missing", expectedPlatformKey, actualPlatformKey: null };
457
- }
458
- try {
459
- const marker = JSON.parse(fs.readFileSync(markerPath, "utf8"));
460
- const actualPlatformKey = typeof marker.platformKey === "string"
461
- ? marker.platformKey
462
- : typeof marker.platform === "string" && typeof marker.arch === "string"
463
- ? `${marker.platform}-${marker.arch}`
464
- : null;
465
- return {
466
- ok: actualPlatformKey === expectedPlatformKey,
467
- reason: actualPlatformKey === expectedPlatformKey ? "ok" : "mismatch",
468
- expectedPlatformKey,
469
- actualPlatformKey
470
- };
471
- } catch {
472
- return { ok: false, reason: "invalid", expectedPlatformKey, actualPlatformKey: null };
473
- }
474
- }
475
-
476
- function resolveManagedHostKind(options, commandSpec) {
477
- const raw = options.hostKind ?? process.env.AIB_MANAGED_HOST_KIND ?? "";
478
- if (raw) {
479
- if (raw !== "vscode-serve-web" && raw !== "code-server") {
480
- throw new Error("Expected managed host kind to be vscode-serve-web or code-server.");
481
- }
482
- return raw;
483
- }
484
-
485
- const command = commandDisplayName(commandSpec);
486
- const commandName = path.basename(String(command)).toLowerCase();
487
- return commandName === "code-server"
488
- || commandName === "code-server.cmd"
489
- || command.endsWith(path.join("code-server", "out", "node", "entry.js"))
490
- ? "code-server"
491
- : "vscode-serve-web";
492
- }
493
-
494
- function resolveManagedIdeCommand(options) {
495
- if (options.ideCommand) {
496
- return makeCommandSpec(options.ideCommand, [], "explicit");
497
- }
498
-
499
- const rawHostKind = options.hostKind ?? process.env.AIB_MANAGED_HOST_KIND ?? "";
500
- if (rawHostKind === "code-server") {
501
- if (process.env.AIB_CODE_SERVER_PATH && commandOrFileExists(process.env.AIB_CODE_SERVER_PATH)) {
502
- return makeCommandSpec(process.env.AIB_CODE_SERVER_PATH, [], "env-code-server");
503
- }
504
- const packagedCommand = resolvePackagedCodeServerCommand();
505
- if (packagedCommand) {
506
- return packagedCommand;
507
- }
508
- const candidates = [
509
- path.join(rootDir, "runtimes", "code-server", "bin", process.platform === "win32" ? "code-server.cmd" : "code-server"),
510
- path.join(rootDir, "runtimes", "code-server", "bin", "code-server"),
511
- path.join(rootDir, "packages", "cli", "runtimes", "code-server", "bin", process.platform === "win32" ? "code-server.cmd" : "code-server"),
512
- path.join(rootDir, "packages", "cli", "runtimes", "code-server", "bin", "code-server"),
513
- "code-server"
514
- ].filter(Boolean);
515
- const found = candidates.find((candidate) => commandOrFileExists(candidate)) ?? "code-server";
516
- return makeCommandSpec(found, [], "code-server-path");
517
- }
518
-
519
- return makeCommandSpec(resolveIdeCommand({ ide: "code" }), [], "vscode-code");
520
- }
521
-
522
- function buildIdeCommandEnv(runDir) {
523
- const env = ideCommandEnv();
524
- if (process.platform !== "win32") {
525
- const homeDir = path.join(runDir, "home");
526
- env.HOME = homeDir;
527
- env.VSCODE_CLI_DATA_DIR = path.join(runDir, "cli-data");
528
- }
529
- return env;
530
- }
531
-
532
- function readJsonFile(filePath) {
533
- try {
534
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
535
- } catch {
536
- return null;
537
- }
538
- }
539
-
540
- function readMetadataByHostId(hostId) {
541
- const metadataPath = getMetadataPath(hostId);
542
- const metadata = fs.existsSync(metadataPath) ? readJsonFile(metadataPath) : null;
543
- if (!metadata || typeof metadata !== "object") {
544
- return null;
545
- }
546
- return metadata;
547
- }
548
-
549
- function readAllManagedHostEntries() {
550
- if (!fs.existsSync(managedRootDir)) {
551
- return [];
552
- }
553
- const entries = [];
554
- for (const item of fs.readdirSync(managedRootDir, { withFileTypes: true })) {
555
- if (!item.isDirectory()) {
556
- continue;
557
- }
558
- const metadata = readMetadataByHostId(item.name);
559
- if (!metadata) {
560
- continue;
561
- }
562
- entries.push({
563
- hostId: item.name,
564
- metadata
565
- });
566
- }
567
- return entries.sort((left, right) => String(left.hostId).localeCompare(String(right.hostId)));
568
- }
569
-
570
- function compactRuntimeStatus(status, metadata = {}) {
571
- return {
572
- hostId: status.hostId ?? metadata.hostId ?? null,
573
- hostKind: status.hostKind ?? metadata.hostKind ?? null,
574
- running: status.running === true,
575
- healthy: status.healthy === true,
576
- fixture: metadata.fixture ?? status.metadata?.fixture ?? null,
577
- runDir: metadata.runDir ?? status.metadata?.runDir ?? null,
578
- rootUrl: metadata.rootUrl ?? status.metadata?.rootUrl ?? null,
579
- reason: status.reason ?? null
580
- };
581
- }
582
-
583
- function writeMetadata(hostId, metadata) {
584
- writeJsonAtomic(getMetadataPath(hostId), metadata);
585
- }
586
-
587
- function removeMetadata(hostId) {
588
- fs.rmSync(getMetadataPath(hostId), { force: true });
589
- }
590
-
591
- function quoteCmdArg(value) {
592
- const raw = String(value);
593
- return `"${raw.replace(/%/g, "%%").replace(/"/g, '\\"')}"`;
594
- }
595
-
596
- function quotePowerShellString(value) {
597
- return `'${String(value).replace(/'/g, "''")}'`;
598
- }
599
-
600
- function writeWindowsDetachedScript(command, args, options) {
601
- ensureDir(path.dirname(options.stdoutPath));
602
- ensureDir(path.dirname(options.stderrPath));
603
- const scriptPath = `${options.stdoutPath}.cmd`;
604
- const cwd = options.cwd ?? rootDir;
605
- const commandLine = [
606
- quoteCmdArg(command),
607
- ...args.map(quoteCmdArg),
608
- ">",
609
- quoteCmdArg(options.stdoutPath),
610
- "2>",
611
- quoteCmdArg(options.stderrPath)
612
- ].join(" ");
613
- writeText(scriptPath, [
614
- "@echo off",
615
- `cd /d ${quoteCmdArg(cwd)}`,
616
- commandLine,
617
- ""
618
- ].join("\r\n"));
619
- return scriptPath;
620
- }
621
-
622
- function startWindowsHiddenScript(scriptPath, options) {
623
- const cwd = options.cwd ?? rootDir;
624
- const command = [
625
- "$p = Start-Process -FilePath 'cmd.exe'",
626
- `-ArgumentList @('/d','/s','/c', ${quotePowerShellString(scriptPath)})`,
627
- `-WorkingDirectory ${quotePowerShellString(cwd)}`,
628
- "-WindowStyle Hidden -PassThru;",
629
- "$p.Id"
630
- ].join(" ");
631
- const result = spawnSync("powershell.exe", [
632
- "-NoProfile",
633
- "-ExecutionPolicy",
634
- "Bypass",
635
- "-WindowStyle",
636
- "Hidden",
637
- "-Command",
638
- command
639
- ], {
640
- cwd,
641
- env: options.env ?? process.env,
642
- encoding: "utf8",
643
- windowsHide: true,
644
- stdio: ["ignore", "pipe", "pipe"]
645
- });
646
- if (result.status !== 0) {
647
- appendText(options.stderrPath, `${result.stdout ?? ""}${result.stderr ?? ""}${result.error ? result.error.message : ""}\n`);
648
- }
649
- const pid = Number(String(result.stdout ?? "").trim().split(/\r?\n/).pop());
650
- return {
651
- pid: Number.isFinite(pid) && pid > 0 ? pid : null,
652
- on() {},
653
- unref() {}
654
- };
655
- }
656
-
657
- function startLoggedProcess(command, args, options) {
658
- const spawnCommand = prepareSpawnCommand(command, args);
659
- if (options.detached === true && process.platform === "win32") {
660
- const scriptPath = writeWindowsDetachedScript(spawnCommand.command, spawnCommand.args, options);
661
- const child = startWindowsHiddenScript(scriptPath, options);
662
- return {
663
- child,
664
- command,
665
- args,
666
- stdoutPath: options.stdoutPath,
667
- stderrPath: options.stderrPath
668
- };
669
- }
670
-
671
- const stdoutFd = fs.openSync(options.stdoutPath, "a");
672
- const stderrFd = fs.openSync(options.stderrPath, "a");
673
- const child = spawn(spawnCommand.command, spawnCommand.args, {
674
- cwd: options.cwd ?? rootDir,
675
- env: options.env ?? process.env,
676
- detached: options.detached === true,
677
- windowsHide: true,
678
- windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments ?? false,
679
- stdio: ["ignore", stdoutFd, stderrFd]
680
- });
681
-
682
- child.on("error", (error) => {
683
- appendText(options.stderrPath, `${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
684
- });
685
- if (options.detached === true) {
686
- child.unref();
687
- }
688
-
689
- return {
690
- child,
691
- command,
692
- args,
693
- stdoutPath: options.stdoutPath,
694
- stderrPath: options.stderrPath
695
- };
696
- }
697
-
698
- function stopProcessTree(pid) {
699
- if (!Number.isInteger(pid) || pid <= 0) {
700
- return {
701
- attempted: false,
702
- pid
703
- };
704
- }
705
-
706
- if (process.platform === "win32") {
707
- const result = run("taskkill.exe", ["/pid", String(pid), "/t", "/f"], { timeoutMs: 10_000 });
708
- return {
709
- attempted: true,
710
- pid,
711
- status: result.status,
712
- error: result.error,
713
- stdoutTail: tail(result.stdout, 1000),
714
- stderrTail: tail(result.stderr, 1000)
715
- };
716
- }
717
-
718
- try {
719
- if (process.platform !== "win32") {
720
- process.kill(-pid, "SIGTERM");
721
- } else {
722
- process.kill(pid, "SIGTERM");
723
- }
724
- if (process.platform !== "win32" && !waitForProcessExit(pid, 2000)) {
725
- try {
726
- process.kill(-pid, "SIGKILL");
727
- } catch {
728
- try {
729
- process.kill(pid, "SIGKILL");
730
- } catch {
731
- // Keep the SIGTERM result; this is best-effort cleanup.
732
- }
733
- }
734
- waitForProcessExit(pid, 1000);
735
- }
736
- return {
737
- attempted: true,
738
- pid,
739
- status: 0,
740
- error: null
741
- };
742
- } catch (error) {
743
- if (process.platform !== "win32") {
744
- try {
745
- process.kill(pid, "SIGTERM");
746
- if (!waitForProcessExit(pid, 2000)) {
747
- try {
748
- process.kill(pid, "SIGKILL");
749
- } catch {
750
- // Keep the SIGTERM result; this is best-effort cleanup.
751
- }
752
- waitForProcessExit(pid, 1000);
753
- }
754
- return {
755
- attempted: true,
756
- pid,
757
- status: 0,
758
- error: null
759
- };
760
- } catch {
761
- // Keep the original process-group error for diagnostics.
762
- }
763
- }
764
- return {
765
- attempted: true,
766
- pid,
767
- status: 1,
768
- error: error instanceof Error ? error.message : String(error)
769
- };
770
- }
771
- }
772
-
773
- function stopProcessesByCommandLineFragment(fragment) {
774
- if (!fragment) {
775
- return {
776
- attempted: false,
777
- fragment
778
- };
779
- }
780
- if (process.platform !== "win32") {
781
- return stopProcessesByCommandLineFragmentUnix(fragment);
782
- }
783
-
784
- const escaped = String(fragment).replace(/'/g, "''");
785
- const script = [
786
- "$self = $PID",
787
- "$fragment = '" + escaped + "'",
788
- "Get-CimInstance Win32_Process |",
789
- "Where-Object { $_.ProcessId -ne $self -and $_.CommandLine -and $_.CommandLine.Contains($fragment) } |",
790
- "ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop; $_.ProcessId } catch {} }"
791
- ].join("\n");
792
- const result = run("powershell.exe", ["-NoProfile", "-Command", script], { timeoutMs: 10_000 });
793
- const killed = result.stdout
794
- .split(/\r?\n/)
795
- .map((line) => line.trim())
796
- .filter(Boolean).length;
797
- return {
798
- attempted: true,
799
- fragment,
800
- killed,
801
- status: result.status,
802
- error: result.error,
803
- stdoutTail: tail(result.stdout, 1000),
804
- stderrTail: tail(result.stderr, 1000)
805
- };
806
- }
807
-
808
- function stopProcessesByCommandLineFragmentUnix(fragment) {
809
- const result = run("ps", ["-eo", "pid=,args="], { timeoutMs: 10_000 });
810
- if (result.status !== 0) {
811
- return {
812
- attempted: true,
813
- fragment,
814
- killed: 0,
815
- status: result.status,
816
- error: result.error,
817
- stdoutTail: tail(result.stdout, 1000),
818
- stderrTail: tail(result.stderr, 1000)
819
- };
820
- }
821
-
822
- const currentPid = process.pid;
823
- const parentPid = process.ppid;
824
- const pids = result.stdout
825
- .split(/\r?\n/)
826
- .map((line) => {
827
- const match = line.match(/^\s*(\d+)\s+(.*)$/);
828
- if (!match) {
829
- return null;
830
- }
831
- return {
832
- pid: Number(match[1]),
833
- commandLine: match[2] ?? ""
834
- };
835
- })
836
- .filter(Boolean)
837
- .filter((row) => row.pid !== currentPid && row.pid !== parentPid)
838
- .filter((row) => row.commandLine.includes(fragment))
839
- .map((row) => row.pid);
840
-
841
- const uniquePids = [...new Set(pids)].filter((pid) => Number.isInteger(pid) && pid > 0);
842
- let killed = 0;
843
- const errors = [];
844
- for (const pid of uniquePids) {
845
- try {
846
- process.kill(pid, "SIGTERM");
847
- killed += 1;
848
- } catch (error) {
849
- errors.push(`${pid}: ${error instanceof Error ? error.message : String(error)}`);
850
- }
851
- }
852
- if (killed > 0) {
853
- sleepSync(250);
854
- }
855
- for (const pid of uniquePids) {
856
- if (!isProcessAlive(pid)) {
857
- continue;
858
- }
859
- try {
860
- process.kill(pid, "SIGKILL");
861
- } catch (error) {
862
- errors.push(`${pid}: ${error instanceof Error ? error.message : String(error)}`);
863
- }
864
- }
865
- return {
866
- attempted: true,
867
- fragment,
868
- killed,
869
- status: errors.length === 0 ? 0 : 1,
870
- error: errors.join("; ") || null
871
- };
872
- }
873
-
874
- function stopOrphanedPackagedRuntimeProcesses(activeRunDirs = []) {
875
- if (process.platform !== "win32") {
876
- return {
877
- attempted: false,
878
- reason: "unsupported-platform"
879
- };
880
- }
881
-
882
- const escapedRoot = rootDir.replace(/'/g, "''");
883
- const activeList = activeRunDirs
884
- .filter(Boolean)
885
- .map((item) => "'" + String(item).replace(/'/g, "''") + "'")
886
- .join(",");
887
- const script = [
888
- "$root = '" + escapedRoot + "'",
889
- "$active = @(" + activeList + ")",
890
- "$runtimePattern = [regex]::Escape($root + '\\runtimes\\')",
891
- "$runPattern = [regex]::Escape($root + '\\.tmp\\managed-host-runs\\')",
892
- "Get-CimInstance Win32_Process |",
893
- "Where-Object {",
894
- " $cmd = $_.CommandLine",
895
- " $cmd -and",
896
- " $cmd -match $runtimePattern -and",
897
- " $cmd -match $runPattern -and",
898
- " -not ($active | Where-Object { $_ -and $cmd.Contains($_) })",
899
- "} |",
900
- "ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop; $_.ProcessId } catch {} }"
901
- ].join(" ");
902
- const result = run("powershell.exe", ["-NoProfile", "-Command", script], { timeoutMs: 10_000 });
903
- return {
904
- attempted: true,
905
- status: result.status,
906
- error: result.error,
907
- stdoutTail: tail(result.stdout, 1000),
908
- stderrTail: tail(result.stderr, 1000)
909
- };
910
- }
911
-
912
- function stopAibOwnedRuntimeProcesses() {
913
- if (process.platform !== "win32") {
914
- return stopAibOwnedRuntimeProcessesUnix();
915
- }
916
-
917
- const roots = [
918
- rootDir,
919
- path.join(rootDir, "packages", "cli")
920
- ].map((item) => String(item).replace(/'/g, "''"));
921
- const script = [
922
- "$roots = @(" + roots.map((item) => "'" + item + "'").join(",") + ")",
923
- "$processes = @(Get-CimInstance Win32_Process | Where-Object {",
924
- " $cmd = $_.CommandLine",
925
- " if (-not $cmd) { return $false }",
926
- " $underAibRoot = [bool]($roots | Where-Object { $_ -and $cmd.Contains($_) })",
927
- " $hasRunMarker = $cmd.Contains('managed-host-runs')",
928
- " $isAibWatchdog = $cmd.Contains('manage-serve-web-host.cjs') -and $cmd.Contains('watchdog') -and $underAibRoot",
929
- " $isPackagedRuntime = $underAibRoot -and ($cmd.Contains('\\runtimes\\code-server\\') -or $cmd.Contains('\\runtimes\\browser\\'))",
930
- " $isAibDetachedWrapper = $underAibRoot -and ($cmd.Contains('serve-web.stdout.txt.cmd') -or $cmd.Contains('headless-browser.stdout.txt.cmd') -or $cmd.Contains('watchdog.stdout.txt.cmd'))",
931
- " ($hasRunMarker -and $underAibRoot) -or $isAibWatchdog -or $isPackagedRuntime -or $isAibDetachedWrapper",
932
- "})",
933
- "$processes | ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop; $_.ProcessId } catch {} }"
934
- ].join("\n");
935
- const result = run("powershell.exe", ["-NoProfile", "-Command", script], { timeoutMs: 15_000 });
936
- const killed = result.stdout
937
- .split(/\r?\n/)
938
- .map((line) => line.trim())
939
- .filter(Boolean).length;
940
- return {
941
- attempted: true,
942
- killed,
943
- status: result.status,
944
- error: result.error,
945
- stdoutTail: tail(result.stdout, 1000),
946
- stderrTail: tail(result.stderr, 1000)
947
- };
948
- }
949
-
950
- function stopAibOwnedRuntimeProcessesUnix() {
951
- const roots = [
952
- rootDir,
953
- path.join(rootDir, "packages", "cli")
954
- ].map((item) => String(item));
955
- const result = run("ps", ["-eo", "pid=,args="], { timeoutMs: 10_000 });
956
- if (result.status !== 0) {
957
- return {
958
- attempted: true,
959
- killed: 0,
960
- status: result.status,
961
- error: result.error,
962
- stdoutTail: tail(result.stdout, 1000),
963
- stderrTail: tail(result.stderr, 1000)
964
- };
965
- }
966
-
967
- const currentPid = process.pid;
968
- const parentPid = process.ppid;
969
- const pids = result.stdout
970
- .split(/\r?\n/)
971
- .map((line) => {
972
- const match = line.match(/^\s*(\d+)\s+(.*)$/);
973
- if (!match) {
974
- return null;
975
- }
976
- return {
977
- pid: Number(match[1]),
978
- commandLine: match[2] ?? ""
979
- };
980
- })
981
- .filter(Boolean)
982
- .filter((row) => row.pid !== currentPid && row.pid !== parentPid)
983
- .filter((row) => isAibOwnedRuntimeCommandLine(row.commandLine, roots))
984
- .map((row) => row.pid);
985
-
986
- const uniquePids = [...new Set(pids)].filter((pid) => Number.isInteger(pid) && pid > 0);
987
- let killed = 0;
988
- const errors = [];
989
- for (const pid of uniquePids) {
990
- try {
991
- process.kill(pid, "SIGTERM");
992
- killed += 1;
993
- } catch (error) {
994
- errors.push(`${pid}: ${error instanceof Error ? error.message : String(error)}`);
995
- }
996
- }
997
- if (killed > 0) {
998
- sleepSync(250);
999
- }
1000
- for (const pid of uniquePids) {
1001
- if (!isProcessAlive(pid)) {
1002
- continue;
1003
- }
1004
- try {
1005
- process.kill(pid, "SIGKILL");
1006
- } catch (error) {
1007
- errors.push(`${pid}: ${error instanceof Error ? error.message : String(error)}`);
1008
- }
1009
- }
1010
-
1011
- return {
1012
- attempted: true,
1013
- killed,
1014
- status: errors.length === 0 ? 0 : 1,
1015
- error: errors.join("; ") || null
1016
- };
1017
- }
1018
-
1019
- function isAibOwnedRuntimeCommandLine(commandLine, roots) {
1020
- if (!commandLine) {
1021
- return false;
1022
- }
1023
- const underAibRoot = roots.some((root) => root && commandLine.includes(root));
1024
- if (!underAibRoot) {
1025
- return false;
1026
- }
1027
- const hasRunMarker = commandLine.includes("managed-host-runs");
1028
- const isAibWatchdog = commandLine.includes("manage-serve-web-host.cjs") && commandLine.includes("watchdog");
1029
- const isPackagedRuntime =
1030
- commandLine.includes("/runtimes/code-server/") ||
1031
- commandLine.includes("\\runtimes\\code-server\\") ||
1032
- commandLine.includes("/runtimes/browser/") ||
1033
- commandLine.includes("\\runtimes\\browser\\");
1034
- return hasRunMarker || isAibWatchdog || isPackagedRuntime;
1035
- }
1036
-
1037
- function sleepSync(ms) {
1038
- try {
1039
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1040
- } catch {
1041
- // Best-effort delay only.
1042
- }
1043
- }
1044
-
1045
- function waitForProcessExit(pid, timeoutMs) {
1046
- const deadline = Date.now() + timeoutMs;
1047
- while (Date.now() < deadline) {
1048
- if (!isProcessAlive(pid)) {
1049
- return true;
1050
- }
1051
- sleepSync(50);
1052
- }
1053
- return !isProcessAlive(pid);
1054
- }
1055
-
1056
- function readTtlMs(value, fallbackMs = 15 * 60_000) {
1057
- if (value === null || value === undefined || value === "") {
1058
- return fallbackMs;
1059
- }
1060
- const parsed = Number(value);
1061
- return Number.isFinite(parsed) ? parsed : fallbackMs;
1062
- }
1063
-
1064
- function metadataLastActivityMs(metadata) {
1065
- const raw = metadata?.lastActivityAt ?? metadata?.updatedAt ?? metadata?.startedAt;
1066
- const parsed = typeof raw === "string" ? Date.parse(raw) : NaN;
1067
- return Number.isFinite(parsed) ? parsed : 0;
1068
- }
1069
-
1070
- function isMetadataExpired(metadata, ttlMs, now = Date.now()) {
1071
- if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
1072
- return false;
1073
- }
1074
- const lastActivityMs = metadataLastActivityMs(metadata);
1075
- return lastActivityMs > 0 && now - lastActivityMs > ttlMs;
1076
- }
1077
-
1078
- async function tryFetch(url) {
1079
- try {
1080
- const response = await fetch(url);
1081
- return {
1082
- ok: response.ok,
1083
- status: response.status,
1084
- contentType: response.headers.get("content-type")
1085
- };
1086
- } catch (error) {
1087
- return {
1088
- ok: false,
1089
- error: error instanceof Error ? error.message : String(error)
1090
- };
1091
- }
1092
- }
1093
-
1094
- function bridgeHealthUrl(registryEntry) {
1095
- if (!registryEntry || typeof registryEntry.host !== "string" || !Number.isInteger(registryEntry.port)) {
1096
- return null;
1097
- }
1098
-
1099
- return `http://${registryEntry.host}:${registryEntry.port}/health`;
1100
- }
1101
-
1102
- async function tryPostJson(url, body = {}) {
1103
- if (!url) {
1104
- return {
1105
- ok: false,
1106
- error: "missing url"
1107
- };
1108
- }
1109
-
1110
- try {
1111
- const response = await fetch(url, {
1112
- method: "POST",
1113
- headers: {
1114
- "Content-Type": "application/json"
1115
- },
1116
- body: JSON.stringify(body)
1117
- });
1118
- return {
1119
- ok: response.ok,
1120
- status: response.status,
1121
- contentType: response.headers.get("content-type")
1122
- };
1123
- } catch (error) {
1124
- return {
1125
- ok: false,
1126
- error: error instanceof Error ? error.message : String(error)
1127
- };
1128
- }
1129
- }
1130
-
1131
- async function waitFor(name, timeoutMs, pollMs, getState, isReady) {
1132
- const startedAt = Date.now();
1133
- const attempts = [];
1134
-
1135
- while (Date.now() - startedAt <= timeoutMs) {
1136
- const state = await getState();
1137
- attempts.push(state);
1138
- if (isReady(state)) {
1139
- return {
1140
- ok: true,
1141
- durationMs: Date.now() - startedAt,
1142
- attempts: attempts.length,
1143
- state
1144
- };
1145
- }
1146
- await sleep(pollMs);
1147
- }
1148
-
1149
- return {
1150
- ok: false,
1151
- durationMs: Date.now() - startedAt,
1152
- attempts: attempts.length,
1153
- error: `${name} did not become ready within ${timeoutMs}ms.`,
1154
- lastState: attempts.at(-1) ?? null
1155
- };
1156
- }
1157
-
1158
- async function allocatePort() {
1159
- return new Promise((resolve, reject) => {
1160
- const server = net.createServer();
1161
- server.once("error", reject);
1162
- server.listen(0, "127.0.0.1", () => {
1163
- const address = server.address();
1164
- const port = address && typeof address === "object" ? address.port : null;
1165
- server.close(() => {
1166
- if (Number.isInteger(port)) {
1167
- resolve(port);
1168
- } else {
1169
- reject(new Error("Failed to allocate a TCP port."));
1170
- }
1171
- });
1172
- });
1173
- });
1174
- }
1175
-
1176
- async function acquireLock(hostId, timeoutMs = 15_000, staleMs = 120_000) {
1177
- const lockDir = getLockDir(hostId);
1178
- const startedAt = Date.now();
1179
- ensureDir(path.dirname(lockDir));
1180
-
1181
- while (Date.now() - startedAt <= timeoutMs) {
1182
- try {
1183
- fs.mkdirSync(lockDir);
1184
- writeJson(path.join(lockDir, "owner.json"), {
1185
- pid: process.pid,
1186
- acquiredAt: new Date().toISOString()
1187
- });
1188
- return {
1189
- hostId,
1190
- lockDir,
1191
- release() {
1192
- fs.rmSync(lockDir, { recursive: true, force: true });
1193
- }
1194
- };
1195
- } catch (error) {
1196
- if (error && error.code === "EEXIST") {
1197
- const owner = readJsonFile(path.join(lockDir, "owner.json"));
1198
- const ownerPid = Number.isInteger(owner?.pid) ? owner.pid : null;
1199
- const ownerAlive = ownerPid !== null && isProcessAlive(ownerPid);
1200
- const stat = fs.statSync(lockDir);
1201
- if ((ownerPid !== null && !ownerAlive) || Date.now() - stat.mtimeMs > staleMs) {
1202
- fs.rmSync(lockDir, { recursive: true, force: true });
1203
- continue;
1204
- }
1205
- await sleep(250);
1206
- continue;
1207
- }
1208
- throw error;
1209
- }
1210
- }
1211
-
1212
- throw new Error(`Timed out acquiring managed host lock: ${lockDir}`);
1213
- }
1214
-
1215
- function getMatchingRegistryEntry(metadata) {
1216
- if (!metadata || typeof metadata.instanceId !== "string") {
1217
- return null;
1218
- }
1219
-
1220
- return readRegistryEntries().find((entry) => entry.instanceId === metadata.instanceId) ?? null;
1221
- }
1222
-
1223
- async function getManagedServeWebHostStatus(options = {}) {
1224
- if (options.all === true) {
1225
- return getAllManagedServeWebHostStatuses(options);
1226
- }
1227
-
1228
- const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
1229
- const codeCommand = resolveManagedIdeCommand(options);
1230
- const hostKind = resolveManagedHostKind(options, codeCommand);
1231
- const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
1232
- const expectedVsix = options.vsix ? fingerprintFile(path.resolve(options.vsix)) : null;
1233
- const metadata = readMetadataByHostId(hostId);
1234
- if (!metadata) {
1235
- return {
1236
- ok: true,
1237
- hostId,
1238
- running: false,
1239
- healthy: false,
1240
- reason: "metadata-not-found"
1241
- };
1242
- }
1243
-
1244
- const registryEntry = getMatchingRegistryEntry(metadata);
1245
- const rootUrl = metadata.rootUrl ?? (Number.isInteger(metadata.port) ? `http://127.0.0.1:${metadata.port}` : null);
1246
- const http = rootUrl ? await tryFetch(rootUrl) : { ok: false, error: "missing rootUrl" };
1247
- const bridgeHttp = registryEntry
1248
- ? await tryPostJson(bridgeHealthUrl(registryEntry))
1249
- : { ok: false, error: "missing registryEntry" };
1250
- const serveWebAlive = isProcessAlive(metadata.serveWebPid);
1251
- const browserAlive = isProcessAlive(metadata.browserPid);
1252
- const extensionHostAlive = registryEntry ? isProcessAlive(registryEntry.pid) : false;
1253
- const workspaceMatches = registryEntry ? workspaceMatchesFixture(registryEntry, fixture) : false;
1254
- const vsixMatches = expectedVsix ? fingerprintsMatch(metadata.vsixFingerprint, expectedVsix) : true;
1255
- const healthy = serveWebAlive && browserAlive && extensionHostAlive && http.ok === true && bridgeHttp.ok === true && workspaceMatches && vsixMatches;
1256
-
1257
- return {
1258
- ok: true,
1259
- hostId,
1260
- running: serveWebAlive || browserAlive || extensionHostAlive,
1261
- healthy,
1262
- metadata,
1263
- health: {
1264
- serveWebAlive,
1265
- browserAlive,
1266
- extensionHostAlive,
1267
- workspaceMatches,
1268
- vsixMatches,
1269
- expectedVsix,
1270
- http,
1271
- bridgeHttp,
1272
- registryEntry
1273
- }
1274
- };
1275
- }
1276
-
1277
- async function getAllManagedServeWebHostStatuses(options = {}) {
1278
- const entries = readAllManagedHostEntries();
1279
- const runtimes = [];
1280
- for (const entry of entries) {
1281
- const metadata = entry.metadata;
1282
- const status = await getManagedServeWebHostStatus({
1283
- ...options,
1284
- all: false,
1285
- hostId: entry.hostId,
1286
- fixture: metadata.fixture ?? defaultFixtureDir,
1287
- vsix: metadata.vsix,
1288
- hostKind: metadata.hostKind
1289
- });
1290
- runtimes.push(compactRuntimeStatus(status, metadata));
1291
- }
1292
- return {
1293
- ok: true,
1294
- all: true,
1295
- count: runtimes.length,
1296
- running: runtimes.filter((runtime) => runtime.running === true).length,
1297
- healthy: runtimes.filter((runtime) => runtime.healthy === true).length,
1298
- runtimes
1299
- };
1300
- }
1301
-
1302
- async function startManagedServeWebHost(options = {}) {
1303
- const startedAt = Date.now();
1304
- const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
1305
- const ttlMs = readTtlMs(options.ttlMs);
1306
- const intervalMs = Number.isFinite(Number(options.intervalMs)) && Number(options.intervalMs) > 0
1307
- ? Number(options.intervalMs)
1308
- : 30_000;
1309
- const waitMs = Number.isInteger(options.waitMs) ? options.waitMs : 45_000;
1310
- const pollMs = Number.isInteger(options.pollMs) ? options.pollMs : 500;
1311
- const vsix = path.resolve(options.vsix ?? defaultVsixPath);
1312
- let vsixFingerprint = null;
1313
- const reuse = options.reuse !== false;
1314
- const codeCommand = resolveManagedIdeCommand(options);
1315
- const hostKind = resolveManagedHostKind(options, codeCommand);
1316
- const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
1317
- const runDir = path.resolve(options.runDir ?? path.join(defaultRunsDir, `${hostId}-${timestampForPath()}`));
1318
- const logsDir = path.join(runDir, "logs");
1319
- const stepsDir = path.join(runDir, "steps");
1320
- const userDataDir = path.join(runDir, "user-data");
1321
- const serverDataDir = path.join(runDir, "server-data");
1322
- const extensionsDir = path.join(runDir, "extensions");
1323
- const activeExtensionsDir = hostKind === "code-server" || process.platform === "win32"
1324
- ? extensionsDir
1325
- : path.join(serverDataDir, "extensions");
1326
- const ideEnv = buildIdeCommandEnv(runDir);
1327
- const steps = [];
1328
- const failures = [];
1329
- const beforeRegistry = readRegistryEntries();
1330
- const beforeInstanceIds = new Set(beforeRegistry.map((entry) => entry.instanceId));
1331
- let serveWebProcess = null;
1332
- let browserProcess = null;
1333
- let metadata = null;
1334
-
1335
- ensureDir(logsDir);
1336
- ensureDir(stepsDir);
1337
- await cleanupExpiredManagedServeWebHosts({ ttlMs });
1338
-
1339
- async function step(name, fn) {
1340
- const stepIndex = String(steps.length + 1).padStart(2, "0");
1341
- const stepPath = path.join(stepsDir, `${stepIndex}-${name.replace(/[^a-z0-9]+/gi, "-").toLowerCase()}.json`);
1342
- const startedStepAt = Date.now();
1343
- const record = {
1344
- name,
1345
- ok: false,
1346
- startedAt: new Date(startedStepAt).toISOString()
1347
- };
1348
-
1349
- try {
1350
- record.detail = await fn();
1351
- record.ok = record.detail?.ok !== false;
1352
- if (!record.ok) {
1353
- failures.push(`${name}: ${record.detail?.error ?? "failed"}`);
1354
- }
1355
- } catch (error) {
1356
- record.ok = false;
1357
- record.error = error instanceof Error ? error.message : String(error);
1358
- failures.push(`${name}: ${record.error}`);
1359
- } finally {
1360
- record.durationMs = Date.now() - startedStepAt;
1361
- record.finishedAt = new Date().toISOString();
1362
- record.path = stepPath;
1363
- writeJson(stepPath, record);
1364
- steps.push(record);
1365
- }
1366
-
1367
- return record;
1368
- }
1369
-
1370
- const lock = await acquireLock(hostId, Math.max(90_000, waitMs + 45_000));
1371
- try {
1372
- await step("ensure fresh extension vsix", async () => {
1373
- const freshness = getExtensionVsixFreshness(vsix);
1374
- if (!freshness.supported) {
1375
- vsixFingerprint = fingerprintFile(vsix);
1376
- return {
1377
- ok: true,
1378
- skipped: true,
1379
- reason: freshness.reason,
1380
- vsix: freshness.vsix,
1381
- vsixFingerprint
1382
- };
1383
- }
1384
-
1385
- if (!freshness.stale) {
1386
- vsixFingerprint = fingerprintFile(vsix);
1387
- return {
1388
- ok: true,
1389
- packaged: false,
1390
- stale: false,
1391
- vsix: freshness.vsix,
1392
- vsixMtimeMs: freshness.vsixMtimeMs,
1393
- newestInput: freshness.newestInput,
1394
- vsixFingerprint
1395
- };
1396
- }
1397
-
1398
- const packageLock = await acquireLock("extension-vsix-package", 180_000, 300_000);
1399
- try {
1400
- const lockedFreshness = getExtensionVsixFreshness(vsix);
1401
- if (lockedFreshness.supported && !lockedFreshness.stale) {
1402
- vsixFingerprint = fingerprintFile(vsix);
1403
- return {
1404
- ok: true,
1405
- packaged: false,
1406
- packagedByOtherProcess: true,
1407
- stale: false,
1408
- vsix: lockedFreshness.vsix,
1409
- vsixMtimeMs: lockedFreshness.vsixMtimeMs,
1410
- newestInput: lockedFreshness.newestInput,
1411
- vsixFingerprint
1412
- };
1413
- }
1414
-
1415
- const packageResult = packageExtensionVsix();
1416
- vsixFingerprint = fingerprintFile(vsix);
1417
- return {
1418
- ok: packageResult.status === 0 && Boolean(vsixFingerprint),
1419
- packaged: true,
1420
- stale: true,
1421
- status: packageResult.status,
1422
- error: packageResult.error ?? null,
1423
- stdoutTail: tail(packageResult.stdout),
1424
- stderrTail: tail(packageResult.stderr),
1425
- durationMs: packageResult.durationMs,
1426
- vsix: freshness.vsix,
1427
- previousVsixMtimeMs: freshness.vsixMtimeMs,
1428
- newestInput: freshness.newestInput,
1429
- vsixFingerprint
1430
- };
1431
- } finally {
1432
- packageLock.release();
1433
- }
1434
- });
1435
- if (failures.length > 0) {
1436
- return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, steps, failures, orphanCleanup: null });
1437
- }
1438
-
1439
- const orphanCleanup = readMetadataByHostId(hostId) ? null : stopProcessesByCommandLineFragment(hostId);
1440
- if (reuse) {
1441
- const existingStatus = await getManagedServeWebHostStatus({ fixture, hostId, vsix, ideCommand: options.ideCommand, hostKind });
1442
- if (existingStatus.healthy) {
1443
- const updatedMetadata = {
1444
- ...existingStatus.metadata,
1445
- updatedAt: new Date().toISOString(),
1446
- lastActivityAt: new Date().toISOString(),
1447
- ttlMs,
1448
- watchdogIntervalMs: intervalMs
1449
- };
1450
- writeMetadata(hostId, updatedMetadata);
1451
- startManagedServeWebWatchdog({ fixture, hostId, ttlMs, intervalMs, logsDir: existingStatus.metadata.logsDir ?? logsDir, ideCommand: options.ideCommand, hostKind });
1452
- return buildStartSummary({
1453
- ok: true,
1454
- reused: true,
1455
- startedAt,
1456
- hostId,
1457
- fixture,
1458
- vsix,
1459
- codeCommand,
1460
- hostKind,
1461
- runDir: existingStatus.metadata.runDir ?? runDir,
1462
- logsDir: existingStatus.metadata.logsDir ?? logsDir,
1463
- stepsDir: existingStatus.metadata.stepsDir ?? stepsDir,
1464
- metadata: updatedMetadata,
1465
- steps,
1466
- failures,
1467
- orphanCleanup
1468
- });
1469
- }
1470
- if (existingStatus.metadata) {
1471
- await stopManagedServeWebHost({ fixture, hostId, removeMetadataOnlyIfKnown: true });
1472
- }
1473
- } else {
1474
- await stopManagedServeWebHost({ fixture, hostId, removeMetadataOnlyIfKnown: true });
1475
- }
1476
-
1477
- await step("prepare profile", async () => {
1478
- ensureDir(path.join(runDir, "home"));
1479
- ensureDir(path.join(runDir, "cli-data"));
1480
- ensureDir(path.join(userDataDir, "User"));
1481
- ensureDir(path.join(serverDataDir, "data", "User"));
1482
- ensureDir(serverDataDir);
1483
- ensureDir(extensionsDir);
1484
- ensureDir(activeExtensionsDir);
1485
- const settings = {
1486
- "security.workspace.trust.enabled": false,
1487
- "workbench.startupEditor": "none"
1488
- };
1489
- writeJson(path.join(userDataDir, "User", "settings.json"), settings);
1490
- writeJson(path.join(serverDataDir, "data", "User", "settings.json"), settings);
1491
- return {
1492
- ok: true,
1493
- runDir,
1494
- userDataDir,
1495
- serverDataDir,
1496
- extensionsDir
1497
- };
1498
- });
1499
-
1500
- await step("install extension", async () => {
1501
- if (!fs.existsSync(vsix)) {
1502
- return {
1503
- ok: false,
1504
- error: `VSIX does not exist: ${vsix}`
1505
- };
1506
- }
1507
- const result = runCommand(codeCommand, [
1508
- "--extensions-dir",
1509
- activeExtensionsDir,
1510
- "--install-extension",
1511
- vsix,
1512
- "--force"
1513
- ], { timeoutMs: 60_000, env: ideEnv });
1514
- const stdoutPath = path.join(logsDir, "install-extension.stdout.txt");
1515
- const stderrPath = path.join(logsDir, "install-extension.stderr.txt");
1516
- writeText(stdoutPath, result.stdout);
1517
- writeText(stderrPath, result.stderr);
1518
- return {
1519
- ok: result.status === 0,
1520
- status: result.status,
1521
- durationMs: result.durationMs,
1522
- stdoutPath,
1523
- stderrPath,
1524
- stdoutTail: tail(result.stdout),
1525
- stderrTail: tail(result.stderr),
1526
- error: result.error
1527
- };
1528
- });
1529
- if (failures.length > 0) {
1530
- return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, steps, failures, orphanCleanup });
1531
- }
1532
-
1533
- const port = Number.isInteger(options.port) ? options.port : await allocatePort();
1534
- const rootUrl = `http://127.0.0.1:${port}`;
1535
-
1536
- const serveWebStdoutPath = path.join(logsDir, "serve-web.stdout.txt");
1537
- const serveWebStderrPath = path.join(logsDir, "serve-web.stderr.txt");
1538
-
1539
- await step("start serve-web", async () => {
1540
- const args = hostKind === "code-server"
1541
- ? [
1542
- "--auth",
1543
- "none",
1544
- "--bind-addr",
1545
- `127.0.0.1:${port}`,
1546
- "--disable-workspace-trust",
1547
- "--disable-telemetry",
1548
- "--disable-update-check",
1549
- "--user-data-dir",
1550
- userDataDir,
1551
- "--extensions-dir",
1552
- activeExtensionsDir,
1553
- fixture
1554
- ]
1555
- : [
1556
- "--user-data-dir",
1557
- userDataDir,
1558
- ...(process.platform === "win32" ? ["--extensions-dir", activeExtensionsDir] : []),
1559
- "serve-web",
1560
- "--accept-server-license-terms",
1561
- "--host",
1562
- "127.0.0.1",
1563
- "--port",
1564
- String(port),
1565
- "--without-connection-token",
1566
- "--default-folder",
1567
- fixture,
1568
- "--server-data-dir",
1569
- serverDataDir,
1570
- "--log",
1571
- "trace",
1572
- ...(process.platform !== "win32" ? ["--cli-data-dir", ideEnv.VSCODE_CLI_DATA_DIR ?? path.join(runDir, "cli-data")] : [])
1573
- ];
1574
- serveWebProcess = startLoggedCommand(codeCommand, args, {
1575
- stdoutPath: serveWebStdoutPath,
1576
- stderrPath: serveWebStderrPath,
1577
- detached: true,
1578
- env: ideEnv
1579
- });
1580
- return {
1581
- ok: true,
1582
- pid: serveWebProcess.child.pid ?? null,
1583
- command: commandDisplayName(codeCommand),
1584
- commandSource: codeCommand.source ?? null,
1585
- hostKind,
1586
- args,
1587
- stdoutPath: serveWebStdoutPath,
1588
- stderrPath: serveWebStderrPath
1589
- };
1590
- });
1591
-
1592
- await step("wait for serve-web http", async () => {
1593
- return waitFor(
1594
- "serve-web http",
1595
- waitMs,
1596
- pollMs,
1597
- () => tryFetch(rootUrl),
1598
- (state) => state.ok === true
1599
- );
1600
- });
1601
-
1602
- await step("wait for extension host agent", async () => {
1603
- return waitFor(
1604
- "extension host agent",
1605
- waitMs,
1606
- pollMs,
1607
- () => {
1608
- const stdout = readTextIfExists(serveWebStdoutPath);
1609
- const stderr = readTextIfExists(serveWebStderrPath);
1610
- const userDataLogs = readRecentLogText(path.join(userDataDir, "logs"));
1611
- const serverDataLogs = readRecentLogText(path.join(serverDataDir, "logs"));
1612
- const combined = `${stdout}\n${stderr}\n${userDataLogs}\n${serverDataLogs}`;
1613
- return {
1614
- ready: combined.includes("Extension host agent started")
1615
- || combined.includes("Extension host agent listening"),
1616
- stdoutTail: tail(stdout, 1000),
1617
- stderrTail: tail(stderr, 1000),
1618
- logTail: tail(`${userDataLogs}\n${serverDataLogs}`, 1000)
1619
- };
1620
- },
1621
- (state) => state.ready === true
1622
- );
1623
- });
1624
- if (failures.length > 0) {
1625
- stopProcessTree(serveWebProcess?.child?.pid);
1626
- return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, steps, failures, orphanCleanup });
1627
- }
1628
-
1629
- await step("open headless browser client", async () => {
1630
- const chromePath = resolveChromePath(options.chromePath);
1631
- if (!chromePath) {
1632
- return {
1633
- ok: false,
1634
- error: "Chrome or Edge executable was not found."
1635
- };
1636
- }
1637
-
1638
- const profileDir = path.join(runDir, "headless-browser-profile");
1639
- const stdoutPath = path.join(logsDir, "headless-browser.stdout.txt");
1640
- const stderrPath = path.join(logsDir, "headless-browser.stderr.txt");
1641
- const url = `${rootUrl}/?folder=${encodeURIComponent(fixture)}`;
1642
- const browserArgs = [
1643
- "--headless=new",
1644
- "--disable-gpu",
1645
- "--disable-dev-shm-usage",
1646
- "--disable-background-networking",
1647
- "--disable-sync",
1648
- "--no-sandbox",
1649
- "--remote-debugging-port=0",
1650
- "--no-first-run",
1651
- "--no-default-browser-check",
1652
- `--user-data-dir=${profileDir}`,
1653
- url
1654
- ];
1655
- if (process.platform === "win32") {
1656
- const scriptPath = writeWindowsDetachedScript(chromePath, browserArgs, {
1657
- cwd: rootDir,
1658
- stdoutPath,
1659
- stderrPath
1660
- });
1661
- browserProcess = startWindowsHiddenScript(scriptPath, {
1662
- cwd: rootDir,
1663
- stdoutPath,
1664
- stderrPath
1665
- });
1666
- } else {
1667
- const stdoutFd = fs.openSync(stdoutPath, "a");
1668
- const stderrFd = fs.openSync(stderrPath, "a");
1669
- browserProcess = spawn(chromePath, browserArgs, {
1670
- cwd: rootDir,
1671
- detached: true,
1672
- windowsHide: true,
1673
- stdio: ["ignore", stdoutFd, stderrFd]
1674
- });
1675
- }
1676
- browserProcess.on("error", () => {
1677
- // Later registry/health waits surface browser startup failures.
1678
- });
1679
- browserProcess.unref();
1680
-
1681
- return {
1682
- ok: true,
1683
- pid: browserProcess.pid ?? null,
1684
- chromePath,
1685
- profileDir,
1686
- devToolsActivePortPath: path.join(profileDir, "DevToolsActivePort"),
1687
- stdoutPath,
1688
- stderrPath,
1689
- url
1690
- };
1691
- });
1692
-
1693
- await step("wait for bridge registry entry", async () => {
1694
- const waitResult = await waitFor(
1695
- "fresh bridge registry entry",
1696
- waitMs,
1697
- pollMs,
1698
- () => {
1699
- const entries = readRegistryEntries();
1700
- const matchingFreshEntries = entries.filter(
1701
- (entry) => !beforeInstanceIds.has(entry.instanceId) && workspaceMatchesFixture(entry, fixture)
1702
- );
1703
- return {
1704
- ok: matchingFreshEntries.length > 0,
1705
- entry: matchingFreshEntries[0] ?? null,
1706
- entryCount: entries.length,
1707
- matchingFreshCount: matchingFreshEntries.length
1708
- };
1709
- },
1710
- (state) => state.ok === true
1711
- );
1712
- return waitResult;
1713
- });
1714
- if (failures.length > 0) {
1715
- stopProcessTree(browserProcess?.pid);
1716
- stopProcessTree(serveWebProcess?.child?.pid);
1717
- return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, steps, failures, orphanCleanup });
1718
- }
1719
-
1720
- const registryEntry = steps.at(-1)?.detail?.state?.entry ?? null;
1721
- await step("wait for bridge http health", async () => {
1722
- return waitFor(
1723
- "bridge http health",
1724
- waitMs,
1725
- pollMs,
1726
- () => tryPostJson(bridgeHealthUrl(registryEntry)),
1727
- (state) => state.ok === true
1728
- );
1729
- });
1730
- if (failures.length > 0) {
1731
- stopProcessTree(browserProcess?.pid);
1732
- stopProcessTree(serveWebProcess?.child?.pid);
1733
- return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, registryEntry, steps, failures, orphanCleanup });
1734
- }
1735
-
1736
- metadata = {
1737
- hostId,
1738
- kind: "serve-web-headless-client",
1739
- hostKind,
1740
- fixture,
1741
- vsix,
1742
- vsixFingerprint,
1743
- codeCommand: commandDisplayName(codeCommand),
1744
- codeCommandSource: codeCommand.source ?? null,
1745
- runDir,
1746
- logsDir,
1747
- stepsDir,
1748
- port,
1749
- rootUrl,
1750
- serveWebPid: serveWebProcess?.child?.pid ?? null,
1751
- browserPid: browserProcess?.pid ?? null,
1752
- instanceId: registryEntry?.instanceId ?? null,
1753
- registryPath: registryEntry?.registryPath ?? null,
1754
- workspaceFolders: registryEntry?.workspaceFolders ?? [],
1755
- startedAt: new Date(startedAt).toISOString(),
1756
- updatedAt: new Date().toISOString()
1757
- };
1758
- metadata.lastActivityAt = metadata.updatedAt;
1759
- metadata.ttlMs = ttlMs;
1760
- metadata.watchdogIntervalMs = intervalMs;
1761
- writeMetadata(hostId, metadata);
1762
- startManagedServeWebWatchdog({ fixture, hostId, ttlMs, intervalMs, logsDir, ideCommand: options.ideCommand, hostKind });
1763
-
1764
- return buildStartSummary({
1765
- ok: true,
1766
- reused: false,
1767
- startedAt,
1768
- hostId,
1769
- fixture,
1770
- vsix,
1771
- codeCommand: commandDisplayName(codeCommand),
1772
- codeCommandSource: codeCommand.source ?? null,
1773
- hostKind,
1774
- runDir,
1775
- logsDir,
1776
- stepsDir,
1777
- metadata,
1778
- registryEntry,
1779
- steps,
1780
- failures,
1781
- orphanCleanup
1782
- });
1783
- } finally {
1784
- lock.release();
1785
- }
1786
- }
1787
-
1788
- async function stopManagedServeWebHost(options = {}) {
1789
- if (options.all === true) {
1790
- return stopAllManagedServeWebHosts(options);
1791
- }
1792
-
1793
- const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
1794
- const codeCommand = resolveManagedIdeCommand(options);
1795
- const hostKind = resolveManagedHostKind(options, codeCommand);
1796
- const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
1797
- const metadata = readMetadataByHostId(hostId);
1798
- if (!metadata) {
1799
- const orphanCleanup = stopProcessesByCommandLineFragment(hostId);
1800
- return {
1801
- ok: true,
1802
- hostId,
1803
- hostKind,
1804
- stopped: (orphanCleanup.killed ?? 0) > 0,
1805
- reason: (orphanCleanup.killed ?? 0) > 0 ? "orphan-processes-stopped" : "metadata-not-found",
1806
- orphanCleanup
1807
- };
1808
- }
1809
-
1810
- const stopResults = [];
1811
- const watchdog = readJsonFile(getWatchdogPath(hostId));
1812
- if (watchdog && Number.isInteger(watchdog.pid) && watchdog.pid !== process.pid) {
1813
- stopResults.push({
1814
- name: "watchdog",
1815
- ...stopProcessTree(watchdog.pid)
1816
- });
1817
- }
1818
- if (Number.isInteger(metadata.browserPid)) {
1819
- stopResults.push({
1820
- name: "headless-browser",
1821
- ...stopProcessTree(metadata.browserPid)
1822
- });
1823
- }
1824
- if (Number.isInteger(metadata.serveWebPid)) {
1825
- stopResults.push({
1826
- name: "serve-web",
1827
- ...stopProcessTree(metadata.serveWebPid)
1828
- });
1829
- }
1830
- if (typeof metadata.runDir === "string") {
1831
- stopResults.push({
1832
- name: "run-dir-processes",
1833
- ...stopProcessesByCommandLineFragment(metadata.runDir)
1834
- });
1835
- }
1836
-
1837
- let registryCleanup = {
1838
- removed: false,
1839
- path: metadata.registryPath ?? null
1840
- };
1841
- if (typeof metadata.registryPath === "string") {
1842
- try {
1843
- fs.rmSync(metadata.registryPath, { force: true });
1844
- registryCleanup = {
1845
- removed: true,
1846
- path: metadata.registryPath
1847
- };
1848
- } catch (error) {
1849
- registryCleanup = {
1850
- removed: false,
1851
- path: metadata.registryPath,
1852
- error: error instanceof Error ? error.message : String(error)
1853
- };
1854
- }
1855
- }
1856
-
1857
- removeMetadata(hostId);
1858
- fs.rmSync(getWatchdogPath(hostId), { force: true });
1859
- return {
1860
- ok: true,
1861
- hostId,
1862
- hostKind: metadata.hostKind ?? hostKind,
1863
- stopped: true,
1864
- metadata,
1865
- stopResults,
1866
- registryCleanup
1867
- };
1868
- }
1869
-
1870
- async function stopAllManagedServeWebHosts(options = {}) {
1871
- const entries = readAllManagedHostEntries();
1872
- const stopped = [];
1873
- for (const entry of entries) {
1874
- const metadata = entry.metadata;
1875
- const result = await stopManagedServeWebHost({
1876
- ...options,
1877
- all: false,
1878
- hostId: entry.hostId,
1879
- fixture: metadata.fixture ?? defaultFixtureDir,
1880
- hostKind: metadata.hostKind
1881
- });
1882
- stopped.push({
1883
- hostId: entry.hostId,
1884
- hostKind: result.hostKind ?? metadata.hostKind ?? null,
1885
- fixture: metadata.fixture ?? null,
1886
- runDir: metadata.runDir ?? null,
1887
- stopped: result.stopped === true,
1888
- reason: result.reason ?? null
1889
- });
1890
- }
1891
- return {
1892
- ok: true,
1893
- all: true,
1894
- count: stopped.length,
1895
- stopped: stopped.filter((item) => item.stopped === true).length,
1896
- runtimes: stopped
1897
- };
1898
- }
1899
-
1900
- async function killManagedServeWebHosts(options = {}) {
1901
- if (options.all !== true) {
1902
- return {
1903
- ok: false,
1904
- code: "RUNTIME_KILL_REQUIRES_ALL",
1905
- error: "runtime kill currently requires --all."
1906
- };
1907
- }
1908
- const stopResult = await stopAllManagedServeWebHosts(options);
1909
- const orphanCleanup = stopAibOwnedRuntimeProcesses();
1910
- return {
1911
- ok: true,
1912
- all: true,
1913
- stop: stopResult,
1914
- killed: orphanCleanup.killed ?? 0,
1915
- orphanCleanup
1916
- };
1917
- }
1918
-
1919
- async function cleanupExpiredManagedServeWebHosts(options = {}) {
1920
- const ttlMs = readTtlMs(options.ttlMs);
1921
- if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
1922
- return {
1923
- ok: true,
1924
- ttlMs,
1925
- stopped: []
1926
- };
1927
- }
1928
- if (!fs.existsSync(managedRootDir)) {
1929
- return {
1930
- ok: true,
1931
- ttlMs,
1932
- stopped: []
1933
- };
1934
- }
1935
-
1936
- const stopped = [];
1937
- const activeRunDirs = [];
1938
- for (const item of fs.readdirSync(managedRootDir, { withFileTypes: true })) {
1939
- if (!item.isDirectory()) {
1940
- continue;
1941
- }
1942
- const hostId = item.name;
1943
- const metadata = readMetadataByHostId(hostId);
1944
- if (!metadata || !isMetadataExpired(metadata, ttlMs)) {
1945
- if (metadata && typeof metadata.runDir === "string" && metadata.runDir.length > 0) {
1946
- activeRunDirs.push(metadata.runDir);
1947
- }
1948
- continue;
1949
- }
1950
- const result = await stopManagedServeWebHost({ hostId, fixture: metadata.fixture ?? defaultFixtureDir });
1951
- stopped.push({
1952
- hostId,
1953
- fixture: metadata.fixture ?? null,
1954
- lastActivityAt: metadata.lastActivityAt ?? metadata.updatedAt ?? null,
1955
- stopped: result.stopped === true
1956
- });
1957
- }
1958
- const orphanCleanup = stopOrphanedPackagedRuntimeProcesses(activeRunDirs);
1959
-
1960
- return {
1961
- ok: true,
1962
- ttlMs,
1963
- stopped,
1964
- orphanCleanup
1965
- };
1966
- }
1967
-
1968
- async function runManagedServeWebWatchdog(options = {}) {
1969
- const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
1970
- const codeCommand = resolveManagedIdeCommand(options);
1971
- const hostKind = resolveManagedHostKind(options, codeCommand);
1972
- const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
1973
- const ttlMs = readTtlMs(options.ttlMs);
1974
- const intervalMs = Number.isFinite(Number(options.intervalMs)) && Number(options.intervalMs) > 0
1975
- ? Number(options.intervalMs)
1976
- : 30_000;
1977
- if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
1978
- return {
1979
- ok: true,
1980
- hostId,
1981
- hostKind,
1982
- watching: false,
1983
- reason: "ttl-disabled"
1984
- };
1985
- }
1986
-
1987
- writeJsonAtomic(getWatchdogPath(hostId), {
1988
- pid: process.pid,
1989
- hostId,
1990
- hostKind,
1991
- fixture,
1992
- ttlMs,
1993
- intervalMs,
1994
- startedAt: new Date().toISOString()
1995
- });
1996
-
1997
- while (true) {
1998
- const metadata = readMetadataByHostId(hostId);
1999
- if (!metadata) {
2000
- return {
2001
- ok: true,
2002
- hostId,
2003
- hostKind,
2004
- stopped: false,
2005
- reason: "metadata-not-found"
2006
- };
2007
- }
2008
- const status = await getManagedServeWebHostStatus({ fixture, hostId, vsix: metadata.vsix, hostKind: metadata.hostKind ?? hostKind });
2009
- if (!status.running) {
2010
- return {
2011
- ok: true,
2012
- hostId,
2013
- hostKind: metadata.hostKind ?? hostKind,
2014
- stopped: false,
2015
- reason: "not-running"
2016
- };
2017
- }
2018
- if (isMetadataExpired(metadata, ttlMs)) {
2019
- const result = await stopManagedServeWebHost({ fixture, hostId });
2020
- return {
2021
- ok: true,
2022
- hostId,
2023
- hostKind: metadata.hostKind ?? hostKind,
2024
- stopped: result.stopped === true,
2025
- reason: "ttl-expired"
2026
- };
2027
- }
2028
- await sleep(intervalMs);
2029
- }
2030
- }
2031
-
2032
- function startManagedServeWebWatchdog(options) {
2033
- const ttlMs = readTtlMs(options.ttlMs);
2034
- if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
2035
- return;
2036
- }
2037
- const existing = readJsonFile(getWatchdogPath(options.hostId));
2038
- if (existing && isProcessAlive(existing.pid)) {
2039
- return;
2040
- }
2041
- const stdoutPath = path.join(options.logsDir, "watchdog.stdout.txt");
2042
- const stderrPath = path.join(options.logsDir, "watchdog.stderr.txt");
2043
- const nodeRuntime = resolvePackagedNodeRuntime();
2044
- startLoggedProcess(nodeRuntime.command, [
2045
- path.join(__dirname, "manage-serve-web-host.cjs"),
2046
- "watchdog",
2047
- "--fixture",
2048
- options.fixture,
2049
- ...(options.ideCommand ? ["--ide-command", options.ideCommand] : []),
2050
- ...(options.hostKind ? ["--host-kind", options.hostKind] : []),
2051
- "--ttl-ms",
2052
- String(ttlMs),
2053
- "--interval-ms",
2054
- String(options.intervalMs ?? 30_000)
2055
- ], {
2056
- cwd: rootDir,
2057
- detached: true,
2058
- stdoutPath,
2059
- stderrPath
2060
- });
2061
- }
2062
-
2063
- function buildStartSummary(input) {
2064
- const codeCommand = input.codeCommand && typeof input.codeCommand === "object"
2065
- ? commandDisplayName(input.codeCommand)
2066
- : input.codeCommand;
2067
- const codeCommandSource = input.codeCommandSource
2068
- ?? (input.codeCommand && typeof input.codeCommand === "object" ? input.codeCommand.source : null);
2069
- const summaryPath = input.reused
2070
- ? path.join(input.runDir, `reuse-${timestampForPath()}.json`)
2071
- : path.join(input.runDir, "summary.json");
2072
- const summary = {
2073
- ok: input.ok,
2074
- reused: input.reused,
2075
- hostId: input.hostId,
2076
- kind: "serve-web-headless-client",
2077
- startedAt: new Date(input.startedAt).toISOString(),
2078
- finishedAt: new Date().toISOString(),
2079
- durationMs: Date.now() - input.startedAt,
2080
- fixture: input.fixture,
2081
- vsix: input.vsix,
2082
- vsixFingerprint: input.metadata?.vsixFingerprint ?? fingerprintFile(input.vsix),
2083
- codeCommand,
2084
- codeCommandSource,
2085
- hostKind: input.hostKind ?? input.metadata?.hostKind ?? "vscode-serve-web",
2086
- runDir: input.runDir,
2087
- metadata: input.metadata,
2088
- registryEntry: input.registryEntry ?? null,
2089
- artifacts: {
2090
- logsDir: input.logsDir,
2091
- stepsDir: input.stepsDir,
2092
- summaryPath
2093
- },
2094
- orphanCleanup: input.orphanCleanup ?? null,
2095
- steps: input.steps.map((record) => ({
2096
- name: record.name,
2097
- ok: record.ok,
2098
- durationMs: record.durationMs,
2099
- path: record.path
2100
- })),
2101
- failures: input.failures
2102
- };
2103
- writeJson(summaryPath, summary);
2104
- return summary;
2105
- }
2106
-
2107
- module.exports = {
2108
- buildHostId,
2109
- cleanupExpiredManagedServeWebHosts,
2110
- getManagedServeWebHostStatus,
2111
- killManagedServeWebHosts,
2112
- runManagedServeWebWatchdog,
2113
- startManagedServeWebHost,
2114
- stopManagedServeWebHost
2115
- };