chainlesschain 0.162.188 → 0.162.193

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 (337) hide show
  1. package/README.md +61 -11
  2. package/bin/chainlesschain.js +7 -52
  3. package/completions/_cc +209 -0
  4. package/completions/cc.bash +18 -0
  5. package/completions/cc.fish +13 -0
  6. package/completions/cc.ps1 +226 -0
  7. package/package.json +10 -2
  8. package/src/assets/web-panel/assets/{AIOps-Brr1LVcE.js → AIOps-DBLkaTje.js} +1 -1
  9. package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-BAy-Qebo.js} +1 -1
  10. package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-Dfb1Mlau.js} +3 -3
  11. package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-BbFkN8m0.js} +5 -5
  12. package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-kM4-cEEH.js} +1 -1
  13. package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-BSQfYWf3.js} +1 -1
  14. package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-CWDbI_fG.js} +1 -1
  15. package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BxLPbq_Q.js} +1 -1
  16. package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-DuIbdrp6.js} +1 -1
  17. package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-NEmjGahU.js} +5 -5
  18. package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DfJEo-5_.js} +1 -1
  19. package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CaoeG3Ns.js} +1 -1
  20. package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-BFf_g3xY.js} +1 -1
  21. package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CNOd8mdQ.js} +1 -1
  22. package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-BhpDqz-i.js} +1 -1
  23. package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-CQ42T7Z6.js} +1 -1
  24. package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-DI5PcLMN.js} +1 -1
  25. package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-wYYZe0wR.js} +3 -3
  26. package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-Dlq03DD_.js} +2 -2
  27. package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BzqS8PP3.js} +1 -1
  28. package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-It55Yem7.js} +2 -2
  29. package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-9HkH6oxn.js} +2 -2
  30. package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-00e2YWRB.js} +1 -1
  31. package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-By-ugRDL.js} +1 -1
  32. package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-C396yu5m.js} +1 -1
  33. package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-BME5sbZY.js} +1 -1
  34. package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-B4TEZCr-.js} +1 -1
  35. package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-_9CX9ieY.js} +1 -1
  36. package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-BrS6WVSr.js} +2 -2
  37. package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-BMdCWnKF.js} +1 -1
  38. package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-Bcp3vzcA.js} +1 -1
  39. package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-DlLuqGY5.js} +1 -1
  40. package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-D-W_6x09.js} +2 -2
  41. package/src/assets/web-panel/assets/MarkdownRenderer-DElGPxCZ.js +1 -0
  42. package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-Dzn2guvI.js} +1 -1
  43. package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-uPict-KL.js} +6 -6
  44. package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-BCw43kOb.js} +2 -2
  45. package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-DmV8Wv7F.js} +1 -1
  46. package/src/assets/web-panel/assets/{MobileProjects-COBbEOlj.js → MobileProjects-C-HaCmSM.js} +1 -1
  47. package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-C6jxJ34Y.js} +5 -5
  48. package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-Za8ExwhQ.js} +5 -5
  49. package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-KKDbuEgb.js} +3 -3
  50. package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-Dj97zbCk.js} +1 -1
  51. package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DBXvMraU.js} +4 -4
  52. package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-DOxITLYl.js} +1 -1
  53. package/src/assets/web-panel/assets/OrderTableRenderer-7DVjZwMM.js +1 -0
  54. package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D1i73mjb.js} +4 -4
  55. package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-kW6Q3rvg.js} +1 -1
  56. package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-C-v_8p2v.js} +2 -2
  57. package/src/assets/web-panel/assets/PdhVaultBrowser-tUbbY64W.js +7 -0
  58. package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-DkHxEZ58.js} +4 -4
  59. package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-DWuMPGrK.js} +5 -5
  60. package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-30IYXWWm.js} +1 -1
  61. package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-DZNQ4RrH.js} +1 -1
  62. package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-De2MCElf.js} +2 -2
  63. package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-5uHk9wsC.js} +2 -2
  64. package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-XjDfrFLb.js} +1 -1
  65. package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-Bgwj6gcE.js} +1 -1
  66. package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-gKql9umv.js} +1 -1
  67. package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-BWvmwkh7.js} +1 -1
  68. package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-Uok4hZPd.js} +1 -1
  69. package/src/assets/web-panel/assets/{RemoteSession-oOVIv9wq.js → RemoteSession-DLJzTk9a.js} +3 -3
  70. package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-ZVwIPMSO.js} +1 -1
  71. package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CN04HxWR.js} +1 -1
  72. package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BdSrEG4g.js} +3 -3
  73. package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-5PwVd0IR.js} +1 -1
  74. package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-DdBo4zjN.js} +3 -3
  75. package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-D3letU7M.js} +2 -2
  76. package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton--Jb83f1v.js} +1 -1
  77. package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-Bg-CulHX.js} +1 -1
  78. package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-7QOa7S-X.js} +1 -1
  79. package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-DQwppjRw.js} +1 -1
  80. package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-dNPQGL-s.js} +2 -2
  81. package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-Ggs-Z48q.js} +1 -1
  82. package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-78TYHgRO.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-xHwwv9vO.js} +1 -1
  84. package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BJXSJfm4.js} +2 -2
  85. package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-fLbjRhJw.js} +1 -1
  86. package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-oMSBSBmB.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-CfnY_wX_.js} +1 -1
  88. package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-AdyTgYKO.js} +1 -1
  89. package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-C5K_81dS.js} +1 -1
  90. package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-vcGVDqki.js} +1 -1
  91. package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-hhOc3hlY.js} +4 -4
  92. package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-CVloC11s.js} +3 -3
  93. package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-CYeE32R5.js} +1 -1
  94. package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-CNROR5bN.js} +1 -1
  95. package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-Cjx-cnwU.js} +1 -1
  96. package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-DOIfelvD.js} +1 -1
  97. package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-DfA7UZ8u.js} +1 -1
  98. package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +1 -0
  99. package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-BMSCzcfM.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-5-HXgOX5.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-AWg-0M1S.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-B4iYzpkL.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-B5Udeb_6.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-B7gf77CJ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-BEMwBBHk.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-BM1y_9hM.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-BUonmKSE.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-BbQ2eQ8H.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-BemJ2u_E.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-BolaOQxx.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-BxOk9vkG.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-By61ojUx.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-C2K9SNVX.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-C3L5gdiD.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-C7xn0Nub.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-COG61sRv.js → index-CAnvhwg6.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CEKMcOyU.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-CNC5onm4.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CVL27kXC.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-CVm1_i3t.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-CdYBye8B.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-ClNMrqEY.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-Cs6l7uRu.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-CshQbilz.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-Cyweh0Hy.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-DBpWXFed.js} +3 -3
  127. package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-DHtpHZAQ.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-DLmn0BoK.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-DPAWBrMf.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-DjEsxO5h.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-FBc_CJBd.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Icdoqmzt.js} +1 -1
  133. package/src/assets/web-panel/assets/index-Q2IajL54.js +1 -0
  134. package/src/assets/web-panel/assets/index-bRkHgOMc.js +1 -0
  135. package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-dCd8cSUq.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-m2vG9gG_.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-CaslYch6.js → index-mTpHoEzr.js} +1 -1
  138. package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-ti_lnTWl.js} +1 -1
  139. package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-Bu8k7H1Y.js} +1 -1
  140. package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-s3iVK41k.js} +1 -1
  141. package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-B6B-E5Jz.js} +1 -1
  142. package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-BiYgTidA.js} +1 -1
  143. package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-pU1RdUDA.js} +1 -1
  144. package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-Cdoq_SeM.js} +1 -1
  145. package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-DIuvAn2K.js} +1 -1
  146. package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-8w16YR2D.js} +1 -1
  147. package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-VZ01cZe3.js} +1 -1
  148. package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-lh8YMN8Z.js} +1 -1
  149. package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-CmDyreec.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-hiJWMU5y.js} +1 -1
  151. package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-Thnu-Iaj.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-LBdOxjwY.js} +1 -1
  153. package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-Cg6lFY_X.js} +1 -1
  154. package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-DVfsR1YO.js} +1 -1
  155. package/src/assets/web-panel/index.html +1 -1
  156. package/src/cli-help.js +181 -0
  157. package/src/command-help-index.json +181 -0
  158. package/src/command-manifest.json +1107 -175
  159. package/src/command-surface-policy.js +197 -0
  160. package/src/commands/agent.js +98 -33
  161. package/src/commands/artifacts.js +400 -2
  162. package/src/commands/background-session.js +41 -3
  163. package/src/commands/checkpoint-managed.js +1059 -0
  164. package/src/commands/checkpoint-restore-recovery.js +1476 -0
  165. package/src/commands/checkpoint.js +950 -22
  166. package/src/commands/compact.js +101 -25
  167. package/src/commands/config.js +506 -20
  168. package/src/commands/doctor.js +4 -1
  169. package/src/commands/hub.js +1 -1
  170. package/src/commands/mcp.js +688 -24
  171. package/src/commands/pack.js +16 -2
  172. package/src/commands/plugin.js +20 -12
  173. package/src/commands/session-mcp-recovery.js +218 -0
  174. package/src/commands/session.js +470 -110
  175. package/src/commands/setup.js +36 -10
  176. package/src/commands/skill.js +181 -110
  177. package/src/commands/status.js +21 -5
  178. package/src/commands/team-distributed.js +3324 -0
  179. package/src/commands/team.js +1130 -74
  180. package/src/commands/update.js +68 -6
  181. package/src/constants.js +11 -0
  182. package/src/data/changelog.json +26 -0
  183. package/src/gateways/ws/action-protocol.js +10 -0
  184. package/src/gateways/ws/session-protocol.js +555 -27
  185. package/src/gateways/ws/ws-agent-handler.js +541 -14
  186. package/src/gateways/ws/ws-session-gateway.js +129 -22
  187. package/src/gateways/ws/ws-session-state.js +431 -0
  188. package/src/harness/background-task-manager.js +233 -36
  189. package/src/harness/jsonl-session-store.js +2551 -167
  190. package/src/harness/mcp-client.js +802 -61
  191. package/src/harness/prompt-compressor.js +227 -27
  192. package/src/harness/session-index.js +89 -38
  193. package/src/harness/session-list-index.js +254 -0
  194. package/src/harness/structured-handoff.js +410 -0
  195. package/src/lazy-dispatch.js +747 -84
  196. package/src/lib/__tests__/logger.test.js +50 -2
  197. package/src/lib/advisor-runtime.js +1047 -0
  198. package/src/lib/agent-core.js +2 -0
  199. package/src/lib/agent-sandbox.js +120 -26
  200. package/src/lib/agent-session-export.js +20 -0
  201. package/src/lib/agent-team/task-lease.js +271 -3
  202. package/src/lib/agent-team/team-adjudication.js +1501 -0
  203. package/src/lib/agent-team/team-budget.js +4 -1
  204. package/src/lib/agent-team/team-control-store.js +582 -0
  205. package/src/lib/agent-team/team-distributed-queue.js +5246 -0
  206. package/src/lib/agent-team/team-process-checkpoint.js +211 -0
  207. package/src/lib/agent-team/team-runner.js +543 -24
  208. package/src/lib/agent-team/team-worktree.js +1403 -95
  209. package/src/lib/artifact-store.js +131 -8
  210. package/src/lib/background-agent-supervisor.js +185 -13
  211. package/src/lib/background-interaction-journal.js +37 -9
  212. package/src/lib/background-launch-profile.js +1058 -0
  213. package/src/lib/checkpoint-restore-already-completed-controller.js +902 -0
  214. package/src/lib/checkpoint-restore-orchestrator.js +987 -0
  215. package/src/lib/checkpoint-restore-partial-rollback-controller.js +1953 -0
  216. package/src/lib/checkpoint-restore-recovery-controller.js +689 -0
  217. package/src/lib/checkpoint-restore-recovery.js +884 -0
  218. package/src/lib/checkpoint-restore-retention.js +126 -0
  219. package/src/lib/checkpoint-restore-saga.js +5079 -0
  220. package/src/lib/checkpoint-restore-session-recovery.js +1178 -0
  221. package/src/lib/checkpoint-restore-workspace-target-verifier.js +270 -0
  222. package/src/lib/checkpoint-store.js +1688 -97
  223. package/src/lib/checkpoint-timeline-authority.js +522 -0
  224. package/src/lib/checkpoint-timeline.js +642 -0
  225. package/src/lib/config-keys.js +18 -119
  226. package/src/lib/config-manager.js +446 -27
  227. package/src/lib/config-redaction.js +57 -0
  228. package/src/lib/config-schema.js +853 -0
  229. package/src/lib/cowork-mcp-tools.js +5 -0
  230. package/src/lib/cowork-task-runner.js +458 -63
  231. package/src/lib/delivery-coordinator.js +1234 -0
  232. package/src/lib/delivery-evidence.js +450 -0
  233. package/src/lib/doctor-checkup.js +199 -14
  234. package/src/lib/executable-path.js +34 -0
  235. package/src/lib/fatal-handler.js +16 -0
  236. package/src/lib/file-checkpoint.js +4909 -186
  237. package/src/lib/file-lines.js +125 -0
  238. package/src/lib/headless-config-command.js +16 -5
  239. package/src/lib/hook-runner.cjs +139 -38
  240. package/src/lib/hooks-v2-workspace-context.js +10 -0
  241. package/src/lib/impacted-gate-selector.js +424 -0
  242. package/src/lib/interaction-adapter.js +62 -0
  243. package/src/lib/jsonl-session-store.js +1 -35
  244. package/src/lib/logger.js +66 -14
  245. package/src/lib/managed-checkpoint-render.js +60 -0
  246. package/src/lib/managed-tool-checkpoint.js +194 -0
  247. package/src/lib/mcp-call-ledger-store.js +1069 -0
  248. package/src/lib/mcp-call-ledger.js +747 -0
  249. package/src/lib/mcp-client.js +5 -0
  250. package/src/lib/mcp-conflict-scheduler.js +468 -0
  251. package/src/lib/mcp-effect-contract.js +102 -0
  252. package/src/lib/mcp-headers-helper-trust.js +145 -0
  253. package/src/lib/mcp-headers-helper.js +616 -0
  254. package/src/lib/mcp-host-recovery-runtime.js +131 -0
  255. package/src/lib/mcp-ledger-recovery-admission.js +1065 -0
  256. package/src/lib/mcp-recovery-adjudication.js +443 -0
  257. package/src/lib/output-context.js +86 -0
  258. package/src/lib/packer/file-identity.js +170 -0
  259. package/src/lib/packer/index.js +3 -0
  260. package/src/lib/packer/native-update-state.js +277 -0
  261. package/src/lib/packer/pack-update-applier.js +1983 -136
  262. package/src/lib/packer/pack-update-checker.js +34 -0
  263. package/src/lib/packer/pack-update-downloader.js +875 -86
  264. package/src/lib/packer/pack-update-signature.js +86 -0
  265. package/src/lib/packer/pkg-config-generator.js +6 -0
  266. package/src/lib/paths.js +65 -10
  267. package/src/lib/plan-mode.js +831 -14
  268. package/src/lib/plan-persistence.js +540 -0
  269. package/src/lib/plugin-runtime/hooks.js +227 -69
  270. package/src/lib/plugin-runtime/plugin-options.js +189 -44
  271. package/src/lib/pr-automation-policy.js +146 -8
  272. package/src/lib/process-execution-broker/credential-agent.js +7 -0
  273. package/src/lib/process-execution-broker/index.js +491 -13
  274. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
  275. package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
  276. package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
  277. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  278. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  279. package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
  280. package/src/lib/process-execution-broker/workspace-transaction.js +4459 -0
  281. package/src/lib/project-instructions.js +165 -29
  282. package/src/lib/project-mcp-trust.js +67 -0
  283. package/src/lib/public-ide-capability-manifest.js +272 -0
  284. package/src/lib/public-ide-capability-validation.js +351 -0
  285. package/src/lib/secret-store.js +267 -43
  286. package/src/lib/secure-file-identity.js +433 -0
  287. package/src/lib/secure-fs.js +1089 -0
  288. package/src/lib/security-audit.js +58 -0
  289. package/src/lib/service-manager.js +34 -13
  290. package/src/lib/session-budget-runtime.js +1452 -0
  291. package/src/lib/session-host-snapshot.js +388 -0
  292. package/src/lib/session-message-provenance.js +429 -0
  293. package/src/lib/session-projection.js +804 -0
  294. package/src/lib/session-resource-budget.js +1495 -0
  295. package/src/lib/settings-hooks.cjs +138 -22
  296. package/src/lib/settings-loader.cjs +25 -2
  297. package/src/lib/side-effect-ledger-store.js +45 -23
  298. package/src/lib/skill-execution-identity.js +217 -0
  299. package/src/lib/skill-loader.js +299 -18
  300. package/src/lib/sub-agent-context.js +236 -28
  301. package/src/lib/sub-agent-registry.js +5 -0
  302. package/src/lib/subagent-contract.js +94 -0
  303. package/src/lib/todo-manager.js +185 -18
  304. package/src/lib/todo-persistence.js +417 -0
  305. package/src/lib/turn-binding-store.js +41 -23
  306. package/src/lib/turn-binding.js +160 -2
  307. package/src/lib/with-file-lock.js +165 -24
  308. package/src/lib/ws-chat-handler.js +21 -0
  309. package/src/program-base.js +18 -0
  310. package/src/repl/advisor-command.js +103 -0
  311. package/src/repl/agent-repl.js +2175 -146
  312. package/src/repl/btw-command.js +93 -10
  313. package/src/repl/clipboard-image.js +119 -0
  314. package/src/repl/config-summary.js +64 -108
  315. package/src/repl/prompt-editor.js +233 -0
  316. package/src/repl/prompt-interactions.js +344 -0
  317. package/src/repl/prompt-stash.js +354 -0
  318. package/src/repl/prompt-suggestions.js +312 -0
  319. package/src/repl/repl-keybindings.js +227 -0
  320. package/src/repl/session-recap.js +217 -0
  321. package/src/repl/slash-command-registry.js +22 -0
  322. package/src/repl/terminal-layout.js +88 -0
  323. package/src/runtime/agent-core.js +1630 -447
  324. package/src/runtime/coding-agent-contract-shared.cjs +13 -1
  325. package/src/runtime/coding-agent-managed-tool-policy.cjs +7 -2
  326. package/src/runtime/diagnostics.js +32 -13
  327. package/src/runtime/headless-runner.js +310 -118
  328. package/src/runtime/headless-stream.js +356 -95
  329. package/src/runtime/mcp-config.js +148 -14
  330. package/src/runtime/policies/agent-policy.js +14 -0
  331. package/src/runtime/status-diagnostics-lite.js +137 -0
  332. package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
  333. package/src/assets/web-panel/assets/OrderTableRenderer-BU2Yqq5H.js +0 -1
  334. package/src/assets/web-panel/assets/PdhVaultBrowser-69ca_WeR.js +0 -7
  335. package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
  336. package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
  337. package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
