chainlesschain 0.162.148 → 0.162.149

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 (231) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DwpCCp64.js → AIOps-BJif14yR.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-C9pBYocA.js → ActionButton-CXek6gJY.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-BWAkeXxK.js → Analytics-BgQhdAJi.js} +2 -2
  7. package/src/assets/web-panel/assets/{AppLayout-CivFGH6B.js → AppLayout-DTYl5NtR.js} +3 -3
  8. package/src/assets/web-panel/assets/{Audit-Cm8hRpZm.js → Audit-BzATQAeR.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DXdFMsE8.js → Backup-BzdPEQhL.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-uAwSTeql.js → BaseInput-BM0KVh8E.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-KU8eeJJE.js → Chat-D7fHXHkz.js} +4 -4
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-Wm34RR-b.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-C6AMDkjd.js → Checkbox-CAiSQ9vO.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-t2moDxcO.js → Codegen-Df40CrEe.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DCcsRRhN.js → Col-DIT2fNFU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DtB-8SAC.js → Community-CSTIbW2k.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CZm8THYA.js → Compact-DtqXZZUi.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-LiQgC7g1.js → Compliance-CUtzw6o7.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CA8SAxht.js → Cowork-CezmlnmU.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-CtRaF1wD.js → Cron-P4BITarg.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-6-br6Hub.js → Crosschain-BRRiYwvq.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Do2EccrL.js → DID-L5ijB9i4.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-wrXcbzGe.js → Dashboard-CFxro4ob.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-p3FsT245.js → Dropdown-BmAFCQ71.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-D95A2L8q.js → EmailListRenderer-DJBCEuon.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-cLuJW2zo.js → FamilyGuardDashboard-z4oLq6eT.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-Brgq_cbN.js → Federation-DsVCpHMF.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DWLCkNgh.js → FormItemContext-DP3bP5th.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BBqUfQd6.js → GenericCardRenderer-BAXP2Gc6.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-8F090w4I.js → Git-I3g_bAw9.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CCogEbxb.js → Governance-jHS5-ioS.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfLD7v7C.js → Inference-CPL7sfx9.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DDuHJewd.js → KnowledgeGraph-Jf236h4C.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-CE8KSEVC.js → Logs-Cm2tcSKg.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-DmwpZmhT.js → Marketplace-CZK82rhi.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-Bf7AazU8.js → McpTools-CZCeji7a.js} +4 -4
  37. package/src/assets/web-panel/assets/{Memory-D0QU_00S.js → Memory-3l2KVS9k.js} +2 -2
  38. package/src/assets/web-panel/assets/{MobileBridge-yXS9xdhx.js → MobileBridge-VYdpoLh6.js} +1 -1
  39. package/src/assets/web-panel/assets/{MobileProjects-BuDUoGUP.js → MobileProjects-CiB9cmm1.js} +1 -1
  40. package/src/assets/web-panel/assets/{Mtc-BYyHy28p.js → Mtc-wCFZbBuL.js} +2 -2
  41. package/src/assets/web-panel/assets/{MtcAudit-CK0aqpiZ.js → MtcAudit-1a3THIyG.js} +4 -4
  42. package/src/assets/web-panel/assets/{Multisig-mNimKYeb.js → Multisig-DMzLB2hG.js} +3 -3
  43. package/src/assets/web-panel/assets/{NLProgramming-AioAJ0YA.js → NLProgramming-D9wZbf6l.js} +1 -1
  44. package/src/assets/web-panel/assets/{Notes-CVVNCLHE.js → Notes-hrFe2ZM-.js} +3 -3
  45. package/src/assets/web-panel/assets/{NotificationSettings-C4ABj-TK.js → NotificationSettings-CHGX5Jwm.js} +1 -1
  46. package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +1 -0
  47. package/src/assets/web-panel/assets/{Organization-uozl_gWK.js → Organization-By7FkhtY.js} +4 -4
  48. package/src/assets/web-panel/assets/{Overflow-BCZOM2hK.js → Overflow--khGSzJn.js} +1 -1
  49. package/src/assets/web-panel/assets/{P2P-CArcaiaj.js → P2P-CAG9dH35.js} +2 -2
  50. package/src/assets/web-panel/assets/{PdhVaultBrowser-Sgq2Srr8.js → PdhVaultBrowser-B3SQZmK9.js} +3 -3
  51. package/src/assets/web-panel/assets/{Permissions-BYrQrXnH.js → Permissions-D35ec6JA.js} +4 -4
  52. package/src/assets/web-panel/assets/{PersonalDataHub-BzHStAbz.js → PersonalDataHub-CxBF7hW_.js} +2 -2
  53. package/src/assets/web-panel/assets/{Pipeline-k7KqxX1M.js → Pipeline-s6EtOO1s.js} +1 -1
  54. package/src/assets/web-panel/assets/{Privacy-IumTUWqx.js → Privacy-BbJYmWmO.js} +1 -1
  55. package/src/assets/web-panel/assets/{ProjectInit-BRyImYTf.js → ProjectInit-VGQq1jMT.js} +2 -2
  56. package/src/assets/web-panel/assets/{ProjectSettings-BFIqTBFk.js → ProjectSettings-B1ew3Teh.js} +2 -2
  57. package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +1 -0
  58. package/src/assets/web-panel/assets/{Providers-CyyFnUPs.js → Providers-mrO47FIK.js} +1 -1
  59. package/src/assets/web-panel/assets/{QrScannerModal-C82cRx-X.js → QrScannerModal-IJF2mHyw.js} +1 -1
  60. package/src/assets/web-panel/assets/{QuickAsk-DGxDF521.js → QuickAsk-CU0wN_Z0.js} +1 -1
  61. package/src/assets/web-panel/assets/{Recommend-Cns-Am1y.js → Recommend-DhN37R6G.js} +1 -1
  62. package/src/assets/web-panel/assets/{RemoteSession-R7OqAIlg.js → RemoteSession-D_xOX_hu.js} +2 -2
  63. package/src/assets/web-panel/assets/{Reputation-D7mgPIYV.js → Reputation-D_Ssv7uH.js} +1 -1
  64. package/src/assets/web-panel/assets/{Row-B3lEURyd.js → Row-Kx5dKxX7.js} +1 -1
  65. package/src/assets/web-panel/assets/{RssFeed-9_UVrpBt.js → RssFeed-BlGwqZkY.js} +2 -2
  66. package/src/assets/web-panel/assets/{Search-ClZeO80q.js → Search-hLFQzxpb.js} +1 -1
  67. package/src/assets/web-panel/assets/{Security-BNNJczEp.js → Security-DMlvsVt4.js} +3 -3
  68. package/src/assets/web-panel/assets/{Services-C5SjrWUj.js → Services-Bd0Qsj2m.js} +2 -2
  69. package/src/assets/web-panel/assets/{Skeleton-Ci_obQ-g.js → Skeleton-DFclq9X3.js} +1 -1
  70. package/src/assets/web-panel/assets/{Skills-DdebN15P.js → Skills-BNtln2qY.js} +1 -1
  71. package/src/assets/web-panel/assets/{Sla-OinZP2vi.js → Sla-Dch5H19G.js} +1 -1
  72. package/src/assets/web-panel/assets/{SpeechSettings-CxzbNF51.js → SpeechSettings-2UfQcU1g.js} +1 -1
  73. package/src/assets/web-panel/assets/{SyncSettings-D06N_66b.js → SyncSettings-CD9YQr4i.js} +2 -2
  74. package/src/assets/web-panel/assets/{Tasks-BdEdW0AZ.js → Tasks-BNu-7MzI.js} +1 -1
  75. package/src/assets/web-panel/assets/{Templates-F1ctKmPa.js → Templates-D1n9CaIR.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tenant-CVz1Urot.js → Tenant-DenL3iBW.js} +1 -1
  77. package/src/assets/web-panel/assets/Terminal-ZzU0Io1Y.js +3 -0
  78. package/src/assets/web-panel/assets/{TimelineRenderer-Doitzjmf.js → TimelineRenderer-D87IfukO.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tokens-BpyVRpVA.js → Tokens-BkiZc8E3.js} +1 -1
  80. package/src/assets/web-panel/assets/{Trigger-nWhWYTbU.js → Trigger-BNRSytGN.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trust-DK_G-wLx.js → Trust-Bwbd4X2m.js} +1 -1
  82. package/src/assets/web-panel/assets/{UkeySign-B5yBUojO.js → UkeySign-B8O2bs0o.js} +1 -1
  83. package/src/assets/web-panel/assets/{VideoEditing-C5D51qAe.js → VideoEditing-C2fL8zmH.js} +1 -1
  84. package/src/assets/web-panel/assets/{Wallet-CLbL9K7v.js → Wallet-BORYDdE1.js} +4 -4
  85. package/src/assets/web-panel/assets/{WebAuthn-DZUelI3V.js → WebAuthn-C0V5S2FM.js} +4 -4
  86. package/src/assets/web-panel/assets/{WorkflowEditor-8RPxt4KW.js → WorkflowEditor-2dR0fcHL.js} +1 -1
  87. package/src/assets/web-panel/assets/{chat-4N4tOA3d.js → chat-BmuAFAn8.js} +1 -1
  88. package/src/assets/web-panel/assets/{colors-zbbGVrua.js → colors-CN3smMcK.js} +1 -1
  89. package/src/assets/web-panel/assets/{compact-item-D7iMkbnE.js → compact-item-DCnxb4kW.js} +1 -1
  90. package/src/assets/web-panel/assets/{createContext-CBzPJv-w.js → createContext-vlR43pHn.js} +1 -1
  91. package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +1 -0
  92. package/src/assets/web-panel/assets/{hasIn-bHdrQSqZ.js → hasIn-D04tHYFd.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-DbE5D0WL.js → index-0DnaZGkC.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-308gCGh7.js → index-430S68MN.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-kmh1TxRa.js → index-B78xL3s2.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-QFObYeo1.js → index-BTPEZTk1.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BZb8F8YG.js → index-BXRg8GFQ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DE9j5YgT.js → index-BasvsWDM.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CiG1IZao.js → index-BeGDEXFs.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Bqmx24kh.js → index-BqhASEL4.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BCr0geV9.js → index-BudvKQfG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-S74FpAIn.js → index-C-6NO5il.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DyoNgbXl.js → index-C5Sxb1sE.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BtbbdLnf.js → index-CP9m9Ggr.js} +3 -3
  105. package/src/assets/web-panel/assets/{index-CkxK86vf.js → index-CTOCnIQ7.js} +1 -1
  106. package/src/assets/web-panel/assets/index-Cg2ldRfU.js +1 -0
  107. package/src/assets/web-panel/assets/{index-BTIjjXry.js → index-ClhAHDK3.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-B7b1hGry.js → index-CnXebZLL.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CPxkoKgA.js → index-CngCC8hC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Ci009ijp.js → index-Cr-A0FYJ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CdmMoYN1.js → index-CsAsCPJ6.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BBNr77E1.js → index-D28DeAAB.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-B1s0Bz9O.js → index-D5LZVZ0L.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-oDKNgCsP.js → index-D65_XseV.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-QNAG4JtR.js → index-D8vwp4qp.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CQfu1U78.js → index-DDwLgQY9.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BnBOpqv3.js → index-DIpY0qM5.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CQ6NcfWz.js → index-DTWg-18U.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D4XKpj6c.js → index-DTtRscUa.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-D1YDh7Wr.js → index-DYAtmqiv.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CvRMA9uT.js → index-D_n8_vfI.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CahryTX0.js → index-DbADHwhr.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BJ4ZGyW0.js → index-DbVsQBOH.js} +1 -1
  124. package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +1 -0
  125. package/src/assets/web-panel/assets/{index-xYCm6W2h.js → index-HhNjnCfe.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BS4_Mwk6.js → index-ToUh2b1-.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DISWAYce.js → index-TslMTwNy.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DmqlJed-.js → index-_FIkN3jd.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CqdPyWm5.js → index-hpvvtAZ3.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BsKehmmS.js → index-jzR7Ujuw.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DUyjUkY7.js → index-ljuBiQW9.js} +1 -1
  132. package/src/assets/web-panel/assets/{initDefaultProps-BD55yNBt.js → initDefaultProps-C3wUb4je.js} +1 -1
  133. package/src/assets/web-panel/assets/{motion-B5Bbe77U.js → motion-D-jYFUNA.js} +1 -1
  134. package/src/assets/web-panel/assets/{move-CTvIqHEH.js → move-tqb8nwJ2.js} +1 -1
  135. package/src/assets/web-panel/assets/{mtc-parser-BkNyPQKB.js → mtc-parser-DSOjMJMz.js} +1 -1
  136. package/src/assets/web-panel/assets/{omit-BaXJXgHA.js → omit-CJ7VimIW.js} +1 -1
  137. package/src/assets/web-panel/assets/{pickAttrs-Bw6-YTxH.js → pickAttrs-qXiG1iT3.js} +1 -1
  138. package/src/assets/web-panel/assets/{placementArrow-BLdbkLqJ.js → placementArrow-CkGBcy1Z.js} +1 -1
  139. package/src/assets/web-panel/assets/{responsiveObserve-IVYkzntU.js → responsiveObserve-D3WvTlLO.js} +1 -1
  140. package/src/assets/web-panel/assets/{slide-DFMFwwtY.js → slide-BEE2ftge.js} +1 -1
  141. package/src/assets/web-panel/assets/{statusUtils-CURYRtZ1.js → statusUtils-CsfBpxiG.js} +1 -1
  142. package/src/assets/web-panel/assets/{styleChecker-Cfz63s1r.js → styleChecker-xPrIiJPI.js} +1 -1
  143. package/src/assets/web-panel/assets/{useFlexGapSupport-COJL0KE3.js → useFlexGapSupport-C5Yd-SCR.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFs-YLLojQQf.js → useFs-D_MDS8HI.js} +1 -1
  145. package/src/assets/web-panel/assets/{usePersonalDataHub-BF_21qUC.js → usePersonalDataHub-BqJh-usA.js} +1 -1
  146. package/src/assets/web-panel/assets/{vnode-9ZpkA7bb.js → vnode-chm_p-gz.js} +1 -1
  147. package/src/assets/web-panel/assets/{zoom-DhtAfhl8.js → zoom-3-YDNz0Z.js} +1 -1
  148. package/src/assets/web-panel/index.html +1 -1
  149. package/src/assets/web-panel/remote-session-sw.js +43 -0
  150. package/src/command-manifest.json +22 -1
  151. package/src/commands/agent.js +44 -5
  152. package/src/commands/codeintel.js +252 -0
  153. package/src/commands/eval.js +248 -0
  154. package/src/commands/plugin.js +354 -0
  155. package/src/commands/team.js +456 -0
  156. package/src/gateways/ws/message-dispatcher.js +45 -2
  157. package/src/gateways/ws/remote-session-protocol.js +37 -0
  158. package/src/gateways/ws/ws-server.js +1 -1
  159. package/src/harness/prompt-compressor.js +27 -1
  160. package/src/harness/remote-command-ledger.js +158 -0
  161. package/src/harness/remote-session-push-apns.js +246 -0
  162. package/src/harness/remote-session-push-errors.js +15 -0
  163. package/src/harness/remote-session-push-fcm.js +9 -25
  164. package/src/harness/remote-session-push-huawei.js +174 -0
  165. package/src/harness/remote-session-push-oppo.js +171 -0
  166. package/src/harness/remote-session-push-senders.js +42 -0
  167. package/src/harness/remote-session-push-vivo.js +179 -0
  168. package/src/harness/remote-session-push-web.js +299 -0
  169. package/src/harness/remote-session-push-xiaomi.js +99 -0
  170. package/src/index.js +6 -0
  171. package/src/lib/agent-core.js +2 -0
  172. package/src/lib/agent-sandbox.js +161 -5
  173. package/src/lib/agent-team/task-lease.js +434 -0
  174. package/src/lib/agent-team/team-budget.js +165 -0
  175. package/src/lib/agent-team/team-mailbox.js +106 -0
  176. package/src/lib/agent-team/team-runner.js +266 -0
  177. package/src/lib/agent-team/team-worktree.js +204 -0
  178. package/src/lib/agents.js +18 -0
  179. package/src/lib/async-hook-supervisor.cjs +305 -0
  180. package/src/lib/eval/runner.js +163 -0
  181. package/src/lib/eval/tasks.js +456 -0
  182. package/src/lib/eval/trend.js +185 -0
  183. package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
  184. package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
  185. package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
  186. package/src/lib/lsp/__tests__/lsp-client.test.js +258 -0
  187. package/src/lib/lsp/__tests__/lsp-manager.test.js +264 -0
  188. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
  189. package/src/lib/lsp/code-intelligence.js +356 -0
  190. package/src/lib/lsp/jsonrpc-stream.js +139 -0
  191. package/src/lib/lsp/lsp-client.js +318 -0
  192. package/src/lib/lsp/lsp-manager.js +323 -0
  193. package/src/lib/lsp/lsp-server-registry.js +196 -0
  194. package/src/lib/plugin-monitor-supervisor.js +238 -0
  195. package/src/lib/plugin-runtime/agents.js +51 -0
  196. package/src/lib/plugin-runtime/bin.js +100 -0
  197. package/src/lib/plugin-runtime/hooks.js +100 -0
  198. package/src/lib/plugin-runtime/install.js +388 -0
  199. package/src/lib/plugin-runtime/lsp.js +75 -0
  200. package/src/lib/plugin-runtime/manifest.js +458 -0
  201. package/src/lib/plugin-runtime/mcp.js +89 -0
  202. package/src/lib/plugin-runtime/monitors.js +100 -0
  203. package/src/lib/plugin-runtime/policy.js +144 -0
  204. package/src/lib/plugin-runtime/reload.js +79 -0
  205. package/src/lib/plugin-runtime/scopes.js +197 -0
  206. package/src/lib/plugin-runtime/settings.js +119 -0
  207. package/src/lib/plugin-runtime/signature.js +107 -0
  208. package/src/lib/plugin-runtime/skills.js +43 -0
  209. package/src/lib/plugin-runtime/trust.js +140 -0
  210. package/src/lib/sandbox-egress-proxy.js +162 -0
  211. package/src/lib/sandbox-network-policy.js +134 -0
  212. package/src/lib/sandbox-v2.js +10 -4
  213. package/src/lib/settings-hook-events.cjs +78 -6
  214. package/src/lib/settings-hooks.cjs +29 -3
  215. package/src/lib/settings-loader.cjs +35 -1
  216. package/src/lib/skill-loader.js +18 -0
  217. package/src/lib/telemetry/span-recorder.js +237 -0
  218. package/src/repl/agent-repl.js +400 -4
  219. package/src/runtime/agent-core.js +546 -52
  220. package/src/runtime/coding-agent-contract-shared.cjs +82 -8
  221. package/src/runtime/coding-agent-policy.cjs +32 -7
  222. package/src/runtime/fallback-model.js +134 -7
  223. package/src/runtime/headless-runner.js +353 -108
  224. package/src/runtime/mcp-config.js +46 -0
  225. package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +0 -1
  226. package/src/assets/web-panel/assets/OrderTableRenderer-DIp8Xcbd.js +0 -1
  227. package/src/assets/web-panel/assets/Projects-Dpid9CDg.js +0 -1
  228. package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +0 -3
  229. package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +0 -1
  230. package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +0 -1
  231. package/src/assets/web-panel/assets/index-BXiw9dQf.js +0 -1
@@ -0,0 +1,258 @@
1
+ /**
2
+ * LSPClient tests — JSON-RPC handshake, request/response correlation,
3
+ * notification dispatch, timeouts and crash propagation. A fake child process
4
+ * is injected through `_deps.spawn`; no real language server is spawned.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
8
+ import { EventEmitter } from "events";
9
+ import {
10
+ LSPClient,
11
+ _deps,
12
+ pathToFileUri,
13
+ fileUriToPath,
14
+ normalizeUri,
15
+ buildSpawnCommand,
16
+ } from "../lsp-client.js";
17
+ import { encodeMessage, MessageBuffer } from "../jsonrpc-stream.js";
18
+
19
+ /** A fake language server process. Auto-replies to `initialize`; test controls the rest. */
20
+ class FakeChild extends EventEmitter {
21
+ constructor({ autoInit = true } = {}) {
22
+ super();
23
+ this.stdout = new EventEmitter();
24
+ this.stderr = new EventEmitter();
25
+ this._inbound = new MessageBuffer();
26
+ this.received = [];
27
+ this.killed = false;
28
+ // Arrow keeps `this` bound to the FakeChild (no this-alias needed).
29
+ this.stdin = {
30
+ writable: true,
31
+ write: (buf) => {
32
+ this._inbound.append(buf);
33
+ for (const msg of this._inbound.readAll()) {
34
+ this.received.push(msg);
35
+ if (autoInit && msg.method === "initialize") {
36
+ this.reply(msg.id, { capabilities: { definitionProvider: true } });
37
+ }
38
+ }
39
+ return true;
40
+ },
41
+ };
42
+ }
43
+ reply(id, result) {
44
+ this.stdout.emit("data", encodeMessage({ jsonrpc: "2.0", id, result }));
45
+ }
46
+ replyError(id, error) {
47
+ this.stdout.emit("data", encodeMessage({ jsonrpc: "2.0", id, error }));
48
+ }
49
+ notify(method, params) {
50
+ this.stdout.emit("data", encodeMessage({ jsonrpc: "2.0", method, params }));
51
+ }
52
+ kill() {
53
+ this.killed = true;
54
+ }
55
+ }
56
+
57
+ let origSpawn;
58
+ beforeEach(() => {
59
+ origSpawn = _deps.spawn;
60
+ });
61
+ afterEach(() => {
62
+ _deps.spawn = origSpawn;
63
+ vi.restoreAllMocks();
64
+ });
65
+
66
+ function makeClient(child, opts = {}) {
67
+ _deps.spawn = vi.fn(() => child);
68
+ return new LSPClient({
69
+ command: "fake-lsp",
70
+ args: ["--stdio"],
71
+ rootPath: "/proj",
72
+ ...opts,
73
+ });
74
+ }
75
+
76
+ describe("LSPClient handshake", () => {
77
+ it("spawns, sends initialize, and captures server capabilities", async () => {
78
+ const child = new FakeChild();
79
+ const client = makeClient(child);
80
+ const caps = await client.start();
81
+ expect(caps).toEqual({ definitionProvider: true });
82
+ expect(client.running).toBe(true);
83
+ // sent initialize then initialized notification
84
+ expect(child.received[0].method).toBe("initialize");
85
+ expect(child.received[1].method).toBe("initialized");
86
+ // spawn used shell:false
87
+ expect(_deps.spawn).toHaveBeenCalledWith(
88
+ "fake-lsp",
89
+ ["--stdio"],
90
+ expect.objectContaining({ shell: false }),
91
+ );
92
+ });
93
+ });
94
+
95
+ describe("LSPClient request/response", () => {
96
+ it("correlates a request to its response by id", async () => {
97
+ const child = new FakeChild();
98
+ const client = makeClient(child);
99
+ await client.start();
100
+ const p = client.request("textDocument/definition", { x: 1 });
101
+ // server replies to the definition request (id follows initialize=1 → 2)
102
+ const req = child.received.find(
103
+ (m) => m.method === "textDocument/definition",
104
+ );
105
+ child.reply(req.id, [
106
+ {
107
+ uri: "file:///proj/a.ts",
108
+ range: {
109
+ start: { line: 0, character: 0 },
110
+ end: { line: 0, character: 3 },
111
+ },
112
+ },
113
+ ]);
114
+ const result = await p;
115
+ expect(result[0].uri).toBe("file:///proj/a.ts");
116
+ });
117
+
118
+ it("rejects a request whose response carries an error", async () => {
119
+ const child = new FakeChild();
120
+ const client = makeClient(child);
121
+ await client.start();
122
+ const p = client.request("bad/method", {});
123
+ const req = child.received.find((m) => m.method === "bad/method");
124
+ child.replyError(req.id, { code: -32601, message: "method not found" });
125
+ await expect(p).rejects.toThrow(/method not found/);
126
+ });
127
+
128
+ it("times out a request with no response", async () => {
129
+ const child = new FakeChild();
130
+ const client = makeClient(child);
131
+ await client.start();
132
+ await expect(
133
+ client.request("slow/op", {}, { timeoutMs: 20 }),
134
+ ).rejects.toThrow(/timed out/);
135
+ });
136
+ });
137
+
138
+ describe("LSPClient notifications", () => {
139
+ it("emits server notifications by method", async () => {
140
+ const child = new FakeChild();
141
+ const client = makeClient(child);
142
+ await client.start();
143
+ const got = vi.fn();
144
+ client.on("notify:textDocument/publishDiagnostics", got);
145
+ child.notify("textDocument/publishDiagnostics", {
146
+ uri: "file:///proj/a.ts",
147
+ diagnostics: [],
148
+ });
149
+ expect(got).toHaveBeenCalledWith({
150
+ uri: "file:///proj/a.ts",
151
+ diagnostics: [],
152
+ });
153
+ });
154
+
155
+ it("auto-replies null to server→client requests so the server never blocks", async () => {
156
+ const child = new FakeChild();
157
+ const client = makeClient(child);
158
+ await client.start();
159
+ child.stdout.emit(
160
+ "data",
161
+ encodeMessage({
162
+ jsonrpc: "2.0",
163
+ id: 999,
164
+ method: "workspace/configuration",
165
+ params: {},
166
+ }),
167
+ );
168
+ const reply = child.received.find((m) => m.id === 999 && m.method == null);
169
+ expect(reply).toEqual({ jsonrpc: "2.0", id: 999, result: null });
170
+ });
171
+ });
172
+
173
+ describe("LSPClient lifecycle", () => {
174
+ it("fails all pending requests when the process exits", async () => {
175
+ const child = new FakeChild();
176
+ const client = makeClient(child);
177
+ await client.start();
178
+ const p = client.request("op", {});
179
+ child.emit("exit", 1, null);
180
+ await expect(p).rejects.toThrow(/exited|stopped/);
181
+ expect(client.running).toBe(false);
182
+ });
183
+
184
+ it("stop() shuts down gracefully and is idempotent", async () => {
185
+ const child = new FakeChild();
186
+ const client = makeClient(child);
187
+ await client.start();
188
+ // auto-reply to shutdown
189
+ child.stdin.write = (buf) => {
190
+ const mb = new MessageBuffer().append(buf);
191
+ for (const m of mb.readAll()) {
192
+ child.received.push(m);
193
+ if (m.method === "shutdown") child.reply(m.id, null);
194
+ }
195
+ return true;
196
+ };
197
+ await client.stop();
198
+ await client.stop(); // no throw second time
199
+ expect(child.killed).toBe(true);
200
+ });
201
+ });
202
+
203
+ describe("uri <-> path", () => {
204
+ it("round-trips a POSIX path", () => {
205
+ expect(fileUriToPath(pathToFileUri("/home/u/a b.ts"))).toBe(
206
+ process.platform === "win32" ? "\\home\\u\\a b.ts" : "/home/u/a b.ts",
207
+ );
208
+ });
209
+ it("builds a windows drive URI", () => {
210
+ expect(pathToFileUri("C:\\proj\\a.ts")).toBe("file:///C%3A/proj/a.ts");
211
+ });
212
+ });
213
+
214
+ describe("normalizeUri", () => {
215
+ it("lowercases the windows drive letter so client/server keys match", () => {
216
+ // path.resolve yields C:, servers echo c: — must collapse to one key
217
+ expect(normalizeUri("file:///C%3A/proj/a.ts")).toBe(
218
+ "file:///c%3A/proj/a.ts",
219
+ );
220
+ expect(normalizeUri("file:///c%3A/proj/a.ts")).toBe(
221
+ "file:///c%3A/proj/a.ts",
222
+ );
223
+ expect(normalizeUri("file:///C:/proj/a.ts")).toBe("file:///c:/proj/a.ts");
224
+ });
225
+ it("leaves POSIX file URIs unchanged", () => {
226
+ expect(normalizeUri("file:///home/u/a.ts")).toBe("file:///home/u/a.ts");
227
+ });
228
+ });
229
+
230
+ describe("buildSpawnCommand", () => {
231
+ const isWin = process.platform === "win32";
232
+ it("passes non-batch commands through verbatim", () => {
233
+ const spec = buildSpawnCommand("/usr/bin/gopls", []);
234
+ expect(spec).toEqual({
235
+ file: "/usr/bin/gopls",
236
+ args: [],
237
+ windowsVerbatimArguments: false,
238
+ });
239
+ });
240
+ it.runIf(isWin)(
241
+ "routes a .cmd shim through cmd.exe /d /s /c with a quoted path",
242
+ () => {
243
+ const spec = buildSpawnCommand(
244
+ "C:\\proj\\node_modules\\.bin\\typescript-language-server.cmd",
245
+ ["--stdio"],
246
+ );
247
+ expect(spec.windowsVerbatimArguments).toBe(true);
248
+ expect(spec.args.slice(0, 3)).toEqual(["/d", "/s", "/c"]);
249
+ expect(spec.args[3]).toBe(
250
+ '"C:\\proj\\node_modules\\.bin\\typescript-language-server.cmd" --stdio',
251
+ );
252
+ },
253
+ );
254
+ it.runIf(isWin)("quotes a path containing spaces", () => {
255
+ const spec = buildSpawnCommand("C:\\Program Files\\svr.cmd", []);
256
+ expect(spec.args[3]).toBe('"C:\\Program Files\\svr.cmd"');
257
+ });
258
+ });
@@ -0,0 +1,264 @@
1
+ /**
2
+ * LSPManager tests — server pooling, document open/change sync, diagnostics
3
+ * collection, and position conversion. A fake client is injected via
4
+ * `_deps.createClient`; the registry resolves a server via `_deps.existsSync`.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
8
+ import { EventEmitter } from "events";
9
+ import path from "path";
10
+ import {
11
+ LSPManager,
12
+ toLspPosition,
13
+ fromLspPosition,
14
+ _deps,
15
+ } from "../lsp-manager.js";
16
+ import { _deps as regDeps } from "../lsp-server-registry.js";
17
+
18
+ /** Minimal fake LSPClient: records notifications, canned requests, emits events. */
19
+ class FakeClient extends EventEmitter {
20
+ constructor(opts) {
21
+ super();
22
+ this.rootPath = opts.rootPath;
23
+ this.running = true;
24
+ this.notifications = [];
25
+ this.started = false;
26
+ }
27
+ async start() {
28
+ this.started = true;
29
+ return { definitionProvider: true };
30
+ }
31
+ notify(method, params) {
32
+ this.notifications.push({ method, params });
33
+ }
34
+ async request() {
35
+ return null;
36
+ }
37
+ async stop() {
38
+ this.running = false;
39
+ }
40
+ }
41
+
42
+ let origCreate, origRead, origExistsMgr, origExistsReg;
43
+ let lastClient;
44
+
45
+ beforeEach(() => {
46
+ origCreate = _deps.createClient;
47
+ origRead = _deps.readFileSync;
48
+ origExistsMgr = _deps.existsSync;
49
+ origExistsReg = regDeps.existsSync;
50
+
51
+ _deps.createClient = (opts) => {
52
+ lastClient = new FakeClient(opts);
53
+ return lastClient;
54
+ };
55
+ _deps.readFileSync = () => "const x = 1;\n";
56
+ // root inference: pretend the nearest /proj ancestor has package.json.
57
+ // Suffix match tolerates path.resolve prepending a drive letter on Windows.
58
+ _deps.existsSync = (p) =>
59
+ String(p).replace(/\\/g, "/").endsWith("/proj/package.json");
60
+ // registry: pretend the TS server bin exists in <root>/node_modules/.bin
61
+ const isWin = process.platform === "win32";
62
+ const binName = isWin
63
+ ? "typescript-language-server.cmd"
64
+ : "typescript-language-server";
65
+ regDeps.existsSync = (p) =>
66
+ String(p).replace(/\\/g, "/").endsWith(`/node_modules/.bin/${binName}`);
67
+ });
68
+
69
+ afterEach(() => {
70
+ _deps.createClient = origCreate;
71
+ _deps.readFileSync = origRead;
72
+ _deps.existsSync = origExistsMgr;
73
+ regDeps.existsSync = origExistsReg;
74
+ vi.restoreAllMocks();
75
+ });
76
+
77
+ describe("position helpers", () => {
78
+ it("converts 1-based ⇄ 0-based", () => {
79
+ expect(toLspPosition({ line: 1, col: 1 })).toEqual({
80
+ line: 0,
81
+ character: 0,
82
+ });
83
+ expect(toLspPosition({ line: 10, col: 5 })).toEqual({
84
+ line: 9,
85
+ character: 4,
86
+ });
87
+ expect(fromLspPosition({ line: 0, character: 0 })).toEqual({
88
+ line: 1,
89
+ col: 1,
90
+ });
91
+ expect(fromLspPosition({ line: 9, character: 4 })).toEqual({
92
+ line: 10,
93
+ col: 5,
94
+ });
95
+ });
96
+ it("clamps below 1 to 0 (no negatives to the server)", () => {
97
+ expect(toLspPosition({ line: 0, col: 0 })).toEqual({
98
+ line: 0,
99
+ character: 0,
100
+ });
101
+ });
102
+ });
103
+
104
+ describe("ensureFor", () => {
105
+ it("starts a server and sends didOpen the first time", async () => {
106
+ const mgr = new LSPManager();
107
+ const ready = await mgr.ensureFor("/proj/src/a.ts");
108
+ expect(ready.languageId).toBe("typescript");
109
+ expect(ready.root.replace(/\\/g, "/")).toMatch(/\/proj$/);
110
+ expect(lastClient.started).toBe(true);
111
+ const open = lastClient.notifications.find(
112
+ (n) => n.method === "textDocument/didOpen",
113
+ );
114
+ expect(open.params.textDocument.text).toBe("const x = 1;\n");
115
+ await mgr.disposeAll();
116
+ });
117
+
118
+ it("sends didChange (not a second didOpen) on the next access", async () => {
119
+ const mgr = new LSPManager();
120
+ await mgr.ensureFor("/proj/src/a.ts");
121
+ await mgr.ensureFor("/proj/src/a.ts");
122
+ const opens = lastClient.notifications.filter(
123
+ (n) => n.method === "textDocument/didOpen",
124
+ );
125
+ const changes = lastClient.notifications.filter(
126
+ (n) => n.method === "textDocument/didChange",
127
+ );
128
+ expect(opens).toHaveLength(1);
129
+ expect(changes).toHaveLength(1);
130
+ expect(changes[0].params.textDocument.version).toBe(2);
131
+ await mgr.disposeAll();
132
+ });
133
+
134
+ it("reuses one server for two files in the same project/language", async () => {
135
+ const mgr = new LSPManager();
136
+ await mgr.ensureFor("/proj/src/a.ts");
137
+ const first = lastClient;
138
+ await mgr.ensureFor("/proj/src/b.ts");
139
+ expect(lastClient).toBe(first); // same client instance
140
+ expect(mgr.activeServerCount).toBe(1);
141
+ await mgr.disposeAll();
142
+ });
143
+
144
+ it("returns unavailable for an unsupported extension", async () => {
145
+ const mgr = new LSPManager();
146
+ const ready = await mgr.ensureFor("/proj/readme.txt");
147
+ expect(ready.unavailable).toBe(true);
148
+ expect(ready.reason).toMatch(/no language mapping/);
149
+ await mgr.disposeAll();
150
+ });
151
+
152
+ it("returns unavailable when no server bin is installed", async () => {
153
+ regDeps.existsSync = () => false; // nothing installed
154
+ const mgr = new LSPManager();
155
+ const ready = await mgr.ensureFor("/proj/src/a.ts");
156
+ expect(ready.unavailable).toBe(true);
157
+ expect(ready.reason).toMatch(/no language server installed/);
158
+ await mgr.disposeAll();
159
+ });
160
+ });
161
+
162
+ describe("crash-loop restart guard", () => {
163
+ // Simulate a language server process dying: the client marks itself not
164
+ // running and emits `exit`, exactly as the real LSPClient does on child exit.
165
+ function crash(client) {
166
+ client.running = false;
167
+ client.emit("exit", 1, null);
168
+ }
169
+
170
+ it("re-spawns a crashed server on the next request (auto-restart)", async () => {
171
+ const mgr = new LSPManager();
172
+ await mgr.ensureFor("/proj/src/a.ts");
173
+ const first = lastClient;
174
+ crash(first);
175
+ const ready = await mgr.ensureFor("/proj/src/a.ts");
176
+ expect(ready.unavailable).toBeUndefined();
177
+ expect(lastClient).not.toBe(first); // a FRESH client was spawned
178
+ expect(lastClient.started).toBe(true);
179
+ expect(ready.client).toBe(lastClient);
180
+ await mgr.disposeAll();
181
+ });
182
+
183
+ it("quarantines a server that crash-loops past maxRestarts within the window", async () => {
184
+ let t = 1000;
185
+ const mgr = new LSPManager({
186
+ maxRestarts: 3,
187
+ restartWindowMs: 30000,
188
+ now: () => t,
189
+ });
190
+ for (let i = 0; i < 3; i++) {
191
+ await mgr.ensureFor("/proj/src/a.ts");
192
+ t += 100;
193
+ crash(lastClient);
194
+ }
195
+ // 3 crashes are now inside the window → the next request quarantines the
196
+ // server (degrade to text search) rather than thrash-respawning it.
197
+ const before = lastClient;
198
+ const ready = await mgr.ensureFor("/proj/src/a.ts");
199
+ expect(ready.unavailable).toBe(true);
200
+ expect(ready.quarantined).toBe(true);
201
+ expect(ready.reason).toMatch(/crash-looped/);
202
+ expect(lastClient).toBe(before); // did NOT spawn yet another
203
+ await mgr.disposeAll();
204
+ });
205
+
206
+ it("recovers and re-spawns once the crash window clears", async () => {
207
+ let t = 1000;
208
+ const mgr = new LSPManager({
209
+ maxRestarts: 3,
210
+ restartWindowMs: 30000,
211
+ now: () => t,
212
+ });
213
+ for (let i = 0; i < 3; i++) {
214
+ await mgr.ensureFor("/proj/src/a.ts");
215
+ t += 100;
216
+ crash(lastClient);
217
+ }
218
+ expect((await mgr.ensureFor("/proj/src/a.ts")).quarantined).toBe(true);
219
+ // Stay healthy past the window → crash history prunes → a fresh start is
220
+ // allowed again (a server isn't quarantined forever).
221
+ t += 30001;
222
+ const quarantined = lastClient;
223
+ const ready = await mgr.ensureFor("/proj/src/a.ts");
224
+ expect(ready.unavailable).toBeUndefined();
225
+ expect(lastClient).not.toBe(quarantined);
226
+ await mgr.disposeAll();
227
+ });
228
+ });
229
+
230
+ describe("diagnostics", () => {
231
+ it("collects pushed diagnostics keyed by file", async () => {
232
+ const mgr = new LSPManager();
233
+ await mgr.ensureFor("/proj/src/a.ts");
234
+ lastClient.emit("notify:textDocument/publishDiagnostics", {
235
+ uri: (await import("../lsp-client.js")).pathToFileUri(
236
+ path.resolve("/proj/src/a.ts"),
237
+ ),
238
+ diagnostics: [
239
+ {
240
+ message: "boom",
241
+ severity: 1,
242
+ range: {
243
+ start: { line: 0, character: 0 },
244
+ end: { line: 0, character: 1 },
245
+ },
246
+ },
247
+ ],
248
+ });
249
+ const diags = mgr.getDiagnostics("/proj/src/a.ts");
250
+ expect(diags).toHaveLength(1);
251
+ expect(diags[0].message).toBe("boom");
252
+ await mgr.disposeAll();
253
+ });
254
+ });
255
+
256
+ describe("disposeAll", () => {
257
+ it("stops all servers and blocks further use", async () => {
258
+ const mgr = new LSPManager();
259
+ await mgr.ensureFor("/proj/src/a.ts");
260
+ await mgr.disposeAll();
261
+ expect(lastClient.running).toBe(false);
262
+ await expect(mgr.ensureFor("/proj/src/a.ts")).rejects.toThrow(/disposed/);
263
+ });
264
+ });
@@ -0,0 +1,128 @@
1
+ /**
2
+ * lsp-server-registry tests — language mapping, bin resolution (project-local
3
+ * wins over PATH), plugin server registration, and availability probing. FS
4
+ * existence is faked via `_deps.existsSync`; no real toolchain needed.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
8
+ import path from "path";
9
+ import {
10
+ languageIdForFile,
11
+ resolveServer,
12
+ resolveBin,
13
+ probeServers,
14
+ registerLanguageServer,
15
+ _resetPluginServers,
16
+ _deps,
17
+ } from "../lsp-server-registry.js";
18
+
19
+ let origExists;
20
+ beforeEach(() => {
21
+ origExists = _deps.existsSync;
22
+ _resetPluginServers();
23
+ });
24
+ afterEach(() => {
25
+ _deps.existsSync = origExists;
26
+ _resetPluginServers();
27
+ });
28
+
29
+ /** existsSync true only for the given absolute paths (normalized separators). */
30
+ function existsFor(paths) {
31
+ const set = new Set(paths.map((p) => p.replace(/\\/g, "/")));
32
+ _deps.existsSync = (p) => set.has(String(p).replace(/\\/g, "/"));
33
+ }
34
+
35
+ describe("languageIdForFile", () => {
36
+ it("maps common extensions", () => {
37
+ expect(languageIdForFile("a.ts")).toBe("typescript");
38
+ expect(languageIdForFile("a.TSX")).toBe("typescriptreact");
39
+ expect(languageIdForFile("a.py")).toBe("python");
40
+ expect(languageIdForFile("a.go")).toBe("go");
41
+ expect(languageIdForFile("a.rs")).toBe("rust");
42
+ });
43
+ it("returns null for unsupported extensions", () => {
44
+ expect(languageIdForFile("a.txt")).toBeNull();
45
+ expect(languageIdForFile("noext")).toBeNull();
46
+ });
47
+ });
48
+
49
+ describe("resolveBin", () => {
50
+ it("prefers project node_modules/.bin over PATH", () => {
51
+ const isWin = process.platform === "win32";
52
+ const binName = isWin
53
+ ? "typescript-language-server.cmd"
54
+ : "typescript-language-server";
55
+ const localBin = path.join("/proj", "node_modules", ".bin", binName);
56
+ existsFor([localBin]);
57
+ const resolved = resolveBin("typescript-language-server", "/proj");
58
+ expect(resolved.replace(/\\/g, "/")).toBe(localBin.replace(/\\/g, "/"));
59
+ });
60
+
61
+ it("returns null when nothing resolves", () => {
62
+ existsFor([]);
63
+ expect(resolveBin("nonexistent-server", "/proj")).toBeNull();
64
+ });
65
+ });
66
+
67
+ describe("resolveServer", () => {
68
+ it("resolves a TS server command with --stdio args", () => {
69
+ const isWin = process.platform === "win32";
70
+ const binName = isWin
71
+ ? "typescript-language-server.cmd"
72
+ : "typescript-language-server";
73
+ existsFor([path.join("/proj", "node_modules", ".bin", binName)]);
74
+ const s = resolveServer("typescript", "/proj");
75
+ expect(s).toMatchObject({
76
+ languageId: "typescript",
77
+ id: "typescript-language-server",
78
+ args: ["--stdio"],
79
+ });
80
+ expect(s.command).toContain("typescript-language-server");
81
+ });
82
+
83
+ it("returns null for a language with no installed server", () => {
84
+ existsFor([]);
85
+ expect(resolveServer("rust", "/proj")).toBeNull();
86
+ });
87
+
88
+ it("returns null for an unknown language", () => {
89
+ existsFor([]);
90
+ expect(resolveServer("cobol", "/proj")).toBeNull();
91
+ });
92
+ });
93
+
94
+ describe("plugin server registration", () => {
95
+ it("registers an extra language server and its extensions", () => {
96
+ registerLanguageServer({
97
+ languageId: "zig",
98
+ extensions: [".zig"],
99
+ command: "zls",
100
+ args: [],
101
+ });
102
+ expect(languageIdForFile("a.zig")).toBe("zig");
103
+ const isWin = process.platform === "win32";
104
+ existsFor([
105
+ path.join("/proj", "node_modules", ".bin", isWin ? "zls.cmd" : "zls"),
106
+ ]);
107
+ const s = resolveServer("zig", "/proj");
108
+ expect(s).toMatchObject({ id: "zls" });
109
+ });
110
+
111
+ it("rejects an incomplete registration", () => {
112
+ expect(() => registerLanguageServer({ languageId: "x" })).toThrow(
113
+ /requires/,
114
+ );
115
+ });
116
+ });
117
+
118
+ describe("probeServers", () => {
119
+ it("reports availability per server family", () => {
120
+ existsFor([]); // nothing installed
121
+ const rows = probeServers("/proj");
122
+ expect(rows.every((r) => r.available === false)).toBe(true);
123
+ // one row per distinct server id (TS family collapses)
124
+ const ids = rows.map((r) => r.id);
125
+ expect(new Set(ids).size).toBe(ids.length);
126
+ expect(ids).toContain("typescript-language-server");
127
+ });
128
+ });