chainlesschain 0.162.147 → 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 (257) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DywJ7xTv.js → AIOps-BJif14yR.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-BTlqcY-q.js → ActionButton-CXek6gJY.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-FdG1Mvwy.js → Analytics-BgQhdAJi.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CVsnbvN1.js → AppLayout-DTYl5NtR.js} +5 -5
  8. package/src/assets/web-panel/assets/{Audit-ITdpJ7vR.js → Audit-BzATQAeR.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BfXqnXo1.js → Backup-BzdPEQhL.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-C09ip3_E.js → BaseInput-BM0KVh8E.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-qdkhB42l.js → Chat-D7fHXHkz.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-D2KfETZD.js → ChatBubbleRenderer-Wm34RR-b.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-j6WZCuff.js → Checkbox-CAiSQ9vO.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-CGZ-K3uK.js → Codegen-Df40CrEe.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CIlPOAu6.js → Col-DIT2fNFU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-sY-i-hic.js → Community-CSTIbW2k.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-DcoEfyL8.js → Compact-DtqXZZUi.js} +1 -1
  18. package/src/assets/web-panel/assets/Compliance-CUtzw6o7.js +1 -0
  19. package/src/assets/web-panel/assets/{Cowork-DLY_fkLv.js → Cowork-CezmlnmU.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-D049pZBC.js → Cron-P4BITarg.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DCXdE8M9.js → Crosschain-BRRiYwvq.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-JCvPZtjW.js → DID-L5ijB9i4.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Cg2xu3iE.js → Dashboard-CFxro4ob.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DjJLjbLh.js → Dropdown-BmAFCQ71.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DugNcSLR.js → EmailListRenderer-DJBCEuon.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-J6Nb-E30.js → FamilyGuardDashboard-z4oLq6eT.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CQ3Ttpbl.js → Federation-DsVCpHMF.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Cz0NKag4.js → FormItemContext-DP3bP5th.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-NYcMP6iz.js → GenericCardRenderer-BAXP2Gc6.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-B4hYN18N.js → Git-I3g_bAw9.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DOQPCcC0.js → Governance-jHS5-ioS.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-HtLF746W.js → Inference-CPL7sfx9.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BXrk8rMm.js → KnowledgeGraph-Jf236h4C.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DS0JnD3-.js → Logs-Cm2tcSKg.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-Cqnjihrz.js → Marketplace-CZK82rhi.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-Cx3Psk-U.js → McpTools-CZCeji7a.js} +5 -5
  37. package/src/assets/web-panel/assets/{Memory-BjyysPtj.js → Memory-3l2KVS9k.js} +2 -2
  38. package/src/assets/web-panel/assets/MobileBridge-D3Arxfeg.css +1 -0
  39. package/src/assets/web-panel/assets/MobileBridge-VYdpoLh6.js +1 -0
  40. package/src/assets/web-panel/assets/{MobileProjects-Xwf-fdOQ.js → MobileProjects-CiB9cmm1.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-Djkql5m5.js → Mtc-wCFZbBuL.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-D2BAZlu0.js → MtcAudit-1a3THIyG.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-ByqP1ZzH.js → Multisig-DMzLB2hG.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-C_kn0nE2.js → NLProgramming-D9wZbf6l.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-7l7eXHPq.js → Notes-hrFe2ZM-.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BrCDoitw.js → NotificationSettings-CHGX5Jwm.js} +1 -1
  47. package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +1 -0
  48. package/src/assets/web-panel/assets/{Organization-DdoNAxsM.js → Organization-By7FkhtY.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Bw7R6dE3.js → Overflow--khGSzJn.js} +1 -1
  50. package/src/assets/web-panel/assets/{OverrideContext-C2r4vyBb.js → OverrideContext-BlgL9440.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-DJNtBugA.js → P2P-CAG9dH35.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-Dwhd5_ue.js → PdhVaultBrowser-B3SQZmK9.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-BYoSfhPR.js → Permissions-D35ec6JA.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-aYCYJbnH.js → PersonalDataHub-CxBF7hW_.js} +2 -2
  55. package/src/assets/web-panel/assets/{Pipeline-DJp6CprF.js → Pipeline-s6EtOO1s.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-Cq9HZweR.js → Privacy-BbJYmWmO.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-CmF0HsSt.js → ProjectInit-VGQq1jMT.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-P8V5Aea2.js → ProjectSettings-B1ew3Teh.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-D589v0q2.js → Providers-mrO47FIK.js} +1 -1
  61. package/src/assets/web-panel/assets/QrScannerModal-IJF2mHyw.js +1 -0
  62. package/src/assets/web-panel/assets/{MobileBridge-zJpPuf4E.css → QrScannerModal-NwAHNfiJ.css} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-lvRV2osB.js → QuickAsk-CU0wN_Z0.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-sb84MTpY.js → Recommend-DhN37R6G.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-D_xOX_hu.js +4 -0
  66. package/src/assets/web-panel/assets/{Reputation-d9Y3vy-W.js → Reputation-D_Ssv7uH.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-DMdhLUTp.js → Row-Kx5dKxX7.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-OL-SMDkz.js → RssFeed-BlGwqZkY.js} +2 -2
  69. package/src/assets/web-panel/assets/{Search-BjStLKq1.js → Search-hLFQzxpb.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-MeI411qr.js → Security-DMlvsVt4.js} +2 -2
  71. package/src/assets/web-panel/assets/{Services-UajosuhT.js → Services-Bd0Qsj2m.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-CGpG-QJ1.js → Skeleton-DFclq9X3.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-C6P2RPY-.js → Skills-BNtln2qY.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-B_fPprgw.js → Sla-Dch5H19G.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-CqxLjSlQ.js → SpeechSettings-2UfQcU1g.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-DixSfFOQ.js → SyncSettings-CD9YQr4i.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-PavzPBxc.js → Tasks-BNu-7MzI.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-BYdGRnfX.js → Templates-D1n9CaIR.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BQLgnarD.js → Tenant-DenL3iBW.js} +1 -1
  80. package/src/assets/web-panel/assets/Terminal-ZzU0Io1Y.js +3 -0
  81. package/src/assets/web-panel/assets/{TimelineRenderer-Bv7uZRM7.js → TimelineRenderer-D87IfukO.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-DxhgszRJ.js → Tokens-BkiZc8E3.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-nQYHayuc.js → Trigger-BNRSytGN.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-CvqZDuZ4.js → Trust-Bwbd4X2m.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DaR8GV2I.js → UkeySign-B8O2bs0o.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-C6Mpsokw.js → VideoEditing-C2fL8zmH.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-B57VRrWc.js → Wallet-BORYDdE1.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-BbtEEtpq.js → WebAuthn-C0V5S2FM.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-C5LwvKmH.js → WorkflowEditor-2dR0fcHL.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-MzuPR5jh.js → chat-BmuAFAn8.js} +1 -1
  91. package/src/assets/web-panel/assets/{collapseMotion-mxzzcDsg.js → collapseMotion-DVfhbsdP.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-HyW8mi_y.js → colors-CN3smMcK.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-DvUSzWAp.js → compact-item-DCnxb4kW.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-4MppZl7X.js → createContext-vlR43pHn.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +1 -0
  96. package/src/assets/web-panel/assets/{echarts-jxgIOFUZ.js → echarts-CL0F2SnX.js} +1 -1
  97. package/src/assets/web-panel/assets/{hasIn-D9q3CJ-u.js → hasIn-D04tHYFd.js} +1 -1
  98. package/src/assets/web-panel/assets/{icons-DP3uiYxy.js → icons-BsDmGpcT.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-C9I5MuJ0.js → index-0DnaZGkC.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Caqtu6Et.js → index-430S68MN.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-eOQO6KnV.js → index-B78xL3s2.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Ci6adKh8.js → index-BTPEZTk1.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CifKUNtV.js → index-BXRg8GFQ.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D7TDMMfu.js → index-BasvsWDM.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DNYqw0MO.js → index-BeGDEXFs.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-m0b6Fj9q.js → index-BqhASEL4.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BF2JsbiX.js → index-BudvKQfG.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DFqvWeGc.js → index-C-6NO5il.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-SDblbKj6.js → index-C5Sxb1sE.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-UUMCA2Sq.js → index-CP9m9Ggr.js} +4 -4
  111. package/src/assets/web-panel/assets/{index--Bm6OqmU.js → index-CTOCnIQ7.js} +1 -1
  112. package/src/assets/web-panel/assets/index-Cg2ldRfU.js +1 -0
  113. package/src/assets/web-panel/assets/{index-Dv7ffI2g.js → index-ClhAHDK3.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BcunsBIx.js → index-CnXebZLL.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CvAqCAo9.js → index-CngCC8hC.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DcMkjb92.js → index-Cr-A0FYJ.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BFGfC5RS.js → index-CsAsCPJ6.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DDZ6fP5Z.js → index-D28DeAAB.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CgIfXOD9.js → index-D5LZVZ0L.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DmZ6-suL.js → index-D65_XseV.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Cs-RKRUM.js → index-D8vwp4qp.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B994UQfE.js → index-DDwLgQY9.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Btz8pU68.js → index-DIpY0qM5.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B5E4DhVc.js → index-DTWg-18U.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-D2jrnaq3.js → index-DTtRscUa.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BmCo2Xdp.js → index-DYAtmqiv.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Zvtru2oE.js → index-D_n8_vfI.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CLAZs1Vd.js → index-DbADHwhr.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CucRo4Vz.js → index-DbVsQBOH.js} +1 -1
  130. package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +1 -0
  131. package/src/assets/web-panel/assets/{index-C0DiL97c.js → index-HhNjnCfe.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Bl-E4W4q.js → index-ToUh2b1-.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-ryFLxB1p.js → index-TslMTwNy.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BGQtFso0.js → index-_FIkN3jd.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-zLwYpvX0.js → index-hpvvtAZ3.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-Dvg5xYuP.js → index-jzR7Ujuw.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-xvUVuFnA.js → index-ljuBiQW9.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-CC--PMsC.js → initDefaultProps-C3wUb4je.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-DdrBjRF1.js → motion-D-jYFUNA.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-D712Gwl2.js → move-tqb8nwJ2.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-39y-keKO.js → mtc-parser-DSOjMJMz.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-6Y51gDB9.js → omit-CJ7VimIW.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-Cy6EHbq9.js → pickAttrs-qXiG1iT3.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-BYxdAm0p.js → placementArrow-CkGBcy1Z.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-C9R6q_cr.js → responsiveObserve-D3WvTlLO.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-CXUJlilB.js → slide-BEE2ftge.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-DOtrOeql.js → statusUtils-CsfBpxiG.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-D5r39o92.js → styleChecker-xPrIiJPI.js} +1 -1
  149. package/src/assets/web-panel/assets/useFlexGapSupport-C5Yd-SCR.js +1 -0
  150. package/src/assets/web-panel/assets/{useFs-BkrT-Zbc.js → useFs-D_MDS8HI.js} +1 -1
  151. package/src/assets/web-panel/assets/{useMergedState-Cl2q1fes.js → useMergedState-Dc9sO2d7.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-BwnnnZjU.js → usePersonalDataHub-BqJh-usA.js} +1 -1
  153. package/src/assets/web-panel/assets/{useRefs-DD9q4mOT.js → useRefs-CAeFRzPf.js} +1 -1
  154. package/src/assets/web-panel/assets/{useState-BK4hy8Ma.js → useState-LwExUYDO.js} +1 -1
  155. package/src/assets/web-panel/assets/{vendor-BvqAck49.js → vendor-BMxUs6UX.js} +16 -16
  156. package/src/assets/web-panel/assets/{vnode-SU-N4pum.js → vnode-chm_p-gz.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-DHScfpTP.js → zoom-3-YDNz0Z.js} +1 -1
  158. package/src/assets/web-panel/index.html +3 -3
  159. package/src/assets/web-panel/remote-session-sw.js +43 -0
  160. package/src/command-manifest.json +22 -1
  161. package/src/commands/agent.js +44 -5
  162. package/src/commands/codeintel.js +252 -0
  163. package/src/commands/eval.js +248 -0
  164. package/src/commands/plugin.js +354 -0
  165. package/src/commands/serve.js +10 -0
  166. package/src/commands/team.js +456 -0
  167. package/src/gateways/remote-session-relay.js +160 -0
  168. package/src/gateways/ws/message-dispatcher.js +77 -2
  169. package/src/gateways/ws/remote-session-protocol.js +466 -0
  170. package/src/gateways/ws/ws-server.js +299 -0
  171. package/src/harness/golden-transcript.js +65 -0
  172. package/src/harness/mcp-client.js +6 -2
  173. package/src/harness/prompt-compressor.js +27 -1
  174. package/src/harness/remote-command-ledger.js +158 -0
  175. package/src/harness/remote-session-audit-sink.js +132 -0
  176. package/src/harness/remote-session-audit.js +110 -0
  177. package/src/harness/remote-session-crypto.js +217 -0
  178. package/src/harness/remote-session-push-apns.js +246 -0
  179. package/src/harness/remote-session-push-errors.js +15 -0
  180. package/src/harness/remote-session-push-fcm.js +238 -0
  181. package/src/harness/remote-session-push-huawei.js +174 -0
  182. package/src/harness/remote-session-push-oppo.js +171 -0
  183. package/src/harness/remote-session-push-senders.js +42 -0
  184. package/src/harness/remote-session-push-vivo.js +179 -0
  185. package/src/harness/remote-session-push-web.js +299 -0
  186. package/src/harness/remote-session-push-xiaomi.js +99 -0
  187. package/src/harness/remote-session-push.js +104 -0
  188. package/src/harness/remote-session-registry.js +420 -0
  189. package/src/index.js +6 -0
  190. package/src/lib/agent-core.js +2 -0
  191. package/src/lib/agent-sandbox.js +161 -5
  192. package/src/lib/agent-team/task-lease.js +434 -0
  193. package/src/lib/agent-team/team-budget.js +165 -0
  194. package/src/lib/agent-team/team-mailbox.js +106 -0
  195. package/src/lib/agent-team/team-runner.js +266 -0
  196. package/src/lib/agent-team/team-worktree.js +204 -0
  197. package/src/lib/agents.js +18 -0
  198. package/src/lib/async-hook-supervisor.cjs +305 -0
  199. package/src/lib/did-manager.js +6 -4
  200. package/src/lib/eval/runner.js +163 -0
  201. package/src/lib/eval/tasks.js +456 -0
  202. package/src/lib/eval/trend.js +185 -0
  203. package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
  204. package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
  205. package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
  206. package/src/lib/lsp/__tests__/lsp-client.test.js +258 -0
  207. package/src/lib/lsp/__tests__/lsp-manager.test.js +264 -0
  208. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
  209. package/src/lib/lsp/code-intelligence.js +356 -0
  210. package/src/lib/lsp/jsonrpc-stream.js +139 -0
  211. package/src/lib/lsp/lsp-client.js +318 -0
  212. package/src/lib/lsp/lsp-manager.js +323 -0
  213. package/src/lib/lsp/lsp-server-registry.js +196 -0
  214. package/src/lib/mcp-oauth.js +23 -6
  215. package/src/lib/plugin-monitor-supervisor.js +238 -0
  216. package/src/lib/plugin-runtime/agents.js +51 -0
  217. package/src/lib/plugin-runtime/bin.js +100 -0
  218. package/src/lib/plugin-runtime/hooks.js +100 -0
  219. package/src/lib/plugin-runtime/install.js +388 -0
  220. package/src/lib/plugin-runtime/lsp.js +75 -0
  221. package/src/lib/plugin-runtime/manifest.js +458 -0
  222. package/src/lib/plugin-runtime/mcp.js +89 -0
  223. package/src/lib/plugin-runtime/monitors.js +100 -0
  224. package/src/lib/plugin-runtime/policy.js +144 -0
  225. package/src/lib/plugin-runtime/reload.js +79 -0
  226. package/src/lib/plugin-runtime/scopes.js +197 -0
  227. package/src/lib/plugin-runtime/settings.js +119 -0
  228. package/src/lib/plugin-runtime/signature.js +107 -0
  229. package/src/lib/plugin-runtime/skills.js +43 -0
  230. package/src/lib/plugin-runtime/trust.js +140 -0
  231. package/src/lib/sandbox-egress-proxy.js +162 -0
  232. package/src/lib/sandbox-network-policy.js +134 -0
  233. package/src/lib/sandbox-v2.js +10 -4
  234. package/src/lib/session-manager.js +7 -3
  235. package/src/lib/settings-hook-events.cjs +78 -6
  236. package/src/lib/settings-hooks.cjs +29 -3
  237. package/src/lib/settings-loader.cjs +35 -1
  238. package/src/lib/skill-loader.js +18 -0
  239. package/src/lib/telemetry/span-recorder.js +237 -0
  240. package/src/repl/agent-repl.js +400 -4
  241. package/src/runtime/agent-core.js +546 -52
  242. package/src/runtime/agent-runtime.js +8 -13
  243. package/src/runtime/coding-agent-contract-shared.cjs +82 -8
  244. package/src/runtime/coding-agent-policy.cjs +32 -7
  245. package/src/runtime/fallback-model.js +134 -7
  246. package/src/runtime/headless-runner.js +353 -108
  247. package/src/runtime/mcp-config.js +46 -0
  248. package/src/runtime/policies/agent-policy.js +2 -0
  249. package/src/assets/web-panel/assets/Compliance-T8EtTecW.js +0 -1
  250. package/src/assets/web-panel/assets/MobileBridge-CYaITZ7w.js +0 -1
  251. package/src/assets/web-panel/assets/OrderTableRenderer-CmtqIdwr.js +0 -1
  252. package/src/assets/web-panel/assets/Projects-DPd5-lNS.js +0 -1
  253. package/src/assets/web-panel/assets/Terminal-BzOHpq-B.js +0 -3
  254. package/src/assets/web-panel/assets/devWarning-6YJJMjSH.js +0 -1
  255. package/src/assets/web-panel/assets/index-D_oeGLr6.js +0 -1
  256. package/src/assets/web-panel/assets/index-Qa57YG19.js +0 -1
  257. package/src/assets/web-panel/assets/useFlexGapSupport-DfAD5U4Z.js +0 -1
@@ -0,0 +1,196 @@
1
+ /**
2
+ * LSP server registry — maps a language id to the language-server command that
3
+ * serves it, and probes whether that command is actually installed.
4
+ *
5
+ * Design constraint (see optimization plan): the CLI must NOT depend on any
6
+ * language server as an npm dependency (they are heavy and language-specific).
7
+ * Instead we DETECT servers on PATH / `node_modules/.bin`, and when none is
8
+ * available the caller degrades gracefully to text search. Plugins may register
9
+ * extra servers via `.lsp.json`.
10
+ *
11
+ * Pure + injectable: filesystem/exec probing goes through `_deps` so the
12
+ * detection logic is unit-testable without a real toolchain.
13
+ */
14
+
15
+ import fs from "fs";
16
+ import path from "path";
17
+ import { execFileSync } from "child_process";
18
+
19
+ export const _deps = {
20
+ existsSync: fs.existsSync,
21
+ execFileSync,
22
+ };
23
+
24
+ // Extension → LSP languageId. Kept small and explicit; unknown extensions get
25
+ // no LSP (text-search fallback).
26
+ const EXTENSION_LANGUAGE = {
27
+ ".ts": "typescript",
28
+ ".tsx": "typescriptreact",
29
+ ".mts": "typescript",
30
+ ".cts": "typescript",
31
+ ".js": "javascript",
32
+ ".jsx": "javascriptreact",
33
+ ".mjs": "javascript",
34
+ ".cjs": "javascript",
35
+ ".py": "python",
36
+ ".pyi": "python",
37
+ ".go": "go",
38
+ ".rs": "rust",
39
+ ".java": "java",
40
+ ".vue": "vue",
41
+ };
42
+
43
+ // languageId → server definition. `bin` names are tried in order; the first one
44
+ // resolvable wins. `args` is passed verbatim. Several languageIds can share one
45
+ // server family (TS server handles JS too).
46
+ const BUILTIN_SERVERS = {
47
+ typescript: tsServer(),
48
+ typescriptreact: tsServer(),
49
+ javascript: tsServer(),
50
+ javascriptreact: tsServer(),
51
+ python: {
52
+ id: "pyright",
53
+ bins: ["pyright-langserver", "pylsp"],
54
+ // pyright wants --stdio; pylsp defaults to stdio with no args. Resolved per-bin.
55
+ argsFor: (bin) => (bin.startsWith("pyright") ? ["--stdio"] : []),
56
+ },
57
+ go: { id: "gopls", bins: ["gopls"], argsFor: () => [] },
58
+ rust: { id: "rust-analyzer", bins: ["rust-analyzer"], argsFor: () => [] },
59
+ java: { id: "jdtls", bins: ["jdtls"], argsFor: () => [] },
60
+ };
61
+
62
+ function tsServer() {
63
+ return {
64
+ id: "typescript-language-server",
65
+ bins: ["typescript-language-server"],
66
+ argsFor: () => ["--stdio"],
67
+ };
68
+ }
69
+
70
+ // Registry of plugin-contributed servers, keyed by languageId. Wins over
71
+ // builtins if a plugin explicitly overrides.
72
+ const pluginServers = new Map();
73
+
74
+ /**
75
+ * Register an extra language server (from a plugin `.lsp.json`).
76
+ * @param {object} def { languageId, extensions?, command, args?, id? }
77
+ */
78
+ export function registerLanguageServer(def) {
79
+ if (!def || !def.languageId || !def.command) {
80
+ throw new Error("registerLanguageServer requires { languageId, command }");
81
+ }
82
+ pluginServers.set(def.languageId, {
83
+ id: def.id || def.command,
84
+ bins: [def.command],
85
+ argsFor: () => (Array.isArray(def.args) ? def.args : []),
86
+ fromPlugin: true,
87
+ });
88
+ if (Array.isArray(def.extensions)) {
89
+ for (const ext of def.extensions) {
90
+ EXTENSION_LANGUAGE[ext.startsWith(".") ? ext : "." + ext] =
91
+ def.languageId;
92
+ }
93
+ }
94
+ }
95
+
96
+ /** Test/reset hook — clears plugin registrations. */
97
+ export function _resetPluginServers() {
98
+ pluginServers.clear();
99
+ }
100
+
101
+ /** Map a file path to its LSP languageId, or null if unsupported. */
102
+ export function languageIdForFile(filePath) {
103
+ const ext = path.extname(String(filePath || "")).toLowerCase();
104
+ return EXTENSION_LANGUAGE[ext] || null;
105
+ }
106
+
107
+ /**
108
+ * Resolve a runnable server command for a languageId within a project root, or
109
+ * null if none is installed. Checks (in order): project `node_modules/.bin`,
110
+ * then PATH (via `resolveBin`).
111
+ *
112
+ * @returns {{ languageId, id, command, args } | null}
113
+ */
114
+ export function resolveServer(languageId, projectRoot) {
115
+ const def = pluginServers.get(languageId) || BUILTIN_SERVERS[languageId];
116
+ if (!def) return null;
117
+ for (const bin of def.bins) {
118
+ const command = resolveBin(bin, projectRoot);
119
+ if (command) {
120
+ return {
121
+ languageId,
122
+ id: def.id,
123
+ command,
124
+ args: def.argsFor(bin),
125
+ };
126
+ }
127
+ }
128
+ return null;
129
+ }
130
+
131
+ /**
132
+ * Find an executable by name: project-local `node_modules/.bin` first (so a
133
+ * repo-pinned server wins), then the system PATH. Returns an absolute path, or
134
+ * null. On Windows the `.cmd`/`.exe` variants are probed too.
135
+ */
136
+ export function resolveBin(bin, projectRoot) {
137
+ const isWin = process.platform === "win32";
138
+ const candidates = isWin ? [bin + ".cmd", bin + ".exe", bin] : [bin];
139
+
140
+ // 1) project node_modules/.bin (walk up a couple levels for monorepos)
141
+ if (projectRoot) {
142
+ let dir = projectRoot;
143
+ for (let i = 0; i < 4; i++) {
144
+ const binDir = path.join(dir, "node_modules", ".bin");
145
+ for (const c of candidates) {
146
+ const full = path.join(binDir, c);
147
+ if (_deps.existsSync(full)) return full;
148
+ }
149
+ const parent = path.dirname(dir);
150
+ if (parent === dir) break;
151
+ dir = parent;
152
+ }
153
+ }
154
+
155
+ // 2) PATH
156
+ const pathEnv = process.env.PATH || process.env.Path || "";
157
+ const sep = isWin ? ";" : ":";
158
+ for (const entry of pathEnv.split(sep)) {
159
+ if (!entry) continue;
160
+ for (const c of candidates) {
161
+ const full = path.join(entry, c);
162
+ if (_deps.existsSync(full)) return full;
163
+ }
164
+ }
165
+ return null;
166
+ }
167
+
168
+ /**
169
+ * Report which builtin servers are available on this machine — used by
170
+ * `cc doctor` and `cc code-intel status` so the user sees why LSP is or isn't
171
+ * active without guessing.
172
+ * @returns {Array<{ languageId, id, available, command }>}
173
+ */
174
+ export function probeServers(projectRoot) {
175
+ const seen = new Set();
176
+ const out = [];
177
+ const all = { ...BUILTIN_SERVERS };
178
+ for (const [lid, def] of pluginServers) all[lid] = def;
179
+ for (const [languageId, def] of Object.entries(all)) {
180
+ if (seen.has(def.id)) continue; // one row per server family
181
+ seen.add(def.id);
182
+ const resolved = resolveServer(languageId, projectRoot);
183
+ out.push({
184
+ languageId,
185
+ id: def.id,
186
+ available: Boolean(resolved),
187
+ command: resolved ? resolved.command : null,
188
+ });
189
+ }
190
+ return out;
191
+ }
192
+
193
+ /** All extensions the registry can currently map (for docs/diagnostics). */
194
+ export function supportedExtensions() {
195
+ return Object.keys(EXTENSION_LANGUAGE).sort();
196
+ }
@@ -35,6 +35,7 @@ export const _deps = {
35
35
  sha256: (s) => crypto.createHash("sha256").update(s).digest(),
36
36
  createServer: (h) => http.createServer(h),
37
37
  openBrowser: defaultOpenBrowser,
38
+ spawn: (...a) => spawn(...a),
38
39
  now: () => Date.now(),
39
40
  // Backoff sleep seam (tests override with a no-op so the retry doesn't wait).
40
41
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
@@ -648,13 +649,29 @@ export async function ensureValidToken(
648
649
 
649
650
  // ── interactive orchestrator ──────────────────────────────────────────────
650
651
 
651
- function defaultOpenBrowser(url) {
652
- const platform = process.platform;
653
- const cmd =
654
- platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
655
- const args = platform === "win32" ? ["/c", "start", "", url] : [url];
652
+ export function defaultOpenBrowser(url, platform = process.platform) {
653
+ // The authorize URL derives from REMOTE OAuth server metadata, so treat it
654
+ // as untrusted: only http(s) may reach the OS opener (no file:/ms-settings:/
655
+ // custom-scheme handlers), and it must never pass through cmd.exe's line
656
+ // parser `cmd /c start "" <url>` truncates at the first unquoted `&` and
657
+ // EXECUTES the remainder as commands (query strings always contain `&`).
658
+ let parsed;
656
659
  try {
657
- const child = spawn(cmd, args, { stdio: "ignore", detached: true });
660
+ parsed = new URL(url);
661
+ } catch {
662
+ return false;
663
+ }
664
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
665
+ // win32: rundll32 is a plain executable — the URL arrives as a verbatim
666
+ // argv entry with no shell metacharacter interpretation.
667
+ const [cmd, args] =
668
+ platform === "win32"
669
+ ? ["rundll32", ["url.dll,FileProtocolHandler", parsed.href]]
670
+ : platform === "darwin"
671
+ ? ["open", [parsed.href]]
672
+ : ["xdg-open", [parsed.href]];
673
+ try {
674
+ const child = _deps.spawn(cmd, args, { stdio: "ignore", detached: true });
658
675
  child.unref?.();
659
676
  return true;
660
677
  } catch {
@@ -0,0 +1,238 @@
1
+ /**
2
+ * PluginMonitorSupervisor — runs installed plugins' background monitors
3
+ * (Phase 3.3i / Phase 6 down payment). Two modes:
4
+ *
5
+ * - longRunning: spawn the command ONCE and keep it alive (e.g. `tail -f`).
6
+ * - interval: re-run the command every `intervalMs`, capturing each run's
7
+ * output (e.g. a periodic health/log check).
8
+ *
9
+ * Captured stdout/stderr lines are buffered as structured records; the agent
10
+ * loop can `drainOutputs()` them and inject them into the next turn's context
11
+ * (Phase 6: "后台结果可在下一轮注入 additionalContext"). The supervisor owns
12
+ * its children and reaps ALL of them on `stopAll()` plus a `process.once('exit')`
13
+ * backstop, so no monitor process leaks past session end.
14
+ *
15
+ * Guardrails (Phase 6 "去重、并发上限、超时、进程回收"): a per-monitor id
16
+ * dedupes double-starts, `maxConcurrent` caps simultaneous interval runs, and
17
+ * `timeoutMs` kills a hung interval run. Commands run with shell:false (argv,
18
+ * never a shell string) — no injection.
19
+ *
20
+ * spawn is injected via `_deps` so the whole thing is unit-testable without
21
+ * real processes.
22
+ */
23
+
24
+ import { spawn } from "node:child_process";
25
+
26
+ export const _deps = { spawn };
27
+
28
+ const DEFAULT_INTERVAL_MS = 60_000;
29
+ const DEFAULT_TIMEOUT_MS = 30_000;
30
+ const DEFAULT_MAX_CONCURRENT = 8;
31
+ const OUTPUT_RING = 200; // max buffered output records before dropping oldest
32
+
33
+ export class PluginMonitorSupervisor {
34
+ constructor(opts = {}) {
35
+ this.maxConcurrent = opts.maxConcurrent || DEFAULT_MAX_CONCURRENT;
36
+ this.defaultIntervalMs = opts.defaultIntervalMs || DEFAULT_INTERVAL_MS;
37
+ this.timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
38
+ this._spawn = opts.spawn || _deps.spawn;
39
+ // id -> { desc, timer?, child?, running }
40
+ this._monitors = new Map();
41
+ this._outputs = []; // structured records awaiting drain
42
+ this._runningIntervalRuns = 0;
43
+ this._stopped = false;
44
+ this._exitHook = () => this.stopAll();
45
+ this._exitHookInstalled = false;
46
+ }
47
+
48
+ /** True when at least one monitor is registered and not stopped. */
49
+ get size() {
50
+ return this._monitors.size;
51
+ }
52
+
53
+ /**
54
+ * Start a batch of monitor descriptors (from collectPluginMonitors). Skips a
55
+ * monitor whose id is already running (dedupe). Returns the ids started.
56
+ * @param {Array<object>} monitors
57
+ * @returns {string[]} ids actually started
58
+ */
59
+ start(monitors = []) {
60
+ if (this._stopped) return [];
61
+ const started = [];
62
+ for (const desc of monitors) {
63
+ if (!desc || !desc.id) continue;
64
+ if (this._monitors.has(desc.id)) continue; // dedupe double-start
65
+ const entry = { desc, timer: null, child: null, running: false };
66
+ this._monitors.set(desc.id, entry);
67
+ if (desc.mode === "longRunning") {
68
+ this._spawnLongRunning(entry);
69
+ } else {
70
+ // interval: run once immediately, then on a cadence.
71
+ const ms = desc.intervalMs || this.defaultIntervalMs;
72
+ this._runIntervalOnce(entry);
73
+ entry.timer = setInterval(() => this._runIntervalOnce(entry), ms);
74
+ if (typeof entry.timer.unref === "function") entry.timer.unref();
75
+ }
76
+ started.push(desc.id);
77
+ }
78
+ if (started.length > 0) this._installExitHook();
79
+ return started;
80
+ }
81
+
82
+ _installExitHook() {
83
+ if (this._exitHookInstalled) return;
84
+ this._exitHookInstalled = true;
85
+ // Backstop: if the caller forgets stopAll(), reap on process exit so no
86
+ // monitor process outlives the session.
87
+ process.once("exit", this._exitHook);
88
+ }
89
+
90
+ _record(desc, stream, text) {
91
+ const lines = String(text)
92
+ .split(/\r?\n/)
93
+ .filter((l) => l.length > 0);
94
+ for (const line of lines) {
95
+ this._outputs.push({
96
+ id: desc.id,
97
+ plugin: desc.plugin,
98
+ monitor: desc.name,
99
+ stream,
100
+ line,
101
+ });
102
+ if (this._outputs.length > OUTPUT_RING) this._outputs.shift();
103
+ }
104
+ }
105
+
106
+ _spawnLongRunning(entry) {
107
+ const { desc } = entry;
108
+ let child;
109
+ try {
110
+ child = this._spawn(desc.command, desc.args, {
111
+ cwd: desc.cwd,
112
+ env: desc.env ? { ...process.env, ...desc.env } : process.env,
113
+ shell: false,
114
+ stdio: ["ignore", "pipe", "pipe"],
115
+ });
116
+ } catch (err) {
117
+ this._record(desc, "error", `spawn failed: ${err.message}`);
118
+ return;
119
+ }
120
+ entry.child = child;
121
+ child.stdout?.on("data", (d) =>
122
+ this._record(desc, "stdout", d.toString("utf8")),
123
+ );
124
+ child.stderr?.on("data", (d) =>
125
+ this._record(desc, "stderr", d.toString("utf8")),
126
+ );
127
+ child.on("error", (err) => this._record(desc, "error", err.message));
128
+ child.on("exit", (code) => {
129
+ entry.child = null;
130
+ this._record(desc, "exit", `longRunning monitor exited (code ${code})`);
131
+ });
132
+ }
133
+
134
+ _runIntervalOnce(entry) {
135
+ const { desc } = entry;
136
+ if (this._stopped) return;
137
+ if (entry.running) return; // previous run still in flight — skip this tick
138
+ if (this._runningIntervalRuns >= this.maxConcurrent) return; // cap
139
+ entry.running = true;
140
+ this._runningIntervalRuns++;
141
+ let child;
142
+ try {
143
+ child = this._spawn(desc.command, desc.args, {
144
+ cwd: desc.cwd,
145
+ env: desc.env ? { ...process.env, ...desc.env } : process.env,
146
+ shell: false,
147
+ stdio: ["ignore", "pipe", "pipe"],
148
+ });
149
+ } catch (err) {
150
+ this._record(desc, "error", `spawn failed: ${err.message}`);
151
+ entry.running = false;
152
+ this._runningIntervalRuns--;
153
+ return;
154
+ }
155
+ entry.child = child;
156
+ let done = false;
157
+ const finish = () => {
158
+ if (done) return;
159
+ done = true;
160
+ if (killTimer) clearTimeout(killTimer);
161
+ entry.child = null;
162
+ entry.running = false;
163
+ this._runningIntervalRuns--;
164
+ };
165
+ const killTimer = setTimeout(() => {
166
+ this._record(
167
+ desc,
168
+ "error",
169
+ `interval run timed out after ${this.timeoutMs}ms`,
170
+ );
171
+ try {
172
+ child.kill("SIGTERM");
173
+ } catch {
174
+ /* already gone */
175
+ }
176
+ }, this.timeoutMs);
177
+ if (typeof killTimer.unref === "function") killTimer.unref();
178
+ child.stdout?.on("data", (d) =>
179
+ this._record(desc, "stdout", d.toString("utf8")),
180
+ );
181
+ child.stderr?.on("data", (d) =>
182
+ this._record(desc, "stderr", d.toString("utf8")),
183
+ );
184
+ child.on("error", (err) => {
185
+ this._record(desc, "error", err.message);
186
+ finish();
187
+ });
188
+ child.on("exit", () => finish());
189
+ }
190
+
191
+ /**
192
+ * Take and clear all buffered monitor output records (for injection into the
193
+ * next agent turn). Returns [] when nothing has been captured.
194
+ */
195
+ drainOutputs() {
196
+ const out = this._outputs;
197
+ this._outputs = [];
198
+ return out;
199
+ }
200
+
201
+ /** Peek at buffered output without clearing it. */
202
+ peekOutputs() {
203
+ return this._outputs.slice();
204
+ }
205
+
206
+ /** Ids of every registered monitor. */
207
+ ids() {
208
+ return [...this._monitors.keys()];
209
+ }
210
+
211
+ /**
212
+ * Stop and reap EVERYTHING: clear interval timers, kill all children. Safe to
213
+ * call more than once. After this, `start` is a no-op.
214
+ */
215
+ stopAll() {
216
+ this._stopped = true;
217
+ for (const entry of this._monitors.values()) {
218
+ if (entry.timer) {
219
+ clearInterval(entry.timer);
220
+ entry.timer = null;
221
+ }
222
+ if (entry.child) {
223
+ try {
224
+ entry.child.kill("SIGTERM");
225
+ } catch {
226
+ /* already gone */
227
+ }
228
+ entry.child = null;
229
+ }
230
+ }
231
+ this._monitors.clear();
232
+ this._runningIntervalRuns = 0;
233
+ if (this._exitHookInstalled) {
234
+ process.removeListener("exit", this._exitHook);
235
+ this._exitHookInstalled = false;
236
+ }
237
+ }
238
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Discover installed plugins' agent-definition directories so `discoverAgents`
3
+ * can expose them as named subagents (Phase 3.3h — plugin Agent component
4
+ * reaches the agent chain).
5
+ *
6
+ * A plugin ships subagents under `<pluginRoot>/agents/<name>.md` (the same
7
+ * markdown-with-frontmatter shape as `.claude/agents/*.md`), so each installed
8
+ * plugin's `agents/` directory becomes an extra agent-scan layer. Only plugins
9
+ * whose manifest fully validated (`manifest.ok`) contribute.
10
+ *
11
+ * NOT trust-gated: an agent definition is declarative (a system prompt + an
12
+ * optional tool ALLOW-list + model — it can only narrow, never expand,
13
+ * capability) and runs only when the user invokes it BY NAME (`cc agents run …`),
14
+ * exactly like a skill. It is never auto-executed at startup (unlike a hook), so
15
+ * it carries the same trust as a skill's SKILL.md, which is also not gated. The
16
+ * code-bearing components (hooks/LSP/MCP) remain trust-gated.
17
+ */
18
+
19
+ import fs from "fs";
20
+ import path from "path";
21
+ import { discoverPlugins } from "./scopes.js";
22
+
23
+ export const _deps = { existsSync: fs.existsSync };
24
+
25
+ /**
26
+ * @param {object} [opts] { cwd, scopes }
27
+ * @returns {Array<{ name, scope, version, dir }>} one entry per plugin that has
28
+ * an `agents/` directory; `dir` is that directory (a scan root for
29
+ * discoverAgents' walkMd).
30
+ */
31
+ export function discoverPluginAgentLayers(opts = {}) {
32
+ let plugins = [];
33
+ try {
34
+ plugins = discoverPlugins({ cwd: opts.cwd, scopes: opts.scopes });
35
+ } catch {
36
+ return [];
37
+ }
38
+ const out = [];
39
+ for (const p of plugins) {
40
+ if (!p.manifest || p.manifest.ok !== true) continue;
41
+ const agentsDir = path.join(p.root, "agents");
42
+ if (!_deps.existsSync(agentsDir)) continue;
43
+ out.push({
44
+ name: p.name,
45
+ scope: p.scope,
46
+ version: p.version,
47
+ dir: agentsDir,
48
+ });
49
+ }
50
+ return out;
51
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Plugin `bin` component — put a trusted plugin's executables on PATH for the
3
+ * session (Phase 3.3n). A plugin ships helper executables under `bin/`; making
4
+ * them resolvable means the agent's `run_shell` (and monitors/hooks, which
5
+ * inherit the process env) can invoke `some-plugin-tool` by name.
6
+ *
7
+ * Wiring: prepend the trusted plugins' bin dirs to `process.env.PATH` at session
8
+ * start and RESTORE the original PATH at session end — session-scoped, so no
9
+ * plugin executable stays resolvable after the session. Every child process
10
+ * inherits process.env, so this one mutation covers run_shell (foreground +
11
+ * background), the monitor supervisor, and hook commands uniformly.
12
+ *
13
+ * SECURITY: an executable on PATH runs code, so — like hooks/LSP/MCP/monitors —
14
+ * only TRUSTED plugins contribute (user/local auto-trusted; project needs
15
+ * `cc plugin trust`). An untrusted cloned-repo plugin can NOT shadow a system
16
+ * command or get its binary invoked.
17
+ */
18
+
19
+ import path from "path";
20
+ import { discoverPlugins } from "./scopes.js";
21
+ import { partitionByTrust, warnUntrustedOnce } from "./trust.js";
22
+
23
+ /**
24
+ * Collect trusted, installed plugins' `bin/` directories.
25
+ * @param {object} [opts] { cwd, scopes }
26
+ * @returns {Array<{plugin, scope, version, dir}>}
27
+ */
28
+ export function collectPluginBinDirs(opts = {}) {
29
+ let plugins = [];
30
+ try {
31
+ plugins = discoverPlugins({ cwd: opts.cwd, scopes: opts.scopes });
32
+ } catch {
33
+ return [];
34
+ }
35
+ const { trusted, skipped } = partitionByTrust(plugins);
36
+ warnUntrustedOnce(
37
+ skipped
38
+ .filter((p) => p.manifest?.components?.bin?.length)
39
+ .map((p) => p.name),
40
+ "bin",
41
+ );
42
+ const out = [];
43
+ for (const p of trusted) {
44
+ if (!p.manifest || p.manifest.ok !== true) continue;
45
+ const bins = p.manifest.components?.bin;
46
+ if (!Array.isArray(bins) || bins.length === 0) continue;
47
+ // Every bin entry's absPath is a file inside the plugin's bin dir; collect
48
+ // the unique parent directories (usually just `<root>/bin`).
49
+ const dirs = new Set(bins.map((b) => path.dirname(b.absPath)));
50
+ for (const dir of dirs) {
51
+ out.push({
52
+ plugin: p.name,
53
+ scope: p.scope,
54
+ version: p.version,
55
+ dir,
56
+ });
57
+ }
58
+ }
59
+ return out;
60
+ }
61
+
62
+ /**
63
+ * Prepend trusted plugins' bin dirs to `env.PATH` (defaults to process.env).
64
+ * Returns a `restore()` that puts PATH back, plus the dirs added. Idempotent per
65
+ * call — a dir already on PATH is not added twice.
66
+ *
67
+ * @param {object} [opts] { cwd, scopes, env }
68
+ * @returns {{ added: string[], restore: () => void }}
69
+ */
70
+ export function applyPluginBinPath(opts = {}) {
71
+ const env = opts.env || process.env;
72
+ const dirs = collectPluginBinDirs({ cwd: opts.cwd, scopes: opts.scopes }).map(
73
+ (b) => b.dir,
74
+ );
75
+ const prevPath = env.PATH;
76
+ if (dirs.length === 0) {
77
+ return { added: [], restore: () => {} };
78
+ }
79
+ const existing = new Set(
80
+ String(prevPath || "")
81
+ .split(path.delimiter)
82
+ .filter(Boolean),
83
+ );
84
+ const added = dirs.filter((d) => !existing.has(d));
85
+ if (added.length === 0) {
86
+ return { added: [], restore: () => {} };
87
+ }
88
+ env.PATH = [...added, prevPath].filter(Boolean).join(path.delimiter);
89
+ let restored = false;
90
+ return {
91
+ added,
92
+ restore: () => {
93
+ if (restored) return;
94
+ restored = true;
95
+ // Restore only if nothing else re-touched PATH out from under us; either
96
+ // way, put the recorded previous value back (session-scoped semantics).
97
+ env.PATH = prevPath;
98
+ },
99
+ };
100
+ }