@@ -12,9 +12,16 @@ import { executionBroker } from "../lib/process-execution-broker/index.js";
12
12
  import { AsyncLocalStorage } from "node:async_hooks";
13
13
  import { EventEmitter } from "events";
14
14
  import { pathToFileURL } from "url";
15
+ import WebSocket from "ws";
15
16
  import { safeJsonParse } from "../lib/safe-json.js";
16
17
  import { EventRuntimeProducer } from "../lib/event-runtime-producer.js";
18
+ import { currentHostHooksV2WorkspaceBinding } from "../lib/hooks-v2-workspace-context.js";
17
19
  import { resolvePluginWorkspaceAuthority } from "../lib/plugin-runtime/sandbox-policy.js";
20
+ import {
21
+ mergeMcpHeaders,
22
+ resolveMcpHeadersHelperContext,
23
+ runMcpHeadersHelper,
24
+ } from "../lib/mcp-headers-helper.js";
18
25
 
19
26
  /**
20
27
  * Injectable dependencies — overridable from tests.
@@ -23,6 +30,9 @@ import { resolvePluginWorkspaceAuthority } from "../lib/plugin-runtime/sandbox-p
23
30
  export const _deps = {
24
31
  spawn: executionBroker.spawn.bind(executionBroker),
25
32
  fetch: (...args) => globalThis.fetch(...args),
33
+ WebSocket,
34
+ runMcpHeadersHelper,
35
+ resolveMcpHeadersHelperContext,
26
36
  // Backoff sleep seam (tests override with a no-op so retries don't wait).
27
37
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
28
38
  };
@@ -37,8 +47,24 @@ export const ServerState = {
37
47
  ERROR: "error",
38
48
  };
39
49
 
40
- /** Transport kinds that carry a URL (no stdio process). */
41
- const URL_TRANSPORTS = new Set(["http", "https", "sse", "ws", "wss"]);
50
+ export const MCP_CONFIG_SCOPES = Object.freeze([
51
+ "local",
52
+ "project",
53
+ "user",
54
+ "managed",
55
+ ]);
56
+
57
+ export function normalizeMcpConfigScope(scope = "user") {
58
+ const normalized = String(scope || "user")
59
+ .trim()
60
+ .toLowerCase();
61
+ if (!MCP_CONFIG_SCOPES.includes(normalized)) {
62
+ throw new Error(
63
+ `Invalid MCP scope "${scope}"; expected ${MCP_CONFIG_SCOPES.join(" | ")}`,
64
+ );
65
+ }
66
+ return normalized;
67
+ }
42
68
 
43
69
  /**
44
70
  * Default per-call timeout for HTTP MCP requests, mirroring the 30s stdio
@@ -47,6 +73,8 @@ const URL_TRANSPORTS = new Set(["http", "https", "sse", "ws", "wss"]);
47
73
  * review) are exempt; override per server with `config.requestTimeoutMs`.
48
74
  */
49
75
  const HTTP_REQUEST_TIMEOUT_MS = 30000;
76
+ const WEBSOCKET_CONNECT_TIMEOUT_MS = 10000;
77
+ const WEBSOCKET_MAX_PAYLOAD_BYTES = 16 * 1024 * 1024;
50
78
 
51
79
  /**
52
80
  * Default per-call timeout for stdio MCP requests — the same 30s default as the
@@ -88,7 +116,7 @@ export function inferTransport(config) {
88
116
  ) {
89
117
  return proto;
90
118
  }
91
- } catch (_e) {
119
+ } catch {
92
120
  // fall through to stdio
93
121
  }
94
122
  }
@@ -171,6 +199,34 @@ export function isHttpTransport(transportKind) {
171
199
  );
172
200
  }
173
201
 
202
+ /** True for MCP's bidirectional JSON-RPC-over-WebSocket transports. */
203
+ export function isWebSocketTransport(transportKind) {
204
+ return transportKind === "ws" || transportKind === "wss";
205
+ }
206
+
207
+ export function redactMcpUrl(value) {
208
+ try {
209
+ const parsed = new URL(String(value));
210
+ parsed.username = "";
211
+ parsed.password = "";
212
+ parsed.search = "";
213
+ parsed.hash = "";
214
+ return parsed.toString();
215
+ } catch {
216
+ return "<invalid-url>";
217
+ }
218
+ }
219
+
220
+ function mcpTransportError(code, message, details = {}) {
221
+ const error = new Error(message);
222
+ error.code = code;
223
+ error.transport = details.transport || null;
224
+ error.url = details.url ? redactMcpUrl(details.url) : null;
225
+ if (details.status != null) error.status = details.status;
226
+ if (details.closeCode != null) error.closeCode = details.closeCode;
227
+ return error;
228
+ }
229
+
174
230
  /**
175
231
  * Heuristic: does this error look like the server went away (vs. the tool
176
232
  * itself failing)? Used to gate reconnect-and-retry for servers that have a
@@ -183,11 +239,19 @@ export function isHttpTransport(transportKind) {
183
239
  */
184
240
  export function isLikelyConnectionError(err) {
185
241
  const msg = String((err && err.message) || err || "");
186
- return /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|socket hang up|network error|HTTP 40[134]\b|not connected|not found|not available/i.test(
242
+ return /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|socket hang up|network error|WebSocket|HTTP 40[134]\b|not connected|not found|not available/i.test(
187
243
  msg,
188
244
  );
189
245
  }
190
246
 
247
+ export function isMcpAuthenticationError(err) {
248
+ return (
249
+ err?.status === 401 ||
250
+ err?.status === 403 ||
251
+ /\bHTTP\s*40[13]\b/i.test(String(err?.message || err || ""))
252
+ );
253
+ }
254
+
191
255
  /** Capability-discovery retry tuning (Claude-Code 2.1.191 "short backoff"). */
192
256
  const MCP_DISCOVERY_RETRIES = 2; // up to 3 attempts total
193
257
  const MCP_DISCOVERY_BACKOFF_MS = 250; // 250ms, 500ms
@@ -238,6 +302,8 @@ export class MCPClient extends EventEmitter {
238
302
  this._reconnecting = new Map(); // name → in-flight reconnect promise
239
303
  this._sessionId =
240
304
  options && options.sessionId != null ? String(options.sessionId) : null;
305
+ this._workspaceBinding =
306
+ options?.workspaceBinding || currentHostHooksV2WorkspaceBinding();
241
307
  // MCP roots capability (Claude-Code 2.1.203 parity): servers may ask the
242
308
  // client for its workspace roots (`roots/list`, a server→client request).
243
309
  // null = derive from process.cwd() at answer time.
@@ -327,7 +393,9 @@ export class MCPClient extends EventEmitter {
327
393
  `mcp-elicitation:${this._elicitationKey(serverName, requestId)}`,
328
394
  { response: normalized },
329
395
  );
330
- } catch {}
396
+ } catch {
397
+ // Runtime inbox acknowledgement is advisory.
398
+ }
331
399
  return true;
332
400
  }
333
401
 
@@ -441,7 +509,9 @@ export class MCPClient extends EventEmitter {
441
509
  `mcp-elicitation:${key}`,
442
510
  { response },
443
511
  );
444
- } catch {}
512
+ } catch {
513
+ // Runtime inbox acknowledgement is advisory.
514
+ }
445
515
  return response;
446
516
  }
447
517
  } catch (error) {
@@ -464,7 +534,9 @@ export class MCPClient extends EventEmitter {
464
534
  `mcp-elicitation:${key}`,
465
535
  { response: { action: "decline" } },
466
536
  );
467
- } catch {}
537
+ } catch {
538
+ // Runtime inbox acknowledgement is advisory.
539
+ }
468
540
  this._settleUrlElicitation(request, { action: "decline" });
469
541
  this.emit("elicitation-deferred", {
470
542
  ...request,
@@ -485,7 +557,9 @@ export class MCPClient extends EventEmitter {
485
557
  "elicitation timeout",
486
558
  { retryDelayMs: 0, maxAttempts: 1 },
487
559
  );
488
- } catch {}
560
+ } catch {
561
+ // Runtime inbox timeout reporting is advisory.
562
+ }
489
563
  resolve({ action: "cancel" });
490
564
  }, this._elicitationTimeoutMs);
491
565
  timeout.unref?.();
@@ -707,6 +781,50 @@ export class MCPClient extends EventEmitter {
707
781
  else this._reconnectors.delete(name);
708
782
  }
709
783
 
784
+ async _connectionHeaders(name, config, transportKind) {
785
+ const staticHeaders = { ...(config?.headers || {}) };
786
+ if (!config?.headersHelper) return staticHeaders;
787
+ if (
788
+ !isHttpTransport(transportKind) &&
789
+ !isWebSocketTransport(transportKind)
790
+ ) {
791
+ const error = new Error(
792
+ `MCP headersHelper is only supported by HTTP, SSE, and WebSocket servers (server "${name}")`,
793
+ );
794
+ error.code = "CC_MCP_HEADERS_HELPER_TRANSPORT_INVALID";
795
+ throw error;
796
+ }
797
+ const context = _deps.resolveMcpHeadersHelperContext(
798
+ {
799
+ ...config,
800
+ serverName: name,
801
+ },
802
+ this._workspaceBinding
803
+ ? { currentWorkspaceBinding: () => this._workspaceBinding }
804
+ : undefined,
805
+ );
806
+ const dynamicHeaders = await _deps.runMcpHeadersHelper({
807
+ command: config.headersHelper,
808
+ cwd: context.cwd,
809
+ pluginRoot: context.pluginRoot,
810
+ execution: context.execution,
811
+ serverName: name,
812
+ serverUrl: config.url,
813
+ });
814
+ return mergeMcpHeaders(staticHeaders, dynamicHeaders);
815
+ }
816
+
817
+ async _refreshConnectionHeaders(name, entry) {
818
+ if (!entry?.config?.headersHelper) return entry?.httpHeaders || {};
819
+ const headers = await this._connectionHeaders(
820
+ name,
821
+ entry.config,
822
+ entry.transportKind,
823
+ );
824
+ entry.httpHeaders = headers;
825
+ return headers;
826
+ }
827
+
710
828
  /**
711
829
  * Connect to an MCP server. Routes to stdio or HTTP transport based on
712
830
  * `config.transport` / `config.url` (see `inferTransport`).
@@ -714,25 +832,37 @@ export class MCPClient extends EventEmitter {
714
832
  * @param {string} name - Server name
715
833
  * @param {object} config - { command?, args?, env?, url?, transport? }
716
834
  */
717
- async connect(name, config) {
835
+ async connect(name, config, _authRetryUsed = false) {
718
836
  if (this.servers.has(name)) {
719
837
  throw new Error(`Server "${name}" already connected`);
720
838
  }
721
839
 
722
840
  const transportKind = inferTransport(config);
841
+ const sourceConfig = config;
842
+ const connectionHeaders = await this._connectionHeaders(
843
+ name,
844
+ sourceConfig,
845
+ transportKind,
846
+ );
847
+ config = { ...sourceConfig, headers: connectionHeaders };
723
848
  const entry = {
724
- config,
849
+ // Keep the source config (static headers + helper command) so every
850
+ // reconnect can execute the helper afresh instead of reusing credentials.
851
+ config: sourceConfig,
725
852
  transportKind,
726
853
  state: ServerState.CONNECTING,
727
854
  process: null,
855
+ socket: null,
728
856
  httpUrl: null,
729
- httpHeaders: {},
857
+ httpHeaders: connectionHeaders,
730
858
  httpSessionId: null,
731
859
  protocolVersion: null,
732
860
  _httpMessageStream: null,
733
861
  tools: [],
734
862
  resources: [],
863
+ resourceTemplates: [],
735
864
  prompts: [],
865
+ resourceSubscriptions: new Set(),
736
866
  _pending: new Map(),
737
867
  _buffer: "",
738
868
  // Per-connection streaming decoders so a multi-byte UTF-8 character (e.g.
@@ -743,6 +873,23 @@ export class MCPClient extends EventEmitter {
743
873
  _stderrDecoder: new TextDecoder("utf-8"),
744
874
  };
745
875
 
876
+ const rejectPending = (error) => {
877
+ const cause =
878
+ error instanceof Error
879
+ ? error
880
+ : new Error(String(error || "MCP transport closed"));
881
+ for (const [, pending] of entry._pending) {
882
+ if (pending.timeout) clearTimeout(pending.timeout);
883
+ try {
884
+ pending.reject(cause);
885
+ } catch {
886
+ // already settled
887
+ }
888
+ }
889
+ entry._pending.clear();
890
+ };
891
+ entry._rejectPending = rejectPending;
892
+
746
893
  this.servers.set(name, entry);
747
894
 
748
895
  try {
@@ -751,7 +898,168 @@ export class MCPClient extends EventEmitter {
751
898
  throw new Error(`HTTP transport requires a url (server "${name}")`);
752
899
  }
753
900
  entry.httpUrl = config.url;
754
- entry.httpHeaders = { ...(config.headers || {}) };
901
+ entry.httpHeaders = connectionHeaders;
902
+ } else if (isWebSocketTransport(transportKind)) {
903
+ if (!config.url) {
904
+ throw mcpTransportError(
905
+ "CC_MCP_WS_URL_REQUIRED",
906
+ `WebSocket transport requires a url (server "${name}")`,
907
+ { transport: transportKind },
908
+ );
909
+ }
910
+ let endpoint;
911
+ try {
912
+ endpoint = new URL(config.url);
913
+ } catch {
914
+ throw mcpTransportError(
915
+ "CC_MCP_WS_INVALID_URL",
916
+ `Invalid WebSocket URL for server "${name}": ${redactMcpUrl(config.url)}`,
917
+ { transport: transportKind, url: config.url },
918
+ );
919
+ }
920
+ if (endpoint.protocol !== `${transportKind}:`) {
921
+ throw mcpTransportError(
922
+ "CC_MCP_WS_SCHEME_MISMATCH",
923
+ `MCP transport "${transportKind}" requires a ${transportKind}:// URL (server "${name}")`,
924
+ { transport: transportKind, url: config.url },
925
+ );
926
+ }
927
+ if (endpoint.username || endpoint.password) {
928
+ throw mcpTransportError(
929
+ "CC_MCP_WS_URL_CREDENTIALS_FORBIDDEN",
930
+ `WebSocket credentials must use headers or OAuth (server "${name}")`,
931
+ { transport: transportKind, url: config.url },
932
+ );
933
+ }
934
+
935
+ const connectTimeoutMs = Number.isFinite(config.connectTimeoutMs)
936
+ ? Math.max(1, config.connectTimeoutMs)
937
+ : WEBSOCKET_CONNECT_TIMEOUT_MS;
938
+ const maxPayload = Number.isFinite(config.maxPayloadBytes)
939
+ ? Math.max(1024, config.maxPayloadBytes)
940
+ : WEBSOCKET_MAX_PAYLOAD_BYTES;
941
+ const Socket = _deps.WebSocket;
942
+ const socket = new Socket(config.url, {
943
+ headers: { ...(config.headers || {}) },
944
+ handshakeTimeout: connectTimeoutMs,
945
+ maxPayload,
946
+ followRedirects: config.followRedirects === true,
947
+ ...(transportKind === "wss" && config.rejectUnauthorized === false
948
+ ? { rejectUnauthorized: false }
949
+ : {}),
950
+ });
951
+ entry.socket = socket;
952
+
953
+ await new Promise((resolve, reject) => {
954
+ let settled = false;
955
+ const finish = (fn, value) => {
956
+ if (settled) return;
957
+ settled = true;
958
+ socket.off("open", onOpen);
959
+ socket.off("error", onError);
960
+ socket.off("unexpected-response", onUnexpectedResponse);
961
+ fn(value);
962
+ };
963
+ const onOpen = () => finish(resolve);
964
+ const onError = (cause) =>
965
+ finish(
966
+ reject,
967
+ mcpTransportError(
968
+ "CC_MCP_WS_CONNECT_FAILED",
969
+ `WebSocket connection failed for server "${name}": ${cause?.message || cause}`,
970
+ { transport: transportKind, url: config.url },
971
+ ),
972
+ );
973
+ const onUnexpectedResponse = (_request, response) =>
974
+ finish(
975
+ reject,
976
+ mcpTransportError(
977
+ "CC_MCP_WS_HANDSHAKE_REJECTED",
978
+ `WebSocket handshake failed for server "${name}": HTTP ${response?.statusCode || "unknown"}`,
979
+ {
980
+ transport: transportKind,
981
+ url: config.url,
982
+ status: response?.statusCode,
983
+ },
984
+ ),
985
+ );
986
+ socket.once("open", onOpen);
987
+ socket.once("error", onError);
988
+ socket.once("unexpected-response", onUnexpectedResponse);
989
+ });
990
+
991
+ socket.on("message", (data, isBinary) => {
992
+ if (isBinary) {
993
+ const error = mcpTransportError(
994
+ "CC_MCP_WS_BINARY_MESSAGE",
995
+ `MCP server "${name}" sent a binary WebSocket message; JSON text is required`,
996
+ { transport: transportKind, url: config.url },
997
+ );
998
+ entry.state = ServerState.ERROR;
999
+ rejectPending(error);
1000
+ this.emit("server-error", {
1001
+ name,
1002
+ error: error.message,
1003
+ code: error.code,
1004
+ });
1005
+ socket.close(1003, "JSON text required");
1006
+ return;
1007
+ }
1008
+ try {
1009
+ const message = JSON.parse(String(data));
1010
+ if (
1011
+ !message ||
1012
+ Array.isArray(message) ||
1013
+ typeof message !== "object"
1014
+ ) {
1015
+ throw new Error("expected one JSON-RPC object");
1016
+ }
1017
+ this._handleMessage(name, message);
1018
+ } catch (cause) {
1019
+ const error = mcpTransportError(
1020
+ "CC_MCP_WS_INVALID_MESSAGE",
1021
+ `Invalid WebSocket JSON-RPC message: ${cause?.message || cause}`,
1022
+ { transport: transportKind, url: config.url },
1023
+ );
1024
+ entry.state = ServerState.ERROR;
1025
+ rejectPending(error);
1026
+ this.emit("server-error", {
1027
+ name,
1028
+ code: error.code,
1029
+ error: error.message,
1030
+ });
1031
+ socket.close(1007, "Invalid JSON-RPC message");
1032
+ }
1033
+ });
1034
+ socket.on("close", (code, reason) => {
1035
+ entry.state = ServerState.DISCONNECTED;
1036
+ const suffix = reason?.length ? `: ${String(reason)}` : "";
1037
+ const error = mcpTransportError(
1038
+ "CC_MCP_WS_CLOSED",
1039
+ `MCP WebSocket server "${name}" disconnected (code ${code})${suffix}`,
1040
+ { transport: transportKind, url: config.url, closeCode: code },
1041
+ );
1042
+ rejectPending(error);
1043
+ this.emit("server-disconnected", {
1044
+ name,
1045
+ code,
1046
+ reason: String(reason || ""),
1047
+ });
1048
+ });
1049
+ socket.on("error", (cause) => {
1050
+ entry.state = ServerState.ERROR;
1051
+ const error = mcpTransportError(
1052
+ "CC_MCP_WS_ERROR",
1053
+ `MCP WebSocket server "${name}" error: ${cause?.message || cause}`,
1054
+ { transport: transportKind, url: config.url },
1055
+ );
1056
+ rejectPending(error);
1057
+ this.emit("server-error", {
1058
+ name,
1059
+ error: error.message,
1060
+ code: error.code,
1061
+ });
1062
+ });
755
1063
  } else if (transportKind === "stdio") {
756
1064
  if (!config.command) {
757
1065
  throw new Error(
@@ -950,6 +1258,18 @@ export class MCPClient extends EventEmitter {
950
1258
  // Server may not support resources
951
1259
  }
952
1260
 
1261
+ // Resource templates are optional even when resources are supported.
1262
+ try {
1263
+ const templatesResult = await this._sendRequest(
1264
+ name,
1265
+ "resources/templates/list",
1266
+ {},
1267
+ );
1268
+ entry.resourceTemplates = templatesResult?.resourceTemplates || [];
1269
+ } catch {
1270
+ // Server may not support resource templates.
1271
+ }
1272
+
953
1273
  // Fetch available prompts (server-provided slash commands)
954
1274
  try {
955
1275
  const promptsResult = await this._sendRequest(name, "prompts/list", {});
@@ -975,6 +1295,7 @@ export class MCPClient extends EventEmitter {
975
1295
  tools: entry.tools,
976
1296
  toolsError: entry.toolsError,
977
1297
  resources: entry.resources,
1298
+ resourceTemplates: entry.resourceTemplates,
978
1299
  prompts: entry.prompts,
979
1300
  serverInfo: entry.serverInfo,
980
1301
  instructions: entry.instructions,
@@ -999,7 +1320,29 @@ export class MCPClient extends EventEmitter {
999
1320
  // teardown is best-effort — the connect error is what matters
1000
1321
  }
1001
1322
  }
1323
+ if (entry.socket) {
1324
+ try {
1325
+ entry.socket.removeAllListeners();
1326
+ // ws emits an asynchronous error when terminate() races a rejected
1327
+ // opening handshake. Keep a sink installed after removing the
1328
+ // connection listeners so teardown cannot become an uncaught error.
1329
+ entry.socket.on?.("error", () => {});
1330
+ entry.socket.terminate?.();
1331
+ } catch {
1332
+ // teardown is best-effort
1333
+ }
1334
+ }
1002
1335
  this.servers.delete(name);
1336
+ if (
1337
+ !_authRetryUsed &&
1338
+ sourceConfig.headersHelper &&
1339
+ isMcpAuthenticationError(err)
1340
+ ) {
1341
+ // A helper is intentionally uncached. One authentication rejection may
1342
+ // mean its short-lived token expired between generation and initialize;
1343
+ // execute it once more and retry the connection exactly once.
1344
+ return this.connect(name, sourceConfig, true);
1345
+ }
1003
1346
  throw err;
1004
1347
  }
1005
1348
  }
@@ -1020,6 +1363,20 @@ export class MCPClient extends EventEmitter {
1020
1363
  if (entry.process) {
1021
1364
  entry.process.kill();
1022
1365
  }
1366
+ if (entry.socket) {
1367
+ entry._rejectPending?.(
1368
+ mcpTransportError(
1369
+ "CC_MCP_WS_CLIENT_DISCONNECT",
1370
+ `MCP WebSocket server "${name}" disconnected by client`,
1371
+ { transport: entry.transportKind, url: entry.config?.url },
1372
+ ),
1373
+ );
1374
+ try {
1375
+ entry.socket.close(1000, "client disconnect");
1376
+ } catch {
1377
+ entry.socket.terminate?.();
1378
+ }
1379
+ }
1023
1380
  if (entry._httpMessageStream) {
1024
1381
  entry._httpMessageStream.stopped = true;
1025
1382
  entry._httpMessageStream.controller?.abort();
@@ -1038,7 +1395,7 @@ export class MCPClient extends EventEmitter {
1038
1395
  entry.protocolVersion || this._protocolVersion,
1039
1396
  },
1040
1397
  });
1041
- } catch (_e) {
1398
+ } catch {
1042
1399
  // ignore — disconnect is best-effort
1043
1400
  }
1044
1401
  }
@@ -1070,6 +1427,7 @@ export class MCPClient extends EventEmitter {
1070
1427
  tools: entry.tools.length,
1071
1428
  toolsError: entry.toolsError || null,
1072
1429
  resources: entry.resources.length,
1430
+ resourceTemplates: (entry.resourceTemplates || []).length,
1073
1431
  prompts: (entry.prompts || []).length,
1074
1432
  serverInfo: entry.serverInfo || {},
1075
1433
  });
@@ -1117,12 +1475,20 @@ export class MCPClient extends EventEmitter {
1117
1475
  return this._callToolOnce(serverName, toolName, args);
1118
1476
  }
1119
1477
  if (
1120
- !this._reconnectors.has(serverName) ||
1121
- !isLikelyConnectionError(err)
1478
+ !this._reconnectors.has(serverName) &&
1479
+ !this.servers.get(serverName)?.config?.headersHelper
1122
1480
  ) {
1123
1481
  throw err;
1124
1482
  }
1125
- const reconnected = await this._tryReconnect(serverName);
1483
+ if (!isLikelyConnectionError(err)) {
1484
+ throw err;
1485
+ }
1486
+ const reconnected = await this._tryReconnect(serverName, {
1487
+ // The high-level tool operation owns the one reconnect/retry budget.
1488
+ // Its reconnect initialize must not recursively mint another auth
1489
+ // retry when the refreshed credential is also rejected.
1490
+ connectAuthRetryUsed: true,
1491
+ });
1126
1492
  if (!reconnected) throw err;
1127
1493
  return await this._callToolOnce(serverName, toolName, args);
1128
1494
  }
@@ -1194,19 +1560,21 @@ export class MCPClient extends EventEmitter {
1194
1560
  * parallel — a double connect would throw "already connected").
1195
1561
  * Resolves true on success, false on any failure (original error wins).
1196
1562
  */
1197
- _tryReconnect(name) {
1563
+ _tryReconnect(name, options = {}) {
1198
1564
  const inFlight = this._reconnecting.get(name);
1199
1565
  if (inFlight) return inFlight;
1200
1566
  const p = (async () => {
1201
1567
  try {
1202
- const fresh = await this._reconnectors.get(name)();
1568
+ const currentConfig = this.servers.get(name)?.config || null;
1569
+ const resolver = this._reconnectors.get(name);
1570
+ const fresh = resolver ? await resolver() : currentConfig;
1203
1571
  if (!fresh) return false;
1204
1572
  try {
1205
1573
  await this.disconnect(name);
1206
1574
  } catch {
1207
1575
  // entry may already be gone — connect() below is what matters
1208
1576
  }
1209
- await this.connect(name, fresh);
1577
+ await this.connect(name, fresh, options.connectAuthRetryUsed === true);
1210
1578
  this.emit("server-reconnected", { name, url: fresh.url || null });
1211
1579
  return true;
1212
1580
  } catch {
@@ -1252,6 +1620,73 @@ export class MCPClient extends EventEmitter {
1252
1620
  return result;
1253
1621
  }
1254
1622
 
1623
+ /** List optional URI templates advertised by one server or all servers. */
1624
+ listResourceTemplates(serverName) {
1625
+ if (serverName) {
1626
+ const entry = this.servers.get(serverName);
1627
+ if (!entry) throw new Error(`Server "${serverName}" not found`);
1628
+ return (entry.resourceTemplates || []).map((template) => ({
1629
+ ...template,
1630
+ server: serverName,
1631
+ }));
1632
+ }
1633
+ const all = [];
1634
+ for (const [name, entry] of this.servers) {
1635
+ for (const template of entry.resourceTemplates || []) {
1636
+ all.push({ ...template, server: name });
1637
+ }
1638
+ }
1639
+ return all;
1640
+ }
1641
+
1642
+ /** Subscribe to change notifications for a concrete resource URI. */
1643
+ async subscribeResource(serverName, uri) {
1644
+ const entry = this.servers.get(serverName);
1645
+ if (!entry) throw new Error(`Server "${serverName}" not found`);
1646
+ await this._sendRequest(serverName, "resources/subscribe", { uri });
1647
+ entry.resourceSubscriptions ||= new Set();
1648
+ entry.resourceSubscriptions.add(String(uri));
1649
+ return true;
1650
+ }
1651
+
1652
+ /** Remove a previously established resource subscription. */
1653
+ async unsubscribeResource(serverName, uri) {
1654
+ const entry = this.servers.get(serverName);
1655
+ if (!entry) throw new Error(`Server "${serverName}" not found`);
1656
+ await this._sendRequest(serverName, "resources/unsubscribe", { uri });
1657
+ entry.resourceSubscriptions?.delete(String(uri));
1658
+ return true;
1659
+ }
1660
+
1661
+ /** Set the optional server-to-client logging verbosity. */
1662
+ async setLoggingLevel(serverName, level) {
1663
+ const normalized = String(level || "").toLowerCase();
1664
+ const levels = new Set([
1665
+ "debug",
1666
+ "info",
1667
+ "notice",
1668
+ "warning",
1669
+ "error",
1670
+ "critical",
1671
+ "alert",
1672
+ "emergency",
1673
+ ]);
1674
+ if (!levels.has(normalized)) {
1675
+ throw new Error(`Invalid MCP logging level: ${level}`);
1676
+ }
1677
+ await this._sendRequest(serverName, "logging/setLevel", {
1678
+ level: normalized,
1679
+ });
1680
+ return normalized;
1681
+ }
1682
+
1683
+ /** Ask a server for optional argument/ref completion candidates. */
1684
+ async complete(serverName, ref, argument, context = undefined) {
1685
+ const params = { ref, argument };
1686
+ if (context !== undefined) params.context = context;
1687
+ return this._sendRequest(serverName, "completion/complete", params);
1688
+ }
1689
+
1255
1690
  /**
1256
1691
  * List prompts from a specific server or all servers. Each prompt is
1257
1692
  * annotated with its owning `server` (mirrors `listTools`).
@@ -1329,6 +1764,10 @@ export class MCPClient extends EventEmitter {
1329
1764
  return this._sendHttpRequest(serverName, method, params);
1330
1765
  }
1331
1766
 
1767
+ if (entry.socket) {
1768
+ return this._sendWebSocketRequest(serverName, method, params);
1769
+ }
1770
+
1332
1771
  return new Promise((resolve, reject) => {
1333
1772
  if (!entry.process) {
1334
1773
  return reject(new Error("Server not available"));
@@ -1360,8 +1799,15 @@ export class MCPClient extends EventEmitter {
1360
1799
  let timeout = null;
1361
1800
  if (!longRunning && timeoutMs > 0) {
1362
1801
  timeout = setTimeout(() => {
1363
- entry._pending.delete(id);
1364
- reject(new Error(`Request timeout: ${method}`));
1802
+ if (!entry._pending.delete(id)) return;
1803
+ const timeoutError = new Error(`Request timeout: ${method}`);
1804
+ this._sendTimeoutCancellation(
1805
+ serverName,
1806
+ method,
1807
+ id,
1808
+ timeoutError.message,
1809
+ );
1810
+ reject(timeoutError);
1365
1811
  }, timeoutMs);
1366
1812
  entry._pending.get(id).timeout = timeout;
1367
1813
  }
@@ -1376,6 +1822,73 @@ export class MCPClient extends EventEmitter {
1376
1822
  });
1377
1823
  }
1378
1824
 
1825
+ _sendWebSocketRequest(serverName, method, params) {
1826
+ const entry = this.servers.get(serverName);
1827
+ if (!entry?.socket || entry.socket.readyState !== 1) {
1828
+ return Promise.reject(
1829
+ mcpTransportError(
1830
+ "CC_MCP_WS_NOT_CONNECTED",
1831
+ `MCP WebSocket server "${serverName}" is not connected`,
1832
+ { transport: entry?.transportKind, url: entry?.config?.url },
1833
+ ),
1834
+ );
1835
+ }
1836
+
1837
+ return new Promise((resolve, reject) => {
1838
+ const id = this._nextId++;
1839
+ const message = JSON.stringify({ jsonrpc: "2.0", id, method, params });
1840
+ const longRunning = Boolean(entry.config?.longRunning);
1841
+ const timeoutMs = Number.isFinite(entry.config?.requestTimeoutMs)
1842
+ ? entry.config.requestTimeoutMs
1843
+ : STDIO_REQUEST_TIMEOUT_MS;
1844
+ let timeout = null;
1845
+ if (!longRunning && timeoutMs > 0) {
1846
+ timeout = setTimeout(() => {
1847
+ if (!entry._pending.delete(id)) return;
1848
+ const timeoutError = mcpTransportError(
1849
+ "CC_MCP_WS_REQUEST_TIMEOUT",
1850
+ `Request timeout: ${method} (WebSocket, no response in ${timeoutMs}ms)`,
1851
+ { transport: entry.transportKind, url: entry.config?.url },
1852
+ );
1853
+ this._sendTimeoutCancellation(
1854
+ serverName,
1855
+ method,
1856
+ id,
1857
+ timeoutError.message,
1858
+ );
1859
+ reject(timeoutError);
1860
+ }, timeoutMs);
1861
+ }
1862
+ entry._pending.set(id, { resolve, reject, timeout });
1863
+ entry.socket.send(message, (cause) => {
1864
+ if (!cause) return;
1865
+ if (timeout) clearTimeout(timeout);
1866
+ entry._pending.delete(id);
1867
+ reject(
1868
+ mcpTransportError(
1869
+ "CC_MCP_WS_SEND_FAILED",
1870
+ `WebSocket send failed for ${method}: ${cause.message}`,
1871
+ { transport: entry.transportKind, url: entry.config?.url },
1872
+ ),
1873
+ );
1874
+ });
1875
+ });
1876
+ }
1877
+
1878
+ _sendTimeoutCancellation(serverName, method, requestId, reason) {
1879
+ // The initialize handshake establishes the protocol/session needed for
1880
+ // later notifications, so it cannot itself be cancelled safely.
1881
+ if (method === "initialize") return;
1882
+ try {
1883
+ this._sendNotification(serverName, "notifications/cancelled", {
1884
+ requestId,
1885
+ reason,
1886
+ });
1887
+ } catch {
1888
+ // Cancellation is best-effort and must never replace the timeout error.
1889
+ }
1890
+ }
1891
+
1379
1892
  _sendNotification(serverName, method, params) {
1380
1893
  const entry = this.servers.get(serverName);
1381
1894
  if (!entry) return;
@@ -1386,6 +1899,16 @@ export class MCPClient extends EventEmitter {
1386
1899
  return;
1387
1900
  }
1388
1901
 
1902
+ if (entry.socket) {
1903
+ if (entry.socket.readyState === 1) {
1904
+ entry.socket.send(
1905
+ JSON.stringify({ jsonrpc: "2.0", method, params }),
1906
+ () => {},
1907
+ );
1908
+ }
1909
+ return;
1910
+ }
1911
+
1389
1912
  if (!entry.process) return;
1390
1913
 
1391
1914
  const message = JSON.stringify({
@@ -1420,6 +1943,7 @@ export class MCPClient extends EventEmitter {
1420
1943
  controller: null,
1421
1944
  lastEventId: null,
1422
1945
  retryMs: 1000,
1946
+ authFailures: 0,
1423
1947
  wake: null,
1424
1948
  promise: null,
1425
1949
  };
@@ -1431,19 +1955,22 @@ export class MCPClient extends EventEmitter {
1431
1955
  typeof AbortController === "function"
1432
1956
  ? new AbortController()
1433
1957
  : null;
1434
- const headers = {
1435
- Accept: "text/event-stream",
1436
- ...(entry.httpHeaders || {}),
1437
- ...(entry.httpSessionId
1438
- ? { "Mcp-Session-Id": entry.httpSessionId }
1439
- : {}),
1440
- "MCP-Protocol-Version":
1441
- entry.protocolVersion || this._protocolVersion,
1442
- ...(stream.lastEventId
1443
- ? { "Last-Event-ID": stream.lastEventId }
1444
- : {}),
1445
- };
1446
1958
  try {
1959
+ const resolvedHeaders = entry.config?.headersHelper
1960
+ ? await this._refreshConnectionHeaders(serverName, entry)
1961
+ : entry.httpHeaders || {};
1962
+ const headers = {
1963
+ Accept: "text/event-stream",
1964
+ ...resolvedHeaders,
1965
+ ...(entry.httpSessionId
1966
+ ? { "Mcp-Session-Id": entry.httpSessionId }
1967
+ : {}),
1968
+ "MCP-Protocol-Version":
1969
+ entry.protocolVersion || this._protocolVersion,
1970
+ ...(stream.lastEventId
1971
+ ? { "Last-Event-ID": stream.lastEventId }
1972
+ : {}),
1973
+ };
1447
1974
  const response = await _deps.fetch(entry.httpUrl, {
1448
1975
  method: "GET",
1449
1976
  headers,
@@ -1452,11 +1979,24 @@ export class MCPClient extends EventEmitter {
1452
1979
  : {}),
1453
1980
  });
1454
1981
  if (response.status === 405) return;
1982
+ if (response.status === 401 || response.status === 403) {
1983
+ if (entry.config?.headersHelper && stream.authFailures === 0) {
1984
+ stream.authFailures = 1;
1985
+ continue;
1986
+ }
1987
+ this.emit("server-stream-error", {
1988
+ name: serverName,
1989
+ code: "CC_MCP_AUTH_RETRY_EXHAUSTED",
1990
+ error: `MCP HTTP message stream authentication failed (HTTP ${response.status})`,
1991
+ });
1992
+ return;
1993
+ }
1455
1994
  if (!response.ok) {
1456
1995
  throw new Error(
1457
1996
  `MCP HTTP message stream returned ${response.status}`,
1458
1997
  );
1459
1998
  }
1999
+ stream.authFailures = 0;
1460
2000
  const contentType = response.headers?.get
1461
2001
  ? String(response.headers.get("content-type") || "").toLowerCase()
1462
2002
  : "";
@@ -1475,6 +2015,9 @@ export class MCPClient extends EventEmitter {
1475
2015
  name: serverName,
1476
2016
  error: error?.message || String(error),
1477
2017
  });
2018
+ if (String(error?.code || "").startsWith("CC_MCP_HEADERS_HELPER")) {
2019
+ return;
2020
+ }
1478
2021
  } finally {
1479
2022
  stream.controller = null;
1480
2023
  }
@@ -1539,13 +2082,17 @@ export class MCPClient extends EventEmitter {
1539
2082
  : HTTP_REQUEST_TIMEOUT_MS;
1540
2083
  let controller = null;
1541
2084
  let timer = null;
2085
+ const timeoutMessage = `Request timeout: ${method} (HTTP, no response in ${timeoutMs}ms)`;
1542
2086
  if (
1543
2087
  !longRunning &&
1544
2088
  timeoutMs > 0 &&
1545
2089
  typeof AbortController === "function"
1546
2090
  ) {
1547
2091
  controller = new AbortController();
1548
- timer = setTimeout(() => controller.abort(), timeoutMs);
2092
+ timer = setTimeout(() => {
2093
+ controller.abort();
2094
+ this._sendTimeoutCancellation(serverName, method, id, timeoutMessage);
2095
+ }, timeoutMs);
1549
2096
  }
1550
2097
 
1551
2098
  try {
@@ -1567,18 +2114,37 @@ export class MCPClient extends EventEmitter {
1567
2114
  }
1568
2115
 
1569
2116
  if (!response.ok) {
1570
- const text =
1571
- typeof response.text === "function" ? await response.text() : "";
2117
+ // Authentication bodies frequently echo token/debug material. When a
2118
+ // dynamic helper is configured, any error body may reflect its opaque
2119
+ // header values; structured HTTP status is sufficient and cannot leak
2120
+ // freshly generated credentials into logs or exceptions.
2121
+ const suppressBody =
2122
+ Boolean(entry.config?.headersHelper) ||
2123
+ response.status === 401 ||
2124
+ response.status === 403;
2125
+ const text = suppressBody
2126
+ ? ""
2127
+ : typeof response.text === "function"
2128
+ ? await response.text()
2129
+ : "";
1572
2130
  const detail = text ? `: ${text.slice(0, 200)}` : "";
1573
2131
  // 404 usually means a wrong/stale server URL — name it and point at the
1574
2132
  // MCP config (Claude-Code 2.1.191: "HTTP 404 errors now show the URL and
1575
2133
  // point to your MCP config") instead of a bare "HTTP 404".
1576
2134
  if (response.status === 404) {
1577
2135
  throw new Error(
1578
- `HTTP 404${detail} — ${entry.httpUrl} returned Not Found; check this server's "url" in your MCP config`,
2136
+ `HTTP 404${detail} — ${redactMcpUrl(entry.httpUrl)} returned Not Found; check this server's "url" in your MCP config`,
1579
2137
  );
1580
2138
  }
1581
- throw new Error(`HTTP ${response.status}${detail}`);
2139
+ throw mcpTransportError(
2140
+ "CC_MCP_HTTP_STATUS",
2141
+ `HTTP ${response.status}${detail}`,
2142
+ {
2143
+ transport: entry.transportKind,
2144
+ url: entry.config?.url,
2145
+ status: response.status,
2146
+ },
2147
+ );
1582
2148
  }
1583
2149
 
1584
2150
  const contentType = response.headers?.get
@@ -1609,9 +2175,7 @@ export class MCPClient extends EventEmitter {
1609
2175
  return envelope.result;
1610
2176
  } catch (err) {
1611
2177
  if (controller && controller.signal.aborted) {
1612
- throw new Error(
1613
- `Request timeout: ${method} (HTTP, no response in ${timeoutMs}ms)`,
1614
- );
2178
+ throw new Error(timeoutMessage);
1615
2179
  }
1616
2180
  throw err;
1617
2181
  } finally {
@@ -1641,7 +2205,7 @@ export class MCPClient extends EventEmitter {
1641
2205
  body,
1642
2206
  });
1643
2207
  if (p && typeof p.catch === "function") p.catch(() => {});
1644
- } catch (_e) {
2208
+ } catch {
1645
2209
  // ignore
1646
2210
  }
1647
2211
  }
@@ -1739,6 +2303,20 @@ export class MCPClient extends EventEmitter {
1739
2303
  this._refreshServerList(serverName, "tools");
1740
2304
  } else if (msg.method === "notifications/resources/list_changed") {
1741
2305
  this._refreshServerList(serverName, "resources");
2306
+ this._refreshServerList(serverName, "resourceTemplates");
2307
+ } else if (msg.method === "notifications/resources/updated") {
2308
+ this.emit("resource-updated", {
2309
+ server: serverName,
2310
+ uri: msg.params?.uri || null,
2311
+ params: msg.params || {},
2312
+ });
2313
+ } else if (msg.method === "notifications/message") {
2314
+ this.emit("log-message", {
2315
+ server: serverName,
2316
+ level: msg.params?.level || "info",
2317
+ logger: msg.params?.logger || null,
2318
+ data: msg.params?.data,
2319
+ });
1742
2320
  } else if (msg.method === "notifications/elicitation/complete") {
1743
2321
  this._handleElicitationComplete(serverName, msg.params || {});
1744
2322
  }
@@ -1770,10 +2348,19 @@ export class MCPClient extends EventEmitter {
1770
2348
  do {
1771
2349
  flags[`${kind}Dirty`] = false;
1772
2350
  try {
1773
- const method = kind === "tools" ? "tools/list" : "resources/list";
2351
+ const method =
2352
+ kind === "tools"
2353
+ ? "tools/list"
2354
+ : kind === "resourceTemplates"
2355
+ ? "resources/templates/list"
2356
+ : "resources/list";
1774
2357
  const result = await this._sendRequest(serverName, method, {});
1775
2358
  const list =
1776
- (kind === "tools" ? result?.tools : result?.resources) || [];
2359
+ (kind === "tools"
2360
+ ? result?.tools
2361
+ : kind === "resourceTemplates"
2362
+ ? result?.resourceTemplates
2363
+ : result?.resources) || [];
1777
2364
  entry[kind] = list;
1778
2365
  if (kind === "tools") entry.toolsError = null;
1779
2366
  this.emit(`${kind}-changed`, {
@@ -1860,6 +2447,12 @@ export class MCPClient extends EventEmitter {
1860
2447
  }
1861
2448
  return;
1862
2449
  }
2450
+ if (entry.socket) {
2451
+ if (entry.socket.readyState === 1) {
2452
+ entry.socket.send(JSON.stringify(envelope), () => {});
2453
+ }
2454
+ return;
2455
+ }
1863
2456
  if (!entry.process) return;
1864
2457
  const message = JSON.stringify(envelope);
1865
2458
  try {
@@ -2054,6 +2647,12 @@ export class MCPServerConfig {
2054
2647
  auto_connect INTEGER DEFAULT 0,
2055
2648
  url TEXT,
2056
2649
  transport TEXT DEFAULT 'stdio',
2650
+ headers TEXT DEFAULT '{}',
2651
+ headers_helper TEXT,
2652
+ config_scope TEXT DEFAULT 'user',
2653
+ config_source TEXT,
2654
+ project_path TEXT,
2655
+ display_name TEXT,
2057
2656
  created_at TEXT DEFAULT (datetime('now')),
2058
2657
  updated_at TEXT DEFAULT (datetime('now'))
2059
2658
  )
@@ -2081,7 +2680,29 @@ export class MCPServerConfig {
2081
2680
  "ALTER TABLE mcp_servers ADD COLUMN transport TEXT DEFAULT 'stdio'",
2082
2681
  );
2083
2682
  }
2084
- } catch (_e) {
2683
+ if (!cols.has("headers")) {
2684
+ this.db.exec(
2685
+ "ALTER TABLE mcp_servers ADD COLUMN headers TEXT DEFAULT '{}'",
2686
+ );
2687
+ }
2688
+ if (!cols.has("headers_helper")) {
2689
+ this.db.exec("ALTER TABLE mcp_servers ADD COLUMN headers_helper TEXT");
2690
+ }
2691
+ if (!cols.has("config_scope")) {
2692
+ this.db.exec(
2693
+ "ALTER TABLE mcp_servers ADD COLUMN config_scope TEXT DEFAULT 'user'",
2694
+ );
2695
+ }
2696
+ if (!cols.has("config_source")) {
2697
+ this.db.exec("ALTER TABLE mcp_servers ADD COLUMN config_source TEXT");
2698
+ }
2699
+ if (!cols.has("project_path")) {
2700
+ this.db.exec("ALTER TABLE mcp_servers ADD COLUMN project_path TEXT");
2701
+ }
2702
+ if (!cols.has("display_name")) {
2703
+ this.db.exec("ALTER TABLE mcp_servers ADD COLUMN display_name TEXT");
2704
+ }
2705
+ } catch {
2085
2706
  // Best-effort; non-SQLite mocks silently skip.
2086
2707
  }
2087
2708
  }
@@ -2093,31 +2714,81 @@ export class MCPServerConfig {
2093
2714
  if (!url && !config.command) {
2094
2715
  throw new Error("MCP server config requires either command or url");
2095
2716
  }
2717
+ const configScope = normalizeMcpConfigScope(config.configScope || "user");
2718
+ if (configScope === "managed" && config.allowManagedWrite !== true) {
2719
+ throw new Error(
2720
+ "Managed MCP configuration is read-only; provision it through managed settings",
2721
+ );
2722
+ }
2723
+ const projectPath =
2724
+ configScope === "local" || configScope === "project"
2725
+ ? String(config.projectPath || process.cwd())
2726
+ : null;
2727
+ const configSource =
2728
+ config.configSource ||
2729
+ (configScope === "managed"
2730
+ ? "managed-settings"
2731
+ : configScope === "user"
2732
+ ? "user-database"
2733
+ : `${configScope}:${projectPath}`);
2734
+ const headersHelper =
2735
+ typeof config.headersHelper === "string" && config.headersHelper.trim()
2736
+ ? config.headersHelper
2737
+ : null;
2738
+ // The legacy table uses `name` as its primary key. Encode workspace-bound
2739
+ // identities in that key so `foo` can coexist at local/project/user scope
2740
+ // while retaining the old key for user rows and backwards compatibility.
2741
+ const storageName =
2742
+ configScope === "user"
2743
+ ? name
2744
+ : `${configScope}:${Buffer.from(projectPath || "managed", "utf8").toString("base64url")}:${name}`;
2096
2745
  this.db
2097
2746
  .prepare(
2098
- "INSERT OR REPLACE INTO mcp_servers (name, command, args, env, auto_connect, url, transport, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))",
2747
+ "INSERT OR REPLACE INTO mcp_servers (name, command, args, env, auto_connect, url, transport, headers, headers_helper, config_scope, config_source, project_path, display_name, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))",
2099
2748
  )
2100
2749
  .run(
2101
- name,
2750
+ storageName,
2102
2751
  config.command || null,
2103
2752
  JSON.stringify(config.args || []),
2104
2753
  JSON.stringify(config.env || {}),
2105
2754
  config.autoConnect ? 1 : 0,
2106
2755
  url,
2107
2756
  transport,
2757
+ JSON.stringify(config.headers || {}),
2758
+ headersHelper,
2759
+ configScope,
2760
+ configSource,
2761
+ projectPath,
2762
+ name,
2108
2763
  );
2109
2764
  }
2110
2765
 
2111
- remove(name) {
2766
+ remove(name, options = {}) {
2767
+ const current = this.get(name, options);
2768
+ if (!current) return false;
2769
+ if (
2770
+ options.scope &&
2771
+ current.configScope !== normalizeMcpConfigScope(options.scope)
2772
+ ) {
2773
+ return false;
2774
+ }
2775
+ if (
2776
+ current.configScope === "managed" &&
2777
+ options.allowManagedWrite !== true
2778
+ ) {
2779
+ throw new Error(
2780
+ "Managed MCP configuration is read-only; change the managed settings source",
2781
+ );
2782
+ }
2112
2783
  const result = this.db
2113
2784
  .prepare("DELETE FROM mcp_servers WHERE name = ?")
2114
- .run(name);
2785
+ .run(current._storageName || name);
2115
2786
  return result.changes > 0;
2116
2787
  }
2117
2788
 
2118
2789
  _rowToConfig(row) {
2119
- return {
2120
- name: row.name,
2790
+ const config = {
2791
+ name: row.display_name || row.name,
2121
2792
  command: row.command || null,
2122
2793
  // safeJsonParse, not bare JSON.parse: list()/getAutoConnect() map every
2123
2794
  // row through here, so one corrupt cell must not take down the whole
@@ -2128,28 +2799,98 @@ export class MCPServerConfig {
2128
2799
  url: row.url || null,
2129
2800
  transport:
2130
2801
  row.transport || (row.url ? inferTransport({ url: row.url }) : "stdio"),
2802
+ headers: safeJsonParse(row.headers, {}),
2803
+ ...(typeof row.headers_helper === "string" && row.headers_helper.trim()
2804
+ ? { headersHelper: row.headers_helper }
2805
+ : {}),
2806
+ configScope: normalizeMcpConfigScope(row.config_scope || "user"),
2807
+ configSource:
2808
+ row.config_source ||
2809
+ (row.config_scope === "managed"
2810
+ ? "managed-settings"
2811
+ : "legacy-database"),
2812
+ projectPath: row.project_path || null,
2131
2813
  };
2814
+ Object.defineProperty(config, "_storageName", {
2815
+ value: row.name,
2816
+ enumerable: false,
2817
+ });
2818
+ return config;
2132
2819
  }
2133
2820
 
2134
- get(name) {
2135
- const row = this.db
2136
- .prepare("SELECT * FROM mcp_servers WHERE name = ?")
2137
- .get(name);
2138
- if (!row) return null;
2139
- return this._rowToConfig(row);
2821
+ get(name, options = {}) {
2822
+ return this.list(options).find((config) => config.name === name) || null;
2140
2823
  }
2141
2824
 
2142
- list() {
2825
+ list(options = {}) {
2143
2826
  const rows = this.db
2144
2827
  .prepare("SELECT * FROM mcp_servers ORDER BY name")
2145
2828
  .all();
2146
- return rows.map((row) => this._rowToConfig(row));
2829
+ const visible = rows
2830
+ .map((row) => this._rowToConfig(row))
2831
+ .filter((config) => this._isVisible(config, options));
2832
+ return options.allScopes === true || options.scope
2833
+ ? visible
2834
+ : this._effectiveByName(visible);
2147
2835
  }
2148
2836
 
2149
- getAutoConnect() {
2837
+ getAutoConnect(options = {}) {
2150
2838
  const rows = this.db
2151
2839
  .prepare("SELECT * FROM mcp_servers WHERE auto_connect = ? ORDER BY name")
2152
2840
  .all(1);
2153
- return rows.map((row) => this._rowToConfig(row));
2841
+ const visible = rows
2842
+ .map((row) => this._rowToConfig(row))
2843
+ .filter((config) => this._isVisible(config, options));
2844
+ return options.allScopes === true || options.scope
2845
+ ? visible
2846
+ : this._effectiveByName(visible);
2847
+ }
2848
+
2849
+ _effectiveByName(configs) {
2850
+ const priority = { managed: 4, local: 3, project: 2, user: 1 };
2851
+ const selected = new Map();
2852
+ for (const config of configs) {
2853
+ const current = selected.get(config.name);
2854
+ if (
2855
+ !current ||
2856
+ (priority[config.configScope] || 0) >
2857
+ (priority[current.configScope] || 0)
2858
+ ) {
2859
+ selected.set(config.name, config);
2860
+ }
2861
+ }
2862
+ return [...selected.values()].sort((a, b) => a.name.localeCompare(b.name));
2863
+ }
2864
+
2865
+ _isVisible(config, options = {}) {
2866
+ if (
2867
+ options.scope &&
2868
+ config.configScope !== normalizeMcpConfigScope(options.scope)
2869
+ ) {
2870
+ return false;
2871
+ }
2872
+ if (options.allScopes === true) return true;
2873
+ if (config.configScope !== "local" && config.configScope !== "project") {
2874
+ return true;
2875
+ }
2876
+ if (!config.projectPath) return false;
2877
+ const cwd = String(options.cwd || process.cwd());
2878
+ // Avoid importing path solely for an authority comparison: URL
2879
+ // normalization gives stable case-insensitive file URLs on Windows and
2880
+ // resolves dot segments on every supported platform.
2881
+ try {
2882
+ const normalizeAuthority = (value) => {
2883
+ const href = pathToFileURL(value).href;
2884
+ return process.platform === "win32" ? href.toLowerCase() : href;
2885
+ };
2886
+ const expected = normalizeAuthority(config.projectPath);
2887
+ const actual = normalizeAuthority(cwd);
2888
+ return (
2889
+ actual === expected ||
2890
+ actual.startsWith(`${expected.replace(/\/$/, "")}/`)
2891
+ );
2892
+ } catch {
2893
+ return false;
2894
+ }
2154
2895
  }
2155
2896
  }