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,318 @@
1
+ /**
2
+ * LSPClient — one connection to a single language server over stdio.
3
+ *
4
+ * Speaks JSON-RPC 2.0 framed by `jsonrpc-stream.js`. Responsibilities:
5
+ * - spawn the server process (stdio pipes, UTF-8)
6
+ * - the `initialize` / `initialized` handshake
7
+ * - correlate `request` id → Promise, dispatch `notification`s to listeners
8
+ * - request timeouts, graceful `shutdown`/`exit`, and process-level
9
+ * `error`/`exit` propagation
10
+ *
11
+ * Testability: all process spawning goes through `_deps.spawn` so tests inject a
12
+ * fake stdio pair (see cli-dev.md `_deps` pattern — `vi.mock("child_process")`
13
+ * does not intercept this file). Nothing here is language-specific.
14
+ */
15
+
16
+ import { spawn as nodeSpawn } from "child_process";
17
+ import { EventEmitter } from "events";
18
+ import { encodeMessage, MessageBuffer } from "./jsonrpc-stream.js";
19
+
20
+ const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
21
+ const DEFAULT_INIT_TIMEOUT_MS = 30_000;
22
+
23
+ // Injectable so unit tests can supply a fake child process.
24
+ export const _deps = { spawn: nodeSpawn };
25
+
26
+ export class LSPClient extends EventEmitter {
27
+ /**
28
+ * @param {object} opts
29
+ * @param {string} opts.command server executable
30
+ * @param {string[]} [opts.args] server args (e.g. ["--stdio"])
31
+ * @param {string} opts.rootPath absolute project root
32
+ * @param {object} [opts.env] extra env vars
33
+ * @param {number} [opts.requestTimeoutMs]
34
+ * @param {object} [opts.capabilities] client capabilities override
35
+ */
36
+ constructor(opts = {}) {
37
+ super();
38
+ this.command = opts.command;
39
+ this.args = Array.isArray(opts.args) ? opts.args : [];
40
+ this.rootPath = opts.rootPath;
41
+ this.env = opts.env || {};
42
+ this.requestTimeoutMs = opts.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS;
43
+ this.capabilitiesOverride = opts.capabilities || null;
44
+
45
+ this._child = null;
46
+ this._buffer = new MessageBuffer();
47
+ this._nextId = 1;
48
+ /** @type {Map<number, {resolve,reject,timer}>} */
49
+ this._pending = new Map();
50
+ this._initialized = false;
51
+ this._closed = false;
52
+ /** server-reported capabilities from `initialize` */
53
+ this.serverCapabilities = null;
54
+ }
55
+
56
+ get running() {
57
+ return Boolean(this._child) && !this._closed;
58
+ }
59
+
60
+ /** Spawn the process and run the initialize handshake. Resolves when ready. */
61
+ async start({ initTimeoutMs = DEFAULT_INIT_TIMEOUT_MS } = {}) {
62
+ if (this._child) throw new Error("LSPClient already started");
63
+
64
+ // shell:false — never route the server command through a shell (prevents
65
+ // command injection and GBK codepage surprises on Windows). Callers pass a
66
+ // resolved executable path; see lsp-server-registry.js. `.cmd`/`.bat` shims
67
+ // can't be spawned directly with shell:false since Node's CVE-2024-27980
68
+ // fix, so buildSpawnCommand rewrites them to an explicit `cmd.exe /d /s /c`
69
+ // invocation with verbatim args — still no shell, still injection-safe.
70
+ const spawnSpec = buildSpawnCommand(this.command, this.args);
71
+ this._child = _deps.spawn(spawnSpec.file, spawnSpec.args, {
72
+ cwd: this.rootPath,
73
+ env: { ...process.env, ...this.env },
74
+ stdio: ["pipe", "pipe", "pipe"],
75
+ shell: false,
76
+ windowsHide: true,
77
+ windowsVerbatimArguments: spawnSpec.windowsVerbatimArguments,
78
+ });
79
+
80
+ this._child.on("error", (err) => {
81
+ this._failAll(err);
82
+ this.emit("error", err);
83
+ });
84
+ this._child.on("exit", (code, signal) => {
85
+ const wasClosed = this._closed;
86
+ this._closed = true;
87
+ this._failAll(
88
+ new Error(`language server exited (code=${code}, signal=${signal})`),
89
+ );
90
+ if (!wasClosed) this.emit("exit", { code, signal });
91
+ });
92
+
93
+ // UTF-8 decode explicitly (encoding.md) — never rely on system default.
94
+ this._child.stdout.on("data", (chunk) => {
95
+ this._buffer.append(chunk);
96
+ for (const msg of this._buffer.readAll()) this._dispatch(msg);
97
+ });
98
+ // Surface server diagnostics/logs without corrupting the message channel.
99
+ this._child.stderr.on("data", (chunk) => {
100
+ this.emit("stderr", chunk.toString("utf8"));
101
+ });
102
+
103
+ const initResult = await this.request(
104
+ "initialize",
105
+ this._initializeParams(),
106
+ { timeoutMs: initTimeoutMs },
107
+ );
108
+ this.serverCapabilities = initResult?.capabilities || {};
109
+ this.notify("initialized", {});
110
+ this._initialized = true;
111
+ return this.serverCapabilities;
112
+ }
113
+
114
+ _initializeParams() {
115
+ const rootUri = pathToFileUri(this.rootPath);
116
+ return {
117
+ processId: process.pid,
118
+ clientInfo: { name: "chainlesschain-cc", version: "1" },
119
+ rootUri,
120
+ rootPath: this.rootPath,
121
+ workspaceFolders: [{ uri: rootUri, name: "workspace" }],
122
+ capabilities: this.capabilitiesOverride || defaultClientCapabilities(),
123
+ };
124
+ }
125
+
126
+ /** Send a JSON-RPC request and await its result. */
127
+ request(method, params, { timeoutMs } = {}) {
128
+ if (this._closed) {
129
+ return Promise.reject(new Error("LSPClient is closed"));
130
+ }
131
+ const id = this._nextId++;
132
+ const payload = { jsonrpc: "2.0", id, method, params };
133
+ return new Promise((resolve, reject) => {
134
+ const timer = setTimeout(() => {
135
+ this._pending.delete(id);
136
+ reject(
137
+ new Error(
138
+ `LSP request "${method}" timed out after ${timeoutMs || this.requestTimeoutMs}ms`,
139
+ ),
140
+ );
141
+ }, timeoutMs || this.requestTimeoutMs);
142
+ // Don't keep the event loop alive purely for a pending LSP request.
143
+ if (typeof timer.unref === "function") timer.unref();
144
+ this._pending.set(id, { resolve, reject, timer });
145
+ this._write(payload);
146
+ });
147
+ }
148
+
149
+ /** Send a JSON-RPC notification (no response expected). */
150
+ notify(method, params) {
151
+ if (this._closed) return;
152
+ this._write({ jsonrpc: "2.0", method, params });
153
+ }
154
+
155
+ _write(payload) {
156
+ if (!this._child || !this._child.stdin.writable) return;
157
+ try {
158
+ this._child.stdin.write(encodeMessage(payload));
159
+ } catch (err) {
160
+ this.emit("error", err);
161
+ }
162
+ }
163
+
164
+ _dispatch(msg) {
165
+ if (msg == null) return;
166
+ // Response to one of our requests.
167
+ if (Object.prototype.hasOwnProperty.call(msg, "id") && msg.method == null) {
168
+ const entry = this._pending.get(msg.id);
169
+ if (!entry) return; // late/duplicate response — ignore
170
+ this._pending.delete(msg.id);
171
+ clearTimeout(entry.timer);
172
+ if (msg.error) {
173
+ const e = new Error(msg.error.message || "LSP error");
174
+ e.code = msg.error.code;
175
+ e.data = msg.error.data;
176
+ entry.reject(e);
177
+ } else {
178
+ entry.resolve(msg.result);
179
+ }
180
+ return;
181
+ }
182
+ // Server → client request (e.g. workspace/configuration). We don't act on
183
+ // these yet, but must reply so the server doesn't block; reply null.
184
+ if (msg.method != null && Object.prototype.hasOwnProperty.call(msg, "id")) {
185
+ this._write({ jsonrpc: "2.0", id: msg.id, result: null });
186
+ this.emit("serverRequest", msg);
187
+ return;
188
+ }
189
+ // Notification from the server (diagnostics, logs, progress…).
190
+ if (msg.method != null) {
191
+ this.emit("notification", msg.method, msg.params);
192
+ this.emit(`notify:${msg.method}`, msg.params);
193
+ }
194
+ }
195
+
196
+ _failAll(err) {
197
+ for (const [, entry] of this._pending) {
198
+ clearTimeout(entry.timer);
199
+ entry.reject(err);
200
+ }
201
+ this._pending.clear();
202
+ }
203
+
204
+ /** Graceful LSP shutdown → exit → kill fallback. Idempotent. */
205
+ async stop({ graceTimeoutMs = 2000 } = {}) {
206
+ if (!this._child || this._closed) {
207
+ this._closed = true;
208
+ return;
209
+ }
210
+ try {
211
+ if (this._initialized) {
212
+ await this.request("shutdown", null, { timeoutMs: graceTimeoutMs });
213
+ this.notify("exit", null);
214
+ }
215
+ } catch {
216
+ // Server unresponsive — fall through to hard kill.
217
+ } finally {
218
+ this._closed = true;
219
+ this._failAll(new Error("LSPClient stopped"));
220
+ try {
221
+ this._child.kill();
222
+ } catch {
223
+ /* already dead */
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Compute the actual (file, args) to spawn without a shell. On Windows a
231
+ * `.cmd`/`.bat` batch shim cannot be spawned with shell:false (EINVAL since
232
+ * Node's CVE-2024-27980 fix), so route it through `cmd.exe /d /s /c "<cmd>" …`
233
+ * with windowsVerbatimArguments so cmd.exe — not Node — parses the line. The
234
+ * command path is double-quoted (handles spaces) and our args are constant, so
235
+ * there is no injection surface. Everything else spawns verbatim.
236
+ */
237
+ export function buildSpawnCommand(command, args = []) {
238
+ if (process.platform === "win32" && /\.(cmd|bat)$/i.test(command)) {
239
+ const comspec = process.env.ComSpec || "cmd.exe";
240
+ // Always quote the command path (a batch path can contain spaces/&); quote
241
+ // args only when they carry shell-significant characters. Verbatim args mean
242
+ // cmd.exe — not Node — parses this line, so our own quoting is authoritative.
243
+ const quotedCmd = `"${String(command).replace(/"/g, '""')}"`;
244
+ const quotedArgs = args.map((a) =>
245
+ /[\s"&|<>^]/.test(a) ? `"${String(a).replace(/"/g, '""')}"` : a,
246
+ );
247
+ return {
248
+ file: comspec,
249
+ args: ["/d", "/s", "/c", [quotedCmd, ...quotedArgs].join(" ")],
250
+ windowsVerbatimArguments: true,
251
+ };
252
+ }
253
+ return { file: command, args, windowsVerbatimArguments: false };
254
+ }
255
+
256
+ /** LSP wants a `file://` URI; build one that round-trips on Windows and POSIX. */
257
+ export function pathToFileUri(p) {
258
+ if (!p) return "";
259
+ let normalized = String(p).replace(/\\/g, "/");
260
+ // Windows drive path → file:///C:/...
261
+ if (/^[a-zA-Z]:\//.test(normalized)) {
262
+ normalized = "/" + normalized;
263
+ }
264
+ const encoded = normalized
265
+ .split("/")
266
+ .map((seg) => encodeURIComponent(seg))
267
+ .join("/");
268
+ return "file://" + encoded;
269
+ }
270
+
271
+ /**
272
+ * Canonicalize a `file://` URI so keys match regardless of who produced them.
273
+ * Windows language servers echo back a lowercase drive letter
274
+ * (`file:///c%3A/…`) while `path.resolve` yields uppercase (`C%3A`), so a Map
275
+ * keyed on the raw URI misses. Lowercasing the drive letter reconciles both.
276
+ */
277
+ export function normalizeUri(uri) {
278
+ if (!uri) return uri;
279
+ return String(uri).replace(
280
+ /^(file:\/\/\/)([A-Za-z])(%3[Aa]|:)/,
281
+ (_m, p, drive, colon) => p + drive.toLowerCase() + colon,
282
+ );
283
+ }
284
+
285
+ /** Convert a `file://` URI back to a native filesystem path. */
286
+ export function fileUriToPath(uri) {
287
+ if (!uri) return "";
288
+ let p = String(uri).replace(/^file:\/\//, "");
289
+ p = decodeURIComponent(p);
290
+ // /C:/... → C:/... on Windows
291
+ if (/^\/[a-zA-Z]:\//.test(p)) p = p.slice(1);
292
+ return process.platform === "win32" ? p.replace(/\//g, "\\") : p;
293
+ }
294
+
295
+ function defaultClientCapabilities() {
296
+ return {
297
+ textDocument: {
298
+ synchronization: { dynamicRegistration: false, didSave: true },
299
+ definition: { dynamicRegistration: false, linkSupport: false },
300
+ references: { dynamicRegistration: false },
301
+ hover: {
302
+ dynamicRegistration: false,
303
+ contentFormat: ["markdown", "plaintext"],
304
+ },
305
+ documentSymbol: {
306
+ dynamicRegistration: false,
307
+ hierarchicalDocumentSymbolSupport: true,
308
+ },
309
+ publishDiagnostics: { relatedInformation: true },
310
+ rename: { dynamicRegistration: false, prepareSupport: true },
311
+ },
312
+ workspace: {
313
+ workspaceFolders: true,
314
+ symbol: { dynamicRegistration: false },
315
+ configuration: true,
316
+ },
317
+ };
318
+ }
@@ -0,0 +1,323 @@
1
+ /**
2
+ * LSPManager — pools language servers by (projectRoot, languageId), syncs open
3
+ * documents, translates positions, and recovers from server crashes.
4
+ *
5
+ * One `LSPClient` is expensive (spawns a process + indexes the project), so we
6
+ * lazily start and reuse one per (root, language) family. The manager owns:
7
+ * - server lifecycle (start / reuse / bounded crash-loop restart / disposeAll)
8
+ * - textDocument/didOpen + didChange (servers only answer about open docs)
9
+ * - diagnostics collection (servers push them via publishDiagnostics)
10
+ * - 1-based (user) ⇄ 0-based (LSP) position conversion, done HERE only
11
+ *
12
+ * The high-level `code-intelligence.js` calls into this; nothing above the
13
+ * manager should touch raw LSP positions/URIs.
14
+ */
15
+
16
+ import fs from "fs";
17
+ import path from "path";
18
+ import { LSPClient, pathToFileUri, normalizeUri } from "./lsp-client.js";
19
+ import { languageIdForFile, resolveServer } from "./lsp-server-registry.js";
20
+
21
+ export const _deps = {
22
+ readFileSync: fs.readFileSync,
23
+ existsSync: fs.existsSync,
24
+ createClient: (opts) => new LSPClient(opts),
25
+ };
26
+
27
+ export class LSPManager {
28
+ /**
29
+ * @param {object} [opts]
30
+ * @param {string} [opts.projectRoot] default root; per-file root inferred otherwise
31
+ * @param {number} [opts.maxRestarts] crashes within the window before a server
32
+ * is quarantined (degrade to text search) instead of re-spawned. Default 3.
33
+ * @param {number} [opts.restartWindowMs] sliding window for the crash count.
34
+ * A server that stays healthy for this long has its crash history pruned and
35
+ * can restart fresh. Default 30000.
36
+ * @param {() => number} [opts.now] injected clock (deterministic crash-loop tests)
37
+ */
38
+ constructor(opts = {}) {
39
+ this.projectRoot = opts.projectRoot || process.cwd();
40
+ /** key `${root}::${serverId}` → { client, languageId, root, restarts } */
41
+ this._servers = new Map();
42
+ /** open document uri → { version, languageId } */
43
+ this._openDocs = new Map();
44
+ /** uri → latest diagnostics array */
45
+ this._diagnostics = new Map();
46
+ /** uri → { count, at } publish bookkeeping (for settle/readiness) */
47
+ this._diagMeta = new Map();
48
+ // Crash-loop guard: a language server that dies is lazily re-spawned on the
49
+ // next request (ensureFor), but a server that crashes on startup would then
50
+ // thrash — re-spawned on EVERY request forever. Bound it: at most
51
+ // `maxRestarts` crashes inside a sliding `restartWindowMs`, after which the
52
+ // server is quarantined (callers degrade to text search) until the window
53
+ // clears. `restarts` was previously a dead field; this makes it real.
54
+ this.maxRestarts = Number.isFinite(opts.maxRestarts) ? opts.maxRestarts : 3;
55
+ this.restartWindowMs = Number.isFinite(opts.restartWindowMs)
56
+ ? opts.restartWindowMs
57
+ : 30000;
58
+ this._now = typeof opts.now === "function" ? opts.now : () => Date.now();
59
+ /** key → array of recent crash timestamps (survives entry re-spawn) */
60
+ this._crashLog = new Map();
61
+ this._disposed = false;
62
+ }
63
+
64
+ /** Recent crash timestamps for `key`, pruned to the sliding window. */
65
+ _recentCrashes(key) {
66
+ const cutoff = this._now() - this.restartWindowMs;
67
+ const pruned = (this._crashLog.get(key) || []).filter((t) => t >= cutoff);
68
+ if (pruned.length) this._crashLog.set(key, pruned);
69
+ else this._crashLog.delete(key);
70
+ return pruned;
71
+ }
72
+
73
+ /**
74
+ * Ensure a server is running for `filePath` and the file is open. Returns
75
+ * `{ client, uri, languageId }`, or `{ unavailable: true, reason }` when no
76
+ * server is installed (caller degrades to text search).
77
+ */
78
+ async ensureFor(
79
+ filePath,
80
+ { root, waitReady = false, readyTimeoutMs = 8000 } = {},
81
+ ) {
82
+ if (this._disposed) throw new Error("LSPManager disposed");
83
+ const abs = path.resolve(filePath);
84
+ const languageId = languageIdForFile(abs);
85
+ if (!languageId) {
86
+ return {
87
+ unavailable: true,
88
+ reason: `no language mapping for ${path.extname(abs) || "file"}`,
89
+ };
90
+ }
91
+ const projectRoot = root || this._inferRoot(abs);
92
+ const resolved = resolveServer(languageId, projectRoot);
93
+ if (!resolved) {
94
+ return {
95
+ unavailable: true,
96
+ reason: `no language server installed for ${languageId} (install one, e.g. typescript-language-server)`,
97
+ };
98
+ }
99
+
100
+ const key = `${projectRoot}::${resolved.id}`;
101
+ let entry = this._servers.get(key);
102
+ if (!entry || !entry.client.running) {
103
+ // A dead/absent server is (re)started — but if it has crash-looped past
104
+ // the cap inside the window, quarantine it so we don't re-spawn on every
105
+ // request. The caller degrades to text search until the window clears.
106
+ const crashes = this._recentCrashes(key);
107
+ if (crashes.length >= this.maxRestarts) {
108
+ const retryInMs = this.restartWindowMs - (this._now() - crashes[0]);
109
+ return {
110
+ unavailable: true,
111
+ reason:
112
+ `${resolved.id} crash-looped (${crashes.length} crashes in ` +
113
+ `${Math.round(this.restartWindowMs / 1000)}s) — quarantined, ` +
114
+ `retrying in ~${Math.max(0, Math.round(retryInMs / 1000))}s`,
115
+ quarantined: true,
116
+ };
117
+ }
118
+ entry = await this._startServer(key, resolved, projectRoot);
119
+ }
120
+
121
+ const uri = pathToFileUri(abs);
122
+ this._ensureOpen(entry.client, uri, abs, languageId);
123
+
124
+ // One-shot callers (the `cc code-intel` CLI) spawn a cold server per run;
125
+ // the server publishes an empty diagnostics set BEFORE it finishes loading
126
+ // the tsconfig project, so an immediate definition/references/diagnostics
127
+ // query sees a partial project. Waiting for the first publish for THIS doc
128
+ // is a reliable "project loaded far enough" signal. Long-lived callers (the
129
+ // agent session) keep the server warm and pass waitReady:false.
130
+ if (waitReady) {
131
+ await this._waitForFirstPublish(uri, readyTimeoutMs);
132
+ }
133
+ return { client: entry.client, uri, languageId, root: projectRoot };
134
+ }
135
+
136
+ /** Resolve once the server has published diagnostics for `uri` at least once, or timeout. */
137
+ _waitForFirstPublish(rawUri, timeoutMs) {
138
+ const uri = normalizeUri(rawUri);
139
+ if ((this._diagMeta.get(uri)?.count || 0) > 0) return Promise.resolve();
140
+ return new Promise((resolve) => {
141
+ const started = Date.now();
142
+ const timer = setInterval(() => {
143
+ if (
144
+ (this._diagMeta.get(uri)?.count || 0) > 0 ||
145
+ Date.now() - started >= timeoutMs
146
+ ) {
147
+ clearInterval(timer);
148
+ resolve();
149
+ }
150
+ }, 40);
151
+ if (typeof timer.unref === "function") timer.unref();
152
+ });
153
+ }
154
+
155
+ async _startServer(key, resolved, projectRoot) {
156
+ const client = _deps.createClient({
157
+ command: resolved.command,
158
+ args: resolved.args,
159
+ rootPath: projectRoot,
160
+ });
161
+ // Collect pushed diagnostics keyed by document, tracking publish count/time
162
+ // so callers can wait for the project to load (first publish) and settle
163
+ // (last publish) rather than racing the initial empty set tsserver emits.
164
+ client.on("notify:textDocument/publishDiagnostics", (params) => {
165
+ if (params && params.uri) {
166
+ const key = normalizeUri(params.uri);
167
+ this._diagnostics.set(key, params.diagnostics || []);
168
+ const prev = this._diagMeta.get(key) || { count: 0 };
169
+ this._diagMeta.set(key, { count: prev.count + 1, at: Date.now() });
170
+ }
171
+ });
172
+ const entry = {
173
+ client,
174
+ serverId: resolved.id,
175
+ root: projectRoot,
176
+ // How many times THIS key has already been (re)started — crash history
177
+ // survives entry replacement via _crashLog, so this reflects it.
178
+ restarts: (this._crashLog.get(key) || []).length,
179
+ };
180
+ // On crash: record the crash timestamp (bounds the re-spawn loop via the
181
+ // crash-loop guard in ensureFor) and drop this project's open docs so they
182
+ // re-sync against the fresh server on the next ensureFor.
183
+ client.on("exit", () => {
184
+ const log = this._crashLog.get(key) || [];
185
+ log.push(this._now());
186
+ this._crashLog.set(key, log);
187
+ for (const [uri, meta] of this._openDocs) {
188
+ if (meta.root === projectRoot) this._openDocs.delete(uri);
189
+ }
190
+ });
191
+ await client.start();
192
+ this._servers.set(key, entry);
193
+ return entry;
194
+ }
195
+
196
+ _ensureOpen(client, uri, abs, languageId) {
197
+ const existing = this._openDocs.get(uri);
198
+ let text = "";
199
+ try {
200
+ text = _deps.readFileSync(abs, "utf8");
201
+ } catch {
202
+ text = "";
203
+ }
204
+ if (!existing) {
205
+ client.notify("textDocument/didOpen", {
206
+ textDocument: { uri, languageId, version: 1, text },
207
+ });
208
+ this._openDocs.set(uri, {
209
+ version: 1,
210
+ languageId,
211
+ root: client.rootPath,
212
+ });
213
+ } else {
214
+ // File may have changed on disk since first open — push the latest text so
215
+ // definitions/diagnostics reflect current state (edit-freshness).
216
+ const version = existing.version + 1;
217
+ client.notify("textDocument/didChange", {
218
+ textDocument: { uri, version },
219
+ contentChanges: [{ text }],
220
+ });
221
+ this._openDocs.set(uri, { ...existing, version });
222
+ }
223
+ }
224
+
225
+ /** Notify the server that a document changed on disk (after an edit). */
226
+ async didChangeFile(filePath, { root } = {}) {
227
+ const abs = path.resolve(filePath);
228
+ const uri = pathToFileUri(abs);
229
+ if (!this._openDocs.has(uri)) {
230
+ // Not open yet — opening it (via ensureFor) will read fresh text anyway.
231
+ return this.ensureFor(abs, { root });
232
+ }
233
+ const ready = await this.ensureFor(abs, { root });
234
+ return ready;
235
+ }
236
+
237
+ /** Latest diagnostics for a file (may be empty). Server pushes asynchronously. */
238
+ getDiagnostics(filePath) {
239
+ const uri = normalizeUri(pathToFileUri(path.resolve(filePath)));
240
+ return this._diagnostics.get(uri) || [];
241
+ }
242
+
243
+ /**
244
+ * Wait for diagnostics for `filePath` to publish AND settle, then return the
245
+ * latest set. tsserver publishes an initial (often empty) set the moment a doc
246
+ * opens, then republishes once the project is loaded — resolving on the first
247
+ * publish would return stale/empty results. So we wait for at least one
248
+ * publish, then a `settleMs` quiet window with no further publishes (bounded
249
+ * by `timeoutMs`), and return whatever was published last.
250
+ */
251
+ async waitForDiagnostics(
252
+ filePath,
253
+ { timeoutMs = 3000, settleMs = 600 } = {},
254
+ ) {
255
+ const uri = normalizeUri(pathToFileUri(path.resolve(filePath)));
256
+ return new Promise((resolve) => {
257
+ const started = Date.now();
258
+ const timer = setInterval(() => {
259
+ const meta = this._diagMeta.get(uri);
260
+ const elapsed = Date.now() - started;
261
+ const settled =
262
+ meta && meta.count > 0 && Date.now() - meta.at >= settleMs;
263
+ if (settled || elapsed >= timeoutMs) {
264
+ clearInterval(timer);
265
+ resolve(this._diagnostics.get(uri) || []);
266
+ }
267
+ }, 50);
268
+ if (typeof timer.unref === "function") timer.unref();
269
+ });
270
+ }
271
+
272
+ /** Infer the project root for a file: nearest ancestor with package.json / .git / go.mod / Cargo.toml. */
273
+ _inferRoot(abs) {
274
+ let dir = path.dirname(abs);
275
+ const markers = [
276
+ "package.json",
277
+ ".git",
278
+ "go.mod",
279
+ "Cargo.toml",
280
+ "tsconfig.json",
281
+ "pyproject.toml",
282
+ ];
283
+ for (let i = 0; i < 12; i++) {
284
+ for (const m of markers) {
285
+ if (_deps.existsSync(path.join(dir, m))) return dir;
286
+ }
287
+ const parent = path.dirname(dir);
288
+ if (parent === dir) break;
289
+ dir = parent;
290
+ }
291
+ return this.projectRoot;
292
+ }
293
+
294
+ /** Shut down every server and clear state. Idempotent. */
295
+ async disposeAll() {
296
+ this._disposed = true;
297
+ const clients = [...this._servers.values()].map((e) => e.client);
298
+ this._servers.clear();
299
+ this._openDocs.clear();
300
+ this._diagnostics.clear();
301
+ this._diagMeta.clear();
302
+ await Promise.all(clients.map((c) => c.stop().catch(() => {})));
303
+ }
304
+
305
+ get activeServerCount() {
306
+ return this._servers.size;
307
+ }
308
+ }
309
+
310
+ // ---- position helpers (the ONLY place that converts between coordinate spaces) ----
311
+
312
+ /** 1-based user position → 0-based LSP position. */
313
+ export function toLspPosition({ line, col }) {
314
+ return {
315
+ line: Math.max(0, (line || 1) - 1),
316
+ character: Math.max(0, (col || 1) - 1),
317
+ };
318
+ }
319
+
320
+ /** 0-based LSP position → 1-based user position. */
321
+ export function fromLspPosition(pos) {
322
+ return { line: (pos?.line || 0) + 1, col: (pos?.character || 0) + 1 };
323
+ }