chainlesschain 0.162.78 → 0.162.80

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 (194) hide show
  1. package/README.md +37 -1
  2. package/bin/chainlesschain.js +20 -1
  3. package/package.json +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-BhKMd38k.js → AIOps-CwebRiRI.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-BzhKY5C_.js → ActionButton-C7h2xsW3.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-CATIz2Jc.js → Analytics-BRdOQzzK.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-eCx64YWg.js → AppLayout-D_i-Jbsu.js} +5 -5
  8. package/src/assets/web-panel/assets/{Audit-CGOHfCHj.js → Audit-AgCF_nLK.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Dyr6R0Ra.js → Backup-Be9up1Uo.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CVhBu7NZ.js → BaseInput-2j-7gyTU.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DOCCKp2k.js → Chat-ZyYadHdk.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DcXCCnjN.js → ChatBubbleRenderer-CwnbmcAg.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-3yjnENud.js → Checkbox-Di0bejSO.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-D06sn8kB.js → Codegen-BgEwqrVh.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-Bjn5vFES.js → Col-FIduoerd.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-BYHpQHmf.js → Community-Bf7olKXg.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-MKRmnUDQ.js → Compact-CsjIF6B3.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CVtPe8dh.js → Compliance-CQchRaQh.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BRu5M3dv.js → Cowork-gJzDgX9E.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-D_7eU5Ut.js → Cron-CDjeagXb.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BFyVp_e9.js → Crosschain-D4UE5bt0.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-DRoG7638.js → DID-OwvsxTzD.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-NfM_v-Oe.js → Dashboard-CbmOlZ1l.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-CGN1Ksu0.js → Dropdown-B6tBUMev.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DhsY_z_a.js → EmailListRenderer-DnM2-O3n.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-vW_VsKUG.js → FamilyGuardDashboard-DPD9znJH.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-D2ob_c7h.js → Federation-DjUj87Vr.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Cpem15Pr.js → FormItemContext-DyPgrKLf.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-P6XfmXwT.js → GenericCardRenderer-D3fmbO1W.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-CfSeec1R.js → Git-IvwC_R2h.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-4ipIpqNl.js → Governance-CTVEvxpW.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CiQY_P9Y.js → Inference-DQlp7Rf1.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-B3Qem78R.js → KnowledgeGraph-Cg8pVh5j.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-De2zWVy6.js → Logs-D7kLXyaK.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-CTNu4c1A.js → Marketplace-QOj6g-oT.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-Xx25EA2f.js → McpTools-PO9yrTD6.js} +5 -5
  37. package/src/assets/web-panel/assets/{Memory-YzzCCrch.js → Memory-B1FwSFmt.js} +2 -2
  38. package/src/assets/web-panel/assets/{MobileBridge-CJZY98f0.js → MobileBridge-CYiQSoKu.js} +3 -3
  39. package/src/assets/web-panel/assets/{MobileProjects-Dw7yl4KN.js → MobileProjects-BThUga4r.js} +1 -1
  40. package/src/assets/web-panel/assets/{Mtc-D7TzGJhH.js → Mtc-B4pMzmr7.js} +4 -4
  41. package/src/assets/web-panel/assets/{MtcAudit-THyGhf0h.js → MtcAudit-D6-KGsvR.js} +6 -6
  42. package/src/assets/web-panel/assets/{Multisig-RVxuGPUR.js → Multisig-BlZigJMj.js} +3 -3
  43. package/src/assets/web-panel/assets/{NLProgramming-DK7TCzKF.js → NLProgramming-Dhy92OLw.js} +1 -1
  44. package/src/assets/web-panel/assets/{Notes-BaOU0psj.js → Notes-DOSv2Lb0.js} +4 -4
  45. package/src/assets/web-panel/assets/{NotificationSettings-BMivJy85.js → NotificationSettings-FRWsHQfi.js} +1 -1
  46. package/src/assets/web-panel/assets/{OrderTableRenderer-BZOiY8Yw.js → OrderTableRenderer-xRSRWRZg.js} +1 -1
  47. package/src/assets/web-panel/assets/{Organization-zVRtW1n_.js → Organization-DApwYY-B.js} +4 -4
  48. package/src/assets/web-panel/assets/{Overflow-C0YLldFH.js → Overflow-DyJQHxIY.js} +1 -1
  49. package/src/assets/web-panel/assets/{P2P-Xxgzghqp.js → P2P-NKTPd_Bn.js} +2 -2
  50. package/src/assets/web-panel/assets/{PdhVaultBrowser-DH_LO13b.js → PdhVaultBrowser-DCgGt1BE.js} +5 -5
  51. package/src/assets/web-panel/assets/{Permissions-CvAd1VBw.js → Permissions-BX9MCb2z.js} +4 -4
  52. package/src/assets/web-panel/assets/{PersonalDataHub-CWQGgCAK.js → PersonalDataHub-Bcg4az84.js} +3 -3
  53. package/src/assets/web-panel/assets/{Pipeline-BNAoh-Lb.js → Pipeline-DjsbfwbG.js} +1 -1
  54. package/src/assets/web-panel/assets/{Privacy-CNO5pFq-.js → Privacy-kDWl06vo.js} +1 -1
  55. package/src/assets/web-panel/assets/{ProjectInit-WaVVDsm3.js → ProjectInit-yCe-Imkv.js} +2 -2
  56. package/src/assets/web-panel/assets/{ProjectSettings-D1WfkuJ3.js → ProjectSettings-tVcxlGJ5.js} +2 -2
  57. package/src/assets/web-panel/assets/{Projects-BzjvJYMW.js → Projects-DGL4Za4o.js} +1 -1
  58. package/src/assets/web-panel/assets/{Providers-IOOJ4_wy.js → Providers-CciGxskW.js} +1 -1
  59. package/src/assets/web-panel/assets/{QuickAsk-ChHZqVZy.js → QuickAsk-It17rT4F.js} +1 -1
  60. package/src/assets/web-panel/assets/{Recommend-CSiW6Qv9.js → Recommend-CixKdpBl.js} +1 -1
  61. package/src/assets/web-panel/assets/{Reputation-BQe0rkfF.js → Reputation-LB0_D2lx.js} +1 -1
  62. package/src/assets/web-panel/assets/{Row-Dem0Wxxb.js → Row-BoiDd-zb.js} +1 -1
  63. package/src/assets/web-panel/assets/{RssFeed-pBY5G41C.js → RssFeed-yA5FdTgh.js} +2 -2
  64. package/src/assets/web-panel/assets/{Search-CtRepO6B.js → Search-xYldUFeJ.js} +1 -1
  65. package/src/assets/web-panel/assets/{Security-nrSlKpWq.js → Security-BtRMnkFm.js} +4 -4
  66. package/src/assets/web-panel/assets/{Services-DeaDBASi.js → Services-CNKTgE2v.js} +2 -2
  67. package/src/assets/web-panel/assets/{Skeleton-Cz9R-Wjb.js → Skeleton-B9goiUo_.js} +1 -1
  68. package/src/assets/web-panel/assets/{Skills-B3U-XLH3.js → Skills-CUf2Z5Ge.js} +1 -1
  69. package/src/assets/web-panel/assets/{Sla-Bu46dIA_.js → Sla-TPga8c2I.js} +1 -1
  70. package/src/assets/web-panel/assets/{SpeechSettings-C9Z0V0pk.js → SpeechSettings-B8zrmLP6.js} +1 -1
  71. package/src/assets/web-panel/assets/{SyncSettings-Ctj9KHHr.js → SyncSettings-DGyAbZ84.js} +2 -2
  72. package/src/assets/web-panel/assets/{Tasks-D4upQgR_.js → Tasks-DPeVrQNM.js} +1 -1
  73. package/src/assets/web-panel/assets/{Templates-JHsPGU_c.js → Templates-CNLco6pc.js} +1 -1
  74. package/src/assets/web-panel/assets/{Tenant-uoaQL3fB.js → Tenant-CL9Eczo8.js} +1 -1
  75. package/src/assets/web-panel/assets/Terminal-DGLvbp97.js +3 -0
  76. package/src/assets/web-panel/assets/{TimelineRenderer-BTicmSAV.js → TimelineRenderer-Cv0LxMwd.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tokens-Bp3BUe2K.js → Tokens-DLhHgtcS.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trigger-CgoISw5d.js → Trigger-DzDaE-An.js} +1 -1
  79. package/src/assets/web-panel/assets/{Trust-CC29awNT.js → Trust-CRh-fhYe.js} +1 -1
  80. package/src/assets/web-panel/assets/{UkeySign-CB1SB6Nc.js → UkeySign-_xBJ16UC.js} +1 -1
  81. package/src/assets/web-panel/assets/{VideoEditing-D7vptDUg.js → VideoEditing-Z5m_edIa.js} +1 -1
  82. package/src/assets/web-panel/assets/{Wallet-BWfjzF7p.js → Wallet-DgmchNit.js} +4 -4
  83. package/src/assets/web-panel/assets/{WebAuthn-Dzz5OnPc.js → WebAuthn-BxuKxjuf.js} +5 -5
  84. package/src/assets/web-panel/assets/{WorkflowEditor-CiDeVmsG.js → WorkflowEditor-luJ180aM.js} +1 -1
  85. package/src/assets/web-panel/assets/{chat-DQbciNb5.js → chat-DzglnTps.js} +1 -1
  86. package/src/assets/web-panel/assets/{colors-DcLbPJzb.js → colors-DelLNoxZ.js} +1 -1
  87. package/src/assets/web-panel/assets/{compact-item-CvYrR3rc.js → compact-item-BZaabUge.js} +1 -1
  88. package/src/assets/web-panel/assets/{createContext-BR4P7Rgm.js → createContext-DqTSTjmk.js} +1 -1
  89. package/src/assets/web-panel/assets/devWarning-CJLMPKYL.js +1 -0
  90. package/src/assets/web-panel/assets/{hasIn-IQ88RNRJ.js → hasIn-CAeHUQj2.js} +1 -1
  91. package/src/assets/web-panel/assets/index-7CJalvEf.js +1 -0
  92. package/src/assets/web-panel/assets/{index-Bw0Dm_P6.js → index-7FxBHcH8.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-Db5LFFCN.js → index-BAcpfWwI.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B5W1vQHV.js → index-BAfdWN9t.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BUTN1VlO.js → index-BR-DF81e.js} +3 -3
  96. package/src/assets/web-panel/assets/{index-BmPuR0aA.js → index-BTbN0V4A.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-D8OJdOc_.js → index-Bv2Tp7kz.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BhkZZXtI.js → index-BzLgm3Jm.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CzERBV9P.js → index-CBZPDGTg.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-UbB2IcFR.js → index-CBtnHlYF.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-eKd1n8pw.js → index-CDw1am9U.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-JqOP7puJ.js → index-CI5cynRw.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BiMlLIZ-.js → index-CKZQVcH1.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CgP5aQmA.js → index-CV4FisuU.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BH2RT15D.js → index-CaSLz8-6.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DgaCUxpi.js → index-Cj7oeTxA.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DdQBxvpt.js → index-Clq1OP4B.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Bl1TSbTE.js → index-CppTZ4SW.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BQXs-5db.js → index-D-QuIaEh.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DGwa8mnJ.js → index-D-lVDXUg.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DHIp5msb.js → index-DE4-6oHW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CCO8yc1h.js → index-DM7xncnU.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-b6FjzfoJ.js → index-DRt2lx0X.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Bo7HAK6G.js → index-DSATjRyg.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Dpmnk2qv.js → index-DU9QWJO5.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Mn8_ryOe.js → index-DXvcxNo5.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Dox9vEhP.js → index-DhMSUhbW.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-bRT7u-51.js → index-Dk1R9vFq.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BHxJnExB.js → index-DrSuq6t6.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CPOupQSX.js → index-DtfTElxo.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BxY0ozve.js → index-KeadEGaZ.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BvQpTO67.js → index-NZBXGj64.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CiOZ_Whh.js → index-OGKhEFZZ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CeCWyiFl.js → index-RZ23Wlp8.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BVb6RI7f.js → index-WzAdJ0PX.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-Cm74AosZ.js → index-Xo2WWPZ4.js} +1 -1
  127. package/src/assets/web-panel/assets/index-_hLbeSOT.js +1 -0
  128. package/src/assets/web-panel/assets/{index-CE2mqX8w.js → index-kJ30C4m8.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Bu8931Yi.js → index-vLR-ssxc.js} +1 -1
  130. package/src/assets/web-panel/assets/{initDefaultProps-C0arzCLE.js → initDefaultProps-BiHvIjo1.js} +1 -1
  131. package/src/assets/web-panel/assets/{motion-C1K6JxwD.js → motion-COD0OBOe.js} +1 -1
  132. package/src/assets/web-panel/assets/{move-DREsRLHj.js → move-DJNLMhIj.js} +1 -1
  133. package/src/assets/web-panel/assets/{omit-BtPS3EDq.js → omit-4qrDRhlN.js} +1 -1
  134. package/src/assets/web-panel/assets/{pickAttrs-BPz6tHoT.js → pickAttrs-3jv8tAgW.js} +1 -1
  135. package/src/assets/web-panel/assets/{placementArrow-B0CR_CSI.js → placementArrow-N1UVUOH_.js} +1 -1
  136. package/src/assets/web-panel/assets/{responsiveObserve-Ch2ojiNn.js → responsiveObserve-D67_gjCH.js} +1 -1
  137. package/src/assets/web-panel/assets/{slide-9qU9vOhj.js → slide-DiDh7_u4.js} +1 -1
  138. package/src/assets/web-panel/assets/{statusUtils-Cr4fICjV.js → statusUtils-Dzz3tSiz.js} +1 -1
  139. package/src/assets/web-panel/assets/{styleChecker-Cor2-FwV.js → styleChecker-L-tgt7xx.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFlexGapSupport-BINo_rNH.js → useFlexGapSupport-vAgElNal.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFs-Dm1tDNYC.js → useFs-af0c_HYI.js} +1 -1
  142. package/src/assets/web-panel/assets/{usePersonalDataHub-__JgBEkX.js → usePersonalDataHub-V9U2Mbny.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-1hQKpRgP.js → vnode-C7zS_LLr.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-C1EY9X2J.js → zoom-DdXBDemd.js} +1 -1
  145. package/src/assets/web-panel/index.html +1 -1
  146. package/src/commands/audit.js +4 -3
  147. package/src/commands/automation.js +6 -14
  148. package/src/commands/bi.js +10 -9
  149. package/src/commands/codegen.js +5 -13
  150. package/src/commands/dao.js +8 -6
  151. package/src/commands/dbevo.js +13 -14
  152. package/src/commands/economy.js +3 -2
  153. package/src/commands/evolution.js +3 -2
  154. package/src/commands/federation.js +4 -3
  155. package/src/commands/governance.js +9 -4
  156. package/src/commands/hardening.js +5 -4
  157. package/src/commands/incentive.js +6 -5
  158. package/src/commands/kg.js +17 -10
  159. package/src/commands/lowcode.js +23 -11
  160. package/src/commands/marketplace.js +4 -3
  161. package/src/commands/mcp.js +17 -5
  162. package/src/commands/ops.js +9 -4
  163. package/src/commands/recommend.js +7 -5
  164. package/src/commands/scim.js +3 -2
  165. package/src/commands/session.js +9 -6
  166. package/src/commands/social.js +4 -3
  167. package/src/commands/sync.js +3 -2
  168. package/src/commands/tenant.js +11 -6
  169. package/src/commands/zkp.js +8 -9
  170. package/src/gateways/ws/ws-agent-handler.js +12 -3
  171. package/src/gateways/ws/ws-server.js +6 -0
  172. package/src/harness/background-task-manager.js +44 -18
  173. package/src/harness/mcp-client.js +125 -46
  174. package/src/lib/chat-core.js +209 -107
  175. package/src/lib/claude-code-bridge.js +13 -1
  176. package/src/lib/dao-governance.js +3 -3
  177. package/src/lib/downloader.js +82 -25
  178. package/src/lib/headless-config-command.js +62 -0
  179. package/src/lib/json-schema-output.js +55 -11
  180. package/src/lib/mcp-oauth.js +110 -21
  181. package/src/lib/mcp-serve.js +70 -11
  182. package/src/lib/multisig-runtime.js +22 -3
  183. package/src/lib/parse-json-option.js +35 -0
  184. package/src/lib/parse-number-option.js +27 -0
  185. package/src/lib/runnable-provider.js +216 -0
  186. package/src/repl/agent-repl.js +76 -17
  187. package/src/repl/config-summary.js +66 -0
  188. package/src/runtime/agent-core.js +210 -37
  189. package/src/runtime/headless-runner.js +49 -1
  190. package/src/runtime/headless-stream.js +34 -0
  191. package/src/assets/web-panel/assets/Terminal-CWRWr8bq.js +0 -3
  192. package/src/assets/web-panel/assets/devWarning-CnV02N63.js +0 -1
  193. package/src/assets/web-panel/assets/index-DJ2gkaIH.js +0 -1
  194. package/src/assets/web-panel/assets/index-Dvm_-AOi.js +0 -1
@@ -33,6 +33,14 @@ export const ServerState = {
33
33
  /** Transport kinds that carry a URL (no stdio process). */
34
34
  const URL_TRANSPORTS = new Set(["http", "https", "sse", "ws", "wss"]);
35
35
 
36
+ /**
37
+ * Default per-call timeout for HTTP MCP requests, mirroring the 30s stdio
38
+ * timeout so a hung/dead HTTP server can't block a request forever. Servers
39
+ * flagged `longRunning` (e.g. the IDE bridge, whose openDiff blocks on human
40
+ * review) are exempt; override per server with `config.requestTimeoutMs`.
41
+ */
42
+ const HTTP_REQUEST_TIMEOUT_MS = 30000;
43
+
36
44
  /**
37
45
  * Infer the transport kind for a server config. Falls back to "stdio".
38
46
  * Prefers an explicit `transport` field; otherwise derives from URL scheme
@@ -218,13 +226,32 @@ export class MCPClient extends EventEmitter {
218
226
  this.emit("server-error", { name, error: data.toString("utf8") });
219
227
  });
220
228
 
229
+ // If the server process dies with requests in flight, reject them
230
+ // immediately with a clear error instead of letting each hang until its
231
+ // 30s timeout (fail-fast on a crashed/exited MCP server).
232
+ const failPending = (errMsg) => {
233
+ for (const [, pending] of entry._pending) {
234
+ if (pending.timeout) clearTimeout(pending.timeout);
235
+ try {
236
+ pending.reject(new Error(errMsg));
237
+ } catch {
238
+ // already settled — ignore
239
+ }
240
+ }
241
+ entry._pending.clear();
242
+ };
243
+
221
244
  proc.on("close", (code) => {
222
245
  entry.state = ServerState.DISCONNECTED;
246
+ failPending(
247
+ `MCP server "${name}" process exited (code ${code}) before responding`,
248
+ );
223
249
  this.emit("server-disconnected", { name, code });
224
250
  });
225
251
 
226
252
  proc.on("error", (err) => {
227
253
  entry.state = ServerState.ERROR;
254
+ failPending(`MCP server "${name}" process error: ${err.message}`);
228
255
  this.emit("server-error", { name, error: err.message });
229
256
  });
230
257
  } else {
@@ -247,12 +274,24 @@ export class MCPClient extends EventEmitter {
247
274
  entry.serverInfo = initResult?.serverInfo || {};
248
275
  entry.capabilities = initResult?.capabilities || {};
249
276
 
250
- // Fetch available tools
277
+ // Fetch available tools. Per MCP a server advertises a `tools` capability
278
+ // in its initialize response; if it does and tools/list then fails, that
279
+ // is a genuine fetch failure we must surface (Claude-Code 2.1.181 — show
280
+ // "Connected · tools fetch failed" rather than a misleading "Tools: 0").
281
+ // A server that did not advertise tools simply has none, so a failure
282
+ // there is expected and stays quiet.
283
+ entry.tools = [];
284
+ entry.toolsError = null;
285
+ const advertisesTools =
286
+ entry.capabilities && entry.capabilities.tools !== undefined;
251
287
  try {
252
288
  const toolsResult = await this._sendRequest(name, "tools/list", {});
253
289
  entry.tools = toolsResult?.tools || [];
254
- } catch {
255
- // Server may not support tools
290
+ } catch (err) {
291
+ if (advertisesTools) {
292
+ entry.toolsError = err?.message || String(err);
293
+ }
294
+ // else: server did not advertise tools — legitimately none.
256
295
  }
257
296
 
258
297
  // Fetch available resources
@@ -275,11 +314,16 @@ export class MCPClient extends EventEmitter {
275
314
  // Server may not support prompts
276
315
  }
277
316
 
278
- this.emit("server-connected", { name, tools: entry.tools.length });
317
+ this.emit("server-connected", {
318
+ name,
319
+ tools: entry.tools.length,
320
+ toolsError: entry.toolsError,
321
+ });
279
322
  return {
280
323
  name,
281
324
  state: entry.state,
282
325
  tools: entry.tools,
326
+ toolsError: entry.toolsError,
283
327
  resources: entry.resources,
284
328
  prompts: entry.prompts,
285
329
  serverInfo: entry.serverInfo,
@@ -338,6 +382,7 @@ export class MCPClient extends EventEmitter {
338
382
  name,
339
383
  state: entry.state,
340
384
  tools: entry.tools.length,
385
+ toolsError: entry.toolsError || null,
341
386
  resources: entry.resources.length,
342
387
  prompts: (entry.prompts || []).length,
343
388
  serverInfo: entry.serverInfo || {},
@@ -599,53 +644,87 @@ export class MCPClient extends EventEmitter {
599
644
  headers["Mcp-Session-Id"] = entry.httpSessionId;
600
645
  }
601
646
 
602
- const response = await _deps.fetch(entry.httpUrl, {
603
- method: "POST",
604
- headers,
605
- body,
606
- });
607
-
608
- // Capture session id (server may emit on initialize response only)
609
- const sessionId =
610
- (response.headers && typeof response.headers.get === "function"
611
- ? response.headers.get("mcp-session-id") ||
612
- response.headers.get("Mcp-Session-Id")
613
- : null) || null;
614
- if (sessionId && !entry.httpSessionId) {
615
- entry.httpSessionId = sessionId;
616
- }
617
-
618
- if (!response.ok) {
619
- const text =
620
- typeof response.text === "function" ? await response.text() : "";
621
- throw new Error(
622
- `HTTP ${response.status}${text ? `: ${text.slice(0, 200)}` : ""}`,
623
- );
647
+ // Per-call timeout (parity with the 30s stdio timeout) so a hung or dead
648
+ // HTTP MCP server can't block the request forever. Servers flagged
649
+ // longRunning — e.g. the IDE bridge, whose openDiff blocks on human review
650
+ // (see ideServerToMcpConfig) — are exempt. Override per server with
651
+ // config.requestTimeoutMs (0 disables).
652
+ const longRunning = Boolean(entry.config && entry.config.longRunning);
653
+ const timeoutMs = Number.isFinite(entry.config?.requestTimeoutMs)
654
+ ? entry.config.requestTimeoutMs
655
+ : HTTP_REQUEST_TIMEOUT_MS;
656
+ let controller = null;
657
+ let timer = null;
658
+ if (
659
+ !longRunning &&
660
+ timeoutMs > 0 &&
661
+ typeof AbortController === "function"
662
+ ) {
663
+ controller = new AbortController();
664
+ timer = setTimeout(() => controller.abort(), timeoutMs);
624
665
  }
625
666
 
626
- const contentType = response.headers?.get
627
- ? String(response.headers.get("content-type") || "").toLowerCase()
628
- : "";
667
+ try {
668
+ const response = await _deps.fetch(entry.httpUrl, {
669
+ method: "POST",
670
+ headers,
671
+ body,
672
+ ...(controller ? { signal: controller.signal } : {}),
673
+ });
629
674
 
630
- let envelope;
631
- if (contentType.includes("text/event-stream")) {
632
- envelope = await _extractSseResponse(response, id);
633
- } else {
634
- envelope =
635
- typeof response.json === "function"
636
- ? await response.json()
637
- : JSON.parse(
638
- typeof response.text === "function" ? await response.text() : "",
639
- );
640
- }
675
+ // Capture session id (server may emit on initialize response only)
676
+ const sessionId =
677
+ (response.headers && typeof response.headers.get === "function"
678
+ ? response.headers.get("mcp-session-id") ||
679
+ response.headers.get("Mcp-Session-Id")
680
+ : null) || null;
681
+ if (sessionId && !entry.httpSessionId) {
682
+ entry.httpSessionId = sessionId;
683
+ }
641
684
 
642
- if (!envelope || typeof envelope !== "object") {
643
- throw new Error("Empty or invalid JSON-RPC response");
644
- }
645
- if (envelope.error) {
646
- throw new Error(envelope.error.message || "Unknown error");
685
+ if (!response.ok) {
686
+ const text =
687
+ typeof response.text === "function" ? await response.text() : "";
688
+ throw new Error(
689
+ `HTTP ${response.status}${text ? `: ${text.slice(0, 200)}` : ""}`,
690
+ );
691
+ }
692
+
693
+ const contentType = response.headers?.get
694
+ ? String(response.headers.get("content-type") || "").toLowerCase()
695
+ : "";
696
+
697
+ let envelope;
698
+ if (contentType.includes("text/event-stream")) {
699
+ envelope = await _extractSseResponse(response, id);
700
+ } else {
701
+ envelope =
702
+ typeof response.json === "function"
703
+ ? await response.json()
704
+ : JSON.parse(
705
+ typeof response.text === "function"
706
+ ? await response.text()
707
+ : "",
708
+ );
709
+ }
710
+
711
+ if (!envelope || typeof envelope !== "object") {
712
+ throw new Error("Empty or invalid JSON-RPC response");
713
+ }
714
+ if (envelope.error) {
715
+ throw new Error(envelope.error.message || "Unknown error");
716
+ }
717
+ return envelope.result;
718
+ } catch (err) {
719
+ if (controller && controller.signal.aborted) {
720
+ throw new Error(
721
+ `Request timeout: ${method} (HTTP, no response in ${timeoutMs}ms)`,
722
+ );
723
+ }
724
+ throw err;
725
+ } finally {
726
+ if (timer) clearTimeout(timer);
647
727
  }
648
- return envelope.result;
649
728
  }
650
729
 
651
730
  /** Fire-and-forget JSON-RPC notification over HTTP. Errors swallowed. */
@@ -12,23 +12,65 @@
12
12
  import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
13
13
  import { appendTokenUsage } from "../harness/jsonl-session-store.js";
14
14
 
15
+ // A streaming chat call must not hang forever if the API accepts the connection
16
+ // but then goes silent (TCP up, no bytes). Abort the request if no data arrives
17
+ // for STREAM_STALL_MS — reset on every chunk, so it's a stall detector, not a
18
+ // total-time cap (long but healthy responses are unaffected). The default is
19
+ // generous (and env-overridable) so a slow local model's first token isn't cut
20
+ // off; raise CC_CHAT_STALL_MS if you run very large local models.
21
+ export const STREAM_STALL_MS = Number(process.env.CC_CHAT_STALL_MS) || 180000;
22
+
23
+ function makeStallGuard(stallMs = STREAM_STALL_MS) {
24
+ const controller = new AbortController();
25
+ let timer = null;
26
+ const bump = () => {
27
+ if (timer) clearTimeout(timer);
28
+ timer = setTimeout(() => controller.abort(), stallMs);
29
+ if (timer && typeof timer.unref === "function") timer.unref();
30
+ };
31
+ const stop = () => {
32
+ if (timer) clearTimeout(timer);
33
+ timer = null;
34
+ };
35
+ return {
36
+ signal: controller.signal,
37
+ bump,
38
+ stop,
39
+ stalled: () => controller.signal.aborted,
40
+ };
41
+ }
42
+
15
43
  /**
16
44
  * Stream a response from Ollama.
17
45
  * If `onUsage` is provided, it's called with `{inputTokens, outputTokens}`
18
46
  * derived from Ollama's terminal `prompt_eval_count` / `eval_count` fields.
19
47
  */
20
48
  export async function streamOllama(messages, model, baseUrl, onToken, onUsage) {
21
- const response = await fetch(`${baseUrl}/api/chat`, {
22
- method: "POST",
23
- headers: { "Content-Type": "application/json" },
24
- body: JSON.stringify({
25
- model,
26
- messages,
27
- stream: true,
28
- }),
29
- });
49
+ const guard = makeStallGuard();
50
+ guard.bump();
51
+ let response;
52
+ try {
53
+ response = await fetch(`${baseUrl}/api/chat`, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify({
57
+ model,
58
+ messages,
59
+ stream: true,
60
+ }),
61
+ signal: guard.signal,
62
+ });
63
+ } catch (e) {
64
+ guard.stop();
65
+ throw guard.stalled()
66
+ ? new Error(
67
+ `Ollama request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
68
+ )
69
+ : e;
70
+ }
30
71
 
31
72
  if (!response.ok) {
73
+ guard.stop();
32
74
  throw new Error(`Ollama error: ${response.status} ${response.statusText}`);
33
75
  }
34
76
 
@@ -36,31 +78,42 @@ export async function streamOllama(messages, model, baseUrl, onToken, onUsage) {
36
78
  const decoder = new TextDecoder();
37
79
  let fullResponse = "";
38
80
 
39
- while (true) {
40
- const { done, value } = await reader.read();
41
- if (done) break;
81
+ try {
82
+ while (true) {
83
+ const { done, value } = await reader.read();
84
+ if (done) break;
85
+ guard.bump();
42
86
 
43
- const text = decoder.decode(value, { stream: true });
44
- const lines = text.split("\n").filter(Boolean);
87
+ const text = decoder.decode(value, { stream: true });
88
+ const lines = text.split("\n").filter(Boolean);
45
89
 
46
- for (const line of lines) {
47
- try {
48
- const json = JSON.parse(line);
49
- if (json.message?.content) {
50
- fullResponse += json.message.content;
51
- onToken(json.message.content);
52
- }
53
- if (json.done && onUsage) {
54
- const inputTokens = Number(json.prompt_eval_count) || 0;
55
- const outputTokens = Number(json.eval_count) || 0;
56
- if (inputTokens || outputTokens) {
57
- onUsage({ inputTokens, outputTokens });
90
+ for (const line of lines) {
91
+ try {
92
+ const json = JSON.parse(line);
93
+ if (json.message?.content) {
94
+ fullResponse += json.message.content;
95
+ onToken(json.message.content);
58
96
  }
97
+ if (json.done && onUsage) {
98
+ const inputTokens = Number(json.prompt_eval_count) || 0;
99
+ const outputTokens = Number(json.eval_count) || 0;
100
+ if (inputTokens || outputTokens) {
101
+ onUsage({ inputTokens, outputTokens });
102
+ }
103
+ }
104
+ } catch {
105
+ // Partial JSON, skip
59
106
  }
60
- } catch {
61
- // Partial JSON, skip
62
107
  }
63
108
  }
109
+ } catch (e) {
110
+ throw guard.stalled()
111
+ ? new Error(
112
+ `Ollama stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
113
+ )
114
+ : e;
115
+ } finally {
116
+ guard.stop();
64
117
  }
65
118
 
66
119
  return fullResponse;
@@ -77,23 +130,37 @@ export async function streamOpenAI(
77
130
  onToken,
78
131
  onUsage,
79
132
  ) {
80
- const response = await fetch(`${baseUrl}/chat/completions`, {
81
- method: "POST",
82
- headers: {
83
- "Content-Type": "application/json",
84
- Authorization: `Bearer ${apiKey}`,
85
- },
86
- body: JSON.stringify({
87
- model,
88
- messages,
89
- stream: true,
90
- // Opt-in token usage in the terminal chunk (OpenAI-compatible).
91
- // Servers that don't understand it simply ignore it.
92
- stream_options: { include_usage: true },
93
- }),
94
- });
133
+ const guard = makeStallGuard();
134
+ guard.bump();
135
+ let response;
136
+ try {
137
+ response = await fetch(`${baseUrl}/chat/completions`, {
138
+ method: "POST",
139
+ headers: {
140
+ "Content-Type": "application/json",
141
+ Authorization: `Bearer ${apiKey}`,
142
+ },
143
+ body: JSON.stringify({
144
+ model,
145
+ messages,
146
+ stream: true,
147
+ // Opt-in token usage in the terminal chunk (OpenAI-compatible).
148
+ // Servers that don't understand it simply ignore it.
149
+ stream_options: { include_usage: true },
150
+ }),
151
+ signal: guard.signal,
152
+ });
153
+ } catch (e) {
154
+ guard.stop();
155
+ throw guard.stalled()
156
+ ? new Error(
157
+ `API request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
158
+ )
159
+ : e;
160
+ }
95
161
 
96
162
  if (!response.ok) {
163
+ guard.stop();
97
164
  throw new Error(`API error: ${response.status} ${response.statusText}`);
98
165
  }
99
166
 
@@ -101,36 +168,45 @@ export async function streamOpenAI(
101
168
  const decoder = new TextDecoder();
102
169
  let fullResponse = "";
103
170
 
104
- while (true) {
105
- const { done, value } = await reader.read();
106
- if (done) break;
171
+ try {
172
+ while (true) {
173
+ const { done, value } = await reader.read();
174
+ if (done) break;
175
+ guard.bump();
107
176
 
108
- const text = decoder.decode(value, { stream: true });
109
- const lines = text.split("\n").filter(Boolean);
177
+ const text = decoder.decode(value, { stream: true });
178
+ const lines = text.split("\n").filter(Boolean);
110
179
 
111
- for (const line of lines) {
112
- if (line.startsWith("data: ")) {
113
- const data = line.slice(6);
114
- if (data === "[DONE]") continue;
115
- try {
116
- const json = JSON.parse(data);
117
- const content = json.choices?.[0]?.delta?.content;
118
- if (content) {
119
- fullResponse += content;
120
- onToken(content);
121
- }
122
- if (json.usage && onUsage) {
123
- const inputTokens = Number(json.usage.prompt_tokens) || 0;
124
- const outputTokens = Number(json.usage.completion_tokens) || 0;
125
- if (inputTokens || outputTokens) {
126
- onUsage({ inputTokens, outputTokens });
180
+ for (const line of lines) {
181
+ if (line.startsWith("data: ")) {
182
+ const data = line.slice(6);
183
+ if (data === "[DONE]") continue;
184
+ try {
185
+ const json = JSON.parse(data);
186
+ const content = json.choices?.[0]?.delta?.content;
187
+ if (content) {
188
+ fullResponse += content;
189
+ onToken(content);
190
+ }
191
+ if (json.usage && onUsage) {
192
+ const inputTokens = Number(json.usage.prompt_tokens) || 0;
193
+ const outputTokens = Number(json.usage.completion_tokens) || 0;
194
+ if (inputTokens || outputTokens) {
195
+ onUsage({ inputTokens, outputTokens });
196
+ }
127
197
  }
198
+ } catch {
199
+ // Partial data
128
200
  }
129
- } catch {
130
- // Partial data
131
201
  }
132
202
  }
133
203
  }
204
+ } catch (e) {
205
+ throw guard.stalled()
206
+ ? new Error(`API stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`)
207
+ : e;
208
+ } finally {
209
+ guard.stop();
134
210
  }
135
211
 
136
212
  return fullResponse;
@@ -161,23 +237,37 @@ export async function streamAnthropic(
161
237
  }
162
238
  }
163
239
 
164
- const response = await fetch(`${baseUrl}/messages`, {
165
- method: "POST",
166
- headers: {
167
- "Content-Type": "application/json",
168
- "x-api-key": apiKey,
169
- "anthropic-version": "2023-06-01",
170
- },
171
- body: JSON.stringify({
172
- model,
173
- max_tokens: 4096,
174
- stream: true,
175
- ...(system ? { system } : {}),
176
- messages: convo,
177
- }),
178
- });
240
+ const guard = makeStallGuard();
241
+ guard.bump();
242
+ let response;
243
+ try {
244
+ response = await fetch(`${baseUrl}/messages`, {
245
+ method: "POST",
246
+ headers: {
247
+ "Content-Type": "application/json",
248
+ "x-api-key": apiKey,
249
+ "anthropic-version": "2023-06-01",
250
+ },
251
+ body: JSON.stringify({
252
+ model,
253
+ max_tokens: 4096,
254
+ stream: true,
255
+ ...(system ? { system } : {}),
256
+ messages: convo,
257
+ }),
258
+ signal: guard.signal,
259
+ });
260
+ } catch (e) {
261
+ guard.stop();
262
+ throw guard.stalled()
263
+ ? new Error(
264
+ `Anthropic request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
265
+ )
266
+ : e;
267
+ }
179
268
 
180
269
  if (!response.ok) {
270
+ guard.stop();
181
271
  throw new Error(
182
272
  `Anthropic error: ${response.status} ${response.statusText}`,
183
273
  );
@@ -190,36 +280,48 @@ export async function streamAnthropic(
190
280
  let inputTokens = 0;
191
281
  let outputTokens = 0;
192
282
 
193
- while (true) {
194
- const { done, value } = await reader.read();
195
- if (done) break;
196
- buf += decoder.decode(value, { stream: true });
197
- const lines = buf.split("\n");
198
- buf = lines.pop() || "";
199
- for (const raw of lines) {
200
- const line = raw.trim();
201
- if (!line || !line.startsWith("data:")) continue;
202
- const payload = line.slice(5).trim();
203
- if (!payload) continue;
204
- try {
205
- const obj = JSON.parse(payload);
206
- if (obj.type === "content_block_delta") {
207
- const delta = obj.delta?.text;
208
- if (delta) {
209
- fullResponse += delta;
210
- onToken(delta);
283
+ try {
284
+ while (true) {
285
+ const { done, value } = await reader.read();
286
+ if (done) break;
287
+ guard.bump();
288
+ buf += decoder.decode(value, { stream: true });
289
+ const lines = buf.split("\n");
290
+ buf = lines.pop() || "";
291
+ for (const raw of lines) {
292
+ const line = raw.trim();
293
+ if (!line || !line.startsWith("data:")) continue;
294
+ const payload = line.slice(5).trim();
295
+ if (!payload) continue;
296
+ try {
297
+ const obj = JSON.parse(payload);
298
+ if (obj.type === "content_block_delta") {
299
+ const delta = obj.delta?.text;
300
+ if (delta) {
301
+ fullResponse += delta;
302
+ onToken(delta);
303
+ }
304
+ } else if (obj.type === "message_start") {
305
+ inputTokens =
306
+ Number(obj.message?.usage?.input_tokens) || inputTokens;
307
+ outputTokens =
308
+ Number(obj.message?.usage?.output_tokens) || outputTokens;
309
+ } else if (obj.type === "message_delta") {
310
+ outputTokens = Number(obj.usage?.output_tokens) || outputTokens;
211
311
  }
212
- } else if (obj.type === "message_start") {
213
- inputTokens = Number(obj.message?.usage?.input_tokens) || inputTokens;
214
- outputTokens =
215
- Number(obj.message?.usage?.output_tokens) || outputTokens;
216
- } else if (obj.type === "message_delta") {
217
- outputTokens = Number(obj.usage?.output_tokens) || outputTokens;
312
+ } catch {
313
+ /* skip malformed */
218
314
  }
219
- } catch {
220
- /* skip malformed */
221
315
  }
222
316
  }
317
+ } catch (e) {
318
+ throw guard.stalled()
319
+ ? new Error(
320
+ `Anthropic stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
321
+ )
322
+ : e;
323
+ } finally {
324
+ guard.stop();
223
325
  }
224
326
 
225
327
  if (onUsage && (inputTokens || outputTokens)) {
@@ -97,6 +97,7 @@ export class ClaudeCodeAgent extends EventEmitter {
97
97
  timeout = 300_000,
98
98
  context = "",
99
99
  allowedTools = null,
100
+ killGraceMs = 3000,
100
101
  } = options;
101
102
 
102
103
  const fullPrompt = context
@@ -128,10 +129,19 @@ export class ClaudeCodeAgent extends EventEmitter {
128
129
  });
129
130
  this._proc = proc;
130
131
 
132
+ // SIGKILL-escalation timer is hoisted so the close/error handlers can
133
+ // clear it — otherwise, when the process dies promptly from SIGTERM, this
134
+ // inner timer still fires a redundant SIGKILL on a dead pid AND holds the
135
+ // event loop open for the full grace period. unref() is a second guard so
136
+ // it never keeps the process alive on its own.
137
+ let killTimer = null;
131
138
  const timer = setTimeout(() => {
132
139
  timedOut = true;
133
140
  proc.kill("SIGTERM");
134
- setTimeout(() => proc.kill("SIGKILL"), 3000);
141
+ killTimer = setTimeout(() => proc.kill("SIGKILL"), killGraceMs);
142
+ if (killTimer && typeof killTimer.unref === "function") {
143
+ killTimer.unref();
144
+ }
135
145
  }, timeout);
136
146
 
137
147
  proc.stdout.on("data", (data) => {
@@ -146,6 +156,7 @@ export class ClaudeCodeAgent extends EventEmitter {
146
156
 
147
157
  proc.on("close", (code) => {
148
158
  clearTimeout(timer);
159
+ if (killTimer) clearTimeout(killTimer);
149
160
  this._proc = null;
150
161
  const duration = Date.now() - startTime;
151
162
  const rawOutput = outputChunks.join("");
@@ -178,6 +189,7 @@ export class ClaudeCodeAgent extends EventEmitter {
178
189
 
179
190
  proc.on("error", (err) => {
180
191
  clearTimeout(timer);
192
+ if (killTimer) clearTimeout(killTimer);
181
193
  this._proc = null;
182
194
  this.status = AGENT_STATUS.FAILED;
183
195
  this.currentTask = null;