chainlesschain 0.162.124 → 0.162.126

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 (260) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/AIOps-BrTOcqIJ.js +1 -0
  4. package/src/assets/web-panel/assets/{ActionButton-COjZZKWG.js → ActionButton-na9cocmf.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-4ZDZE7lc.js → Analytics-BtDygkpt.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-CYH5k2Rp.js → AppLayout-D_t5CS18.js} +5 -5
  7. package/src/assets/web-panel/assets/Audit-BYMZccCy.js +1 -0
  8. package/src/assets/web-panel/assets/{Backup-CiX61Qc2.js → Backup-D2qDfAdL.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-CDxIQokd.js → BaseInput-kj456Ju9.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-CaKgdRab.js → Chat-BY0A0ZLE.js} +4 -4
  11. package/src/assets/web-panel/assets/ChatBubbleRenderer-D1VvrOPQ.js +1 -0
  12. package/src/assets/web-panel/assets/{Checkbox-CjiWfu4s.js → Checkbox-DiBhtXsh.js} +1 -1
  13. package/src/assets/web-panel/assets/Codegen-B1ER0L0j.js +1 -0
  14. package/src/assets/web-panel/assets/{Col-ZKWREyNs.js → Col-CjLmza7D.js} +1 -1
  15. package/src/assets/web-panel/assets/Community-BNId05U7.js +1 -0
  16. package/src/assets/web-panel/assets/{Compact-DqknM98n.js → Compact-BuuMj7K1.js} +1 -1
  17. package/src/assets/web-panel/assets/Compliance-DUuDycaJ.js +1 -0
  18. package/src/assets/web-panel/assets/{Cowork-Dp29kHsC.js → Cowork-BKHiF6uL.js} +2 -2
  19. package/src/assets/web-panel/assets/{Cron-zmEJAetz.js → Cron-C7p-lHnC.js} +2 -2
  20. package/src/assets/web-panel/assets/Crosschain-BvSOx8a6.js +1 -0
  21. package/src/assets/web-panel/assets/{DID-D_UFNzJ5.js → DID-D1k9jgHv.js} +2 -2
  22. package/src/assets/web-panel/assets/Dashboard-B9mCCnqY.js +3 -0
  23. package/src/assets/web-panel/assets/{Dropdown-CH7l3KCs.js → Dropdown-ge9UHSXF.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-Bud2M3XX.js → EmailListRenderer-TZO7ppXi.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bd2_8uME.js → FamilyGuardDashboard-DkhLJmzx.js} +1 -1
  26. package/src/assets/web-panel/assets/Federation-JLuA_8zp.js +1 -0
  27. package/src/assets/web-panel/assets/{FormItemContext-CpSH464Y.js → FormItemContext-C4w-rzNg.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-CANo8O-y.js → GenericCardRenderer-Btns4sV9.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-hvuZVoD3.js → Git-BheXMjzC.js} +2 -2
  30. package/src/assets/web-panel/assets/Governance-D9rLlatH.js +1 -0
  31. package/src/assets/web-panel/assets/Inference-DiklIMcd.js +1 -0
  32. package/src/assets/web-panel/assets/KnowledgeGraph-C70EWL03.js +1 -0
  33. package/src/assets/web-panel/assets/{Logs-DDkTnedu.js → Logs-Zt_MLI10.js} +2 -2
  34. package/src/assets/web-panel/assets/Marketplace-suQxES99.js +1 -0
  35. package/src/assets/web-panel/assets/{McpTools-Qx78XyOT.js → McpTools-CclpCDpY.js} +3 -3
  36. package/src/assets/web-panel/assets/{Memory-CXYDO1oT.js → Memory-B2lHOUwF.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileBridge-BMXKoDAD.js +1 -0
  38. package/src/assets/web-panel/assets/{MobileProjects-DEDbWNIk.js → MobileProjects-a6mWVdBV.js} +1 -1
  39. package/src/assets/web-panel/assets/{Mtc-Dwa_vlR8.js → Mtc-BFpM4Fkj.js} +2 -2
  40. package/src/assets/web-panel/assets/{MtcAudit-DAVkxhJv.js → MtcAudit-DDQkxkbS.js} +4 -4
  41. package/src/assets/web-panel/assets/{Multisig-C3upmgPN.js → Multisig-Crkd_zKQ.js} +3 -3
  42. package/src/assets/web-panel/assets/NLProgramming-Dpk5An7B.js +1 -0
  43. package/src/assets/web-panel/assets/{Notes-B1Oy3FbC.js → Notes-BrFnOMNz.js} +4 -4
  44. package/src/assets/web-panel/assets/{NotificationSettings-Bs4-Fc6b.js → NotificationSettings-CM0iApFM.js} +1 -1
  45. package/src/assets/web-panel/assets/{OrderTableRenderer-CEy1pIeq.js → OrderTableRenderer-XXjeqkdz.js} +1 -1
  46. package/src/assets/web-panel/assets/{Organization-D4cJ1UNo.js → Organization-CHFW_38T.js} +2 -2
  47. package/src/assets/web-panel/assets/{Overflow-fdzvlF7x.js → Overflow-C-vl3EjV.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-Y13PwXBA.js → P2P-BT7Slavq.js} +2 -2
  49. package/src/assets/web-panel/assets/{PdhVaultBrowser-Ck1zCLe6.js → PdhVaultBrowser-DRUaULap.js} +3 -3
  50. package/src/assets/web-panel/assets/{Permissions-CvEXP6LC.js → Permissions-DkhOAvMz.js} +2 -2
  51. package/src/assets/web-panel/assets/{PersonalDataHub-JeP7IWMW.js → PersonalDataHub-Dl7dSOvV.js} +3 -3
  52. package/src/assets/web-panel/assets/Pipeline-B3nVI4p6.js +1 -0
  53. package/src/assets/web-panel/assets/Privacy-Dv1SXx1Q.js +1 -0
  54. package/src/assets/web-panel/assets/{ProjectInit-DeWd9UcS.js → ProjectInit-DrAhVi5p.js} +2 -2
  55. package/src/assets/web-panel/assets/{ProjectSettings-DUpVuU9F.js → ProjectSettings-h-ggCYN_.js} +2 -2
  56. package/src/assets/web-panel/assets/Projects-CM91hYdr.js +1 -0
  57. package/src/assets/web-panel/assets/{Providers-DECW00ek.js → Providers-rfkZel3N.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-Dl-pK9Io.js → QuickAsk-XepZB7h_.js} +1 -1
  59. package/src/assets/web-panel/assets/Recommend-DzQWsh1a.js +1 -0
  60. package/src/assets/web-panel/assets/Reputation-CDeaal0j.js +1 -0
  61. package/src/assets/web-panel/assets/Row-BN4aKhFH.js +1 -0
  62. package/src/assets/web-panel/assets/{RssFeed-iyQT5q9l.js → RssFeed-INP6VYAA.js} +3 -3
  63. package/src/assets/web-panel/assets/Search-ChPbwatu.js +1 -0
  64. package/src/assets/web-panel/assets/{Security-c4Jxf5Ih.js → Security-C97ZCY8N.js} +4 -4
  65. package/src/assets/web-panel/assets/{Services-CRuisolj.js → Services-Cbtl2Ajs.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-CCnzJkb-.js → Skeleton-B13sFxBQ.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-CZURCwZg.js → Skills-vI9zSIKX.js} +1 -1
  68. package/src/assets/web-panel/assets/Sla-Cr3oBH39.js +1 -0
  69. package/src/assets/web-panel/assets/SpeechSettings-BwemqPPV.js +1 -0
  70. package/src/assets/web-panel/assets/{SyncSettings-CJWVtd87.js → SyncSettings-CLA78P-Z.js} +2 -2
  71. package/src/assets/web-panel/assets/{Tasks-61lrQ_lS.js → Tasks-DBjQ_DOM.js} +1 -1
  72. package/src/assets/web-panel/assets/Templates-BkSLDkBs.js +1 -0
  73. package/src/assets/web-panel/assets/Tenant-Dn0nSlib.js +1 -0
  74. package/src/assets/web-panel/assets/{Terminal-CP15hcNS.js → Terminal-C8f4boru.js} +2 -2
  75. package/src/assets/web-panel/assets/{TimelineRenderer-Sl5uXrkN.js → TimelineRenderer-DW_rMtJR.js} +1 -1
  76. package/src/assets/web-panel/assets/Tokens-BdffrKYV.js +1 -0
  77. package/src/assets/web-panel/assets/{Trigger-BLCQEOF2.js → Trigger-CX4CUg-Q.js} +1 -1
  78. package/src/assets/web-panel/assets/Trust-Cq9Bl7Od.js +1 -0
  79. package/src/assets/web-panel/assets/{UkeySign-Dwp0zXQ6.js → UkeySign-BQy18_VY.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CIHA55Sx.js → VideoEditing-h5hzK7Vw.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-Hjajvnto.js → Wallet-BLotT3gw.js} +3 -3
  82. package/src/assets/web-panel/assets/{WebAuthn-DqSURUvI.js → WebAuthn-8cCANYLE.js} +4 -4
  83. package/src/assets/web-panel/assets/{WorkflowEditor-B5bHRwUG.js → WorkflowEditor-Db6NwtAv.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-DsheLKkG.js → chat-BPk1LbpL.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-CST2UkgL.js → colors-BnNal71p.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-yqEoy1L7.js → compact-item-W2VC0tcy.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-Jwp78HFY.js → createContext-DceVsgnZ.js} +1 -1
  88. package/src/assets/web-panel/assets/devWarning--rwFDBhx.js +1 -0
  89. package/src/assets/web-panel/assets/{hasIn-CVS5mFEP.js → hasIn-BEq6EDcp.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-BLTUVd0V.js → index-5gIBUFBn.js} +2 -2
  91. package/src/assets/web-panel/assets/index-APCHxOkM.js +3 -0
  92. package/src/assets/web-panel/assets/index-B2dMhwZi.js +1 -0
  93. package/src/assets/web-panel/assets/{index-C0FZScMe.js → index-B8cPjrEH.js} +4 -4
  94. package/src/assets/web-panel/assets/{index-CjfN2CpJ.js → index-BDC5HsxZ.js} +1 -1
  95. package/src/assets/web-panel/assets/index-BPFuPGKi.js +1 -0
  96. package/src/assets/web-panel/assets/{index-SjxCCf5h.js → index-BUkDcIwJ.js} +2 -2
  97. package/src/assets/web-panel/assets/{index-Cjig5i3a.js → index-BWcCyH6-.js} +28 -26
  98. package/src/assets/web-panel/assets/{index-AnW25bEq.js → index-BgZikQoh.js} +1 -1
  99. package/src/assets/web-panel/assets/index-Bwjus13Y.js +1 -0
  100. package/src/assets/web-panel/assets/index-BzSzRWsw.js +55 -0
  101. package/src/assets/web-panel/assets/index-C1K9CsGr.js +1 -0
  102. package/src/assets/web-panel/assets/{index-DDAigsel.js → index-CBZVISK6.js} +2 -2
  103. package/src/assets/web-panel/assets/index-CEJN-R2L.js +1 -0
  104. package/src/assets/web-panel/assets/index-CG5dGC9E.js +1 -0
  105. package/src/assets/web-panel/assets/{index-DxaU_lza.js → index-CMGLpstm.js} +1 -1
  106. package/src/assets/web-panel/assets/index-CWrGCndd.js +1 -0
  107. package/src/assets/web-panel/assets/index-CbcvfpCV.js +1 -0
  108. package/src/assets/web-panel/assets/{index-De600Jjm.js → index-Ceqv4lI2.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Cy0dNzkp.js → index-CsT6frT8.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CDAPnZUs.js → index-D8PQKsDT.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Crk4KWLW.js → index-DGncdA-j.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-R-VGFpi6.js → index-DHanQHO0.js} +3 -3
  113. package/src/assets/web-panel/assets/index-DaUBk28E.js +12 -0
  114. package/src/assets/web-panel/assets/{index-BzetOasL.js → index-Di2J4b4V.js} +1 -1
  115. package/src/assets/web-panel/assets/index-DtuLvWZN.js +21 -0
  116. package/src/assets/web-panel/assets/{index-Dgyn8-wN.js → index-DvS1J8xc.js} +2 -2
  117. package/src/assets/web-panel/assets/{index-CtyEOGuj.js → index-DxePJdwP.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CzsKK0tQ.js → index-Et4XvL49.js} +2 -2
  119. package/src/assets/web-panel/assets/{index-BuI27WSx.js → index-GlFxgcj4.js} +2 -2
  120. package/src/assets/web-panel/assets/index-HV119Oui.js +1 -0
  121. package/src/assets/web-panel/assets/index-InGW87pj.js +13 -0
  122. package/src/assets/web-panel/assets/{index-Dxljh9Fu.js → index-P1-s8JR7.js} +2 -2
  123. package/src/assets/web-panel/assets/{index-CSBPc-sI.js → index-SdABCNoi.js} +2 -2
  124. package/src/assets/web-panel/assets/{index-BCHAUdel.js → index-WTAZbN-c.js} +1 -1
  125. package/src/assets/web-panel/assets/index-_2I-KzOj.js +1 -0
  126. package/src/assets/web-panel/assets/index-m1sVaWVU.js +1 -0
  127. package/src/assets/web-panel/assets/index-s37wwyeN.js +1 -0
  128. package/src/assets/web-panel/assets/{index-CRBbVS43.js → index-tyWABqp9.js} +1 -1
  129. package/src/assets/web-panel/assets/{initDefaultProps-ClAjrsQ-.js → initDefaultProps-DzrCDb3j.js} +1 -1
  130. package/src/assets/web-panel/assets/motion-BQERVnE3.js +11 -0
  131. package/src/assets/web-panel/assets/{move-DMelzIW-.js → move-I9ACA5XJ.js} +1 -1
  132. package/src/assets/web-panel/assets/mtc-parser-Dd2Pw-tr.js +1 -0
  133. package/src/assets/web-panel/assets/{omit-oxecfU3b.js → omit-DwzYRzWV.js} +1 -1
  134. package/src/assets/web-panel/assets/{pickAttrs-DJ5F5M6x.js → pickAttrs-BSGULyIL.js} +1 -1
  135. package/src/assets/web-panel/assets/{placementArrow-DdbKsant.js → placementArrow-tSYYjAts.js} +1 -1
  136. package/src/assets/web-panel/assets/{responsiveObserve-BUuM5cmS.js → responsiveObserve-Dy2lqikT.js} +1 -1
  137. package/src/assets/web-panel/assets/{slide-DSV0ccvR.js → slide-RaB4Uq0c.js} +1 -1
  138. package/src/assets/web-panel/assets/{statusUtils-B-6oLAXf.js → statusUtils-CBpC_wZg.js} +1 -1
  139. package/src/assets/web-panel/assets/{styleChecker-DdCAHtsZ.js → styleChecker-BEKwPWt5.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFlexGapSupport-ZORkcoZd.js → useFlexGapSupport-C4CnYJH1.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFs-BFp46LoP.js → useFs-n24jutw5.js} +1 -1
  142. package/src/assets/web-panel/assets/{usePersonalDataHub-H7rxgbdW.js → usePersonalDataHub-DMgS8F6G.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-eIq4Tk0J.js → vnode-BSp2KYcj.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-CTNrv8-H.js → zoom-7UfQtTPh.js} +1 -1
  145. package/src/assets/web-panel/index.html +1 -1
  146. package/src/commands/agent.js +51 -2
  147. package/src/commands/compliance.js +3 -1
  148. package/src/commands/ide.js +60 -5
  149. package/src/commands/incentive.js +22 -22
  150. package/src/commands/mtc.js +5 -1
  151. package/src/commands/review.js +47 -17
  152. package/src/commands/session.js +11 -2
  153. package/src/gateways/ws/message-dispatcher.js +149 -166
  154. package/src/gateways/ws/ws-session-gateway.js +95 -9
  155. package/src/harness/background-task-manager.js +5 -1
  156. package/src/harness/jsonl-session-store.js +90 -12
  157. package/src/harness/mcp-client.js +62 -21
  158. package/src/harness/prompt-compressor.js +24 -5
  159. package/src/harness/worktree-isolator.js +10 -1
  160. package/src/lib/agent-core.js +2 -0
  161. package/src/lib/audit-logger.js +3 -1
  162. package/src/lib/autonomous-agent.js +29 -0
  163. package/src/lib/chat-core.js +5 -2
  164. package/src/lib/chat-intent-service.js +82 -23
  165. package/src/lib/checkpoint-store.js +31 -4
  166. package/src/lib/claude-code-bridge.js +48 -3
  167. package/src/lib/config-manager.js +25 -1
  168. package/src/lib/cost-budget.js +13 -2
  169. package/src/lib/credential-guard.js +81 -1
  170. package/src/lib/git-integration.js +16 -2
  171. package/src/lib/goal-store.js +11 -0
  172. package/src/lib/hook-manager.js +4 -0
  173. package/src/lib/interaction-adapter.js +32 -11
  174. package/src/lib/jetbrains-bridge.js +147 -0
  175. package/src/lib/jsonl-session-store.js +2 -0
  176. package/src/lib/llm-config-defaults.js +37 -0
  177. package/src/lib/llm-pricing.js +11 -4
  178. package/src/lib/llm-providers.js +11 -1
  179. package/src/lib/mcp-oauth.js +88 -10
  180. package/src/lib/mcp-serve.js +29 -0
  181. package/src/lib/orchestrator.js +38 -2
  182. package/src/lib/permission-engine.js +4 -1
  183. package/src/lib/pipeline-orchestrator.js +20 -2
  184. package/src/lib/project-instructions.js +13 -0
  185. package/src/lib/repl-bang-memorize.js +9 -1
  186. package/src/lib/repl-denials.js +137 -0
  187. package/src/lib/runnable-provider.js +7 -1
  188. package/src/lib/safe-json.js +22 -0
  189. package/src/lib/sandbox-v2.js +7 -6
  190. package/src/lib/search-command.js +65 -0
  191. package/src/lib/session-insights.js +13 -6
  192. package/src/lib/session-usage.js +21 -3
  193. package/src/lib/settings-hooks.cjs +133 -0
  194. package/src/lib/settings-loader.cjs +16 -7
  195. package/src/lib/social-graph.js +3 -1
  196. package/src/lib/status-line.cjs +4 -1
  197. package/src/lib/stream-retry.js +27 -0
  198. package/src/lib/sub-agent-context.js +9 -0
  199. package/src/lib/turn-context.js +15 -5
  200. package/src/repl/agent-repl.js +110 -8
  201. package/src/runtime/__tests__/message-roles.test.js +36 -3
  202. package/src/runtime/agent-core.js +179 -52
  203. package/src/runtime/coding-agent-contract-shared.cjs +9 -2
  204. package/src/runtime/coding-agent-shell-policy.cjs +23 -2
  205. package/src/runtime/file-ref-expander.js +51 -7
  206. package/src/runtime/headless-runner.js +96 -2
  207. package/src/runtime/headless-stream.js +78 -3
  208. package/src/runtime/mcp-config.js +114 -4
  209. package/src/runtime/message-roles.js +14 -1
  210. package/src/runtime/pipe-safety.js +51 -0
  211. package/src/runtime/policies/agent-policy.js +3 -0
  212. package/src/assets/web-panel/assets/AIOps-BsDJlD_l.js +0 -1
  213. package/src/assets/web-panel/assets/Audit-BKlFbLdl.js +0 -1
  214. package/src/assets/web-panel/assets/ChatBubbleRenderer-DgvpwU5o.js +0 -1
  215. package/src/assets/web-panel/assets/Codegen-tKWGzajw.js +0 -1
  216. package/src/assets/web-panel/assets/Community-CmLuPBUU.js +0 -1
  217. package/src/assets/web-panel/assets/Compliance-PZw0aBLI.js +0 -1
  218. package/src/assets/web-panel/assets/Crosschain-Sb-uM7Ty.js +0 -1
  219. package/src/assets/web-panel/assets/Dashboard-Btag698v.js +0 -3
  220. package/src/assets/web-panel/assets/Federation-1jhstF5P.js +0 -1
  221. package/src/assets/web-panel/assets/Governance-BQrWksKt.js +0 -1
  222. package/src/assets/web-panel/assets/Inference-jEBTp12v.js +0 -1
  223. package/src/assets/web-panel/assets/KnowledgeGraph-tDiV2taf.js +0 -1
  224. package/src/assets/web-panel/assets/Marketplace-9Yi32avt.js +0 -1
  225. package/src/assets/web-panel/assets/MobileBridge-MfUfkHA0.js +0 -1
  226. package/src/assets/web-panel/assets/NLProgramming-DO3CZ0_z.js +0 -1
  227. package/src/assets/web-panel/assets/Pipeline-BZ7wRzG1.js +0 -1
  228. package/src/assets/web-panel/assets/Privacy-BJ6qj9Hv.js +0 -1
  229. package/src/assets/web-panel/assets/Projects-y1WbI337.js +0 -1
  230. package/src/assets/web-panel/assets/Recommend-Bju04wTd.js +0 -1
  231. package/src/assets/web-panel/assets/Reputation-BUPa3nEk.js +0 -1
  232. package/src/assets/web-panel/assets/Row-WYqqaq_5.js +0 -1
  233. package/src/assets/web-panel/assets/Search-CrfwJF0R.js +0 -1
  234. package/src/assets/web-panel/assets/Sla-CrMzaEi2.js +0 -1
  235. package/src/assets/web-panel/assets/SpeechSettings-jTDOM5eY.js +0 -1
  236. package/src/assets/web-panel/assets/Templates-Cg-iF2g1.js +0 -1
  237. package/src/assets/web-panel/assets/Tenant-DrDv1FFc.js +0 -1
  238. package/src/assets/web-panel/assets/Tokens-yRIZTVsR.js +0 -1
  239. package/src/assets/web-panel/assets/Trust-D5xq8z6s.js +0 -1
  240. package/src/assets/web-panel/assets/community-parser-Cuyslaku.js +0 -3
  241. package/src/assets/web-panel/assets/devWarning-C8to6gQk.js +0 -1
  242. package/src/assets/web-panel/assets/index-BeblNJDH.js +0 -1
  243. package/src/assets/web-panel/assets/index-C130LLPq.js +0 -1
  244. package/src/assets/web-panel/assets/index-C8rq85gu.js +0 -1
  245. package/src/assets/web-panel/assets/index-CIE2n8k_.js +0 -1
  246. package/src/assets/web-panel/assets/index-CTd3lDxp.js +0 -13
  247. package/src/assets/web-panel/assets/index-CUWj5L2s.js +0 -1
  248. package/src/assets/web-panel/assets/index-ChRL6rgA.js +0 -3
  249. package/src/assets/web-panel/assets/index-CrlRa054.js +0 -1
  250. package/src/assets/web-panel/assets/index-Cu6Ql64n.js +0 -1
  251. package/src/assets/web-panel/assets/index-Cx_t5rWw.js +0 -12
  252. package/src/assets/web-panel/assets/index-DZfnYE6e.js +0 -1
  253. package/src/assets/web-panel/assets/index-D_1b7uh6.js +0 -55
  254. package/src/assets/web-panel/assets/index-D_e9gwfn.js +0 -1
  255. package/src/assets/web-panel/assets/index-DbxAlpPC.js +0 -21
  256. package/src/assets/web-panel/assets/index-DiK4vSZa.js +0 -1
  257. package/src/assets/web-panel/assets/index-DpYn5v2k.js +0 -1
  258. package/src/assets/web-panel/assets/index-diWkx2Wq.js +0 -1
  259. package/src/assets/web-panel/assets/motion-BalXv_60.js +0 -11
  260. package/src/assets/web-panel/assets/mtc-parser-CEQffxds.js +0 -1
@@ -76,9 +76,19 @@ function gitDir(root) {
76
76
  return path.resolve(root, git(["rev-parse", "--git-dir"], { cwd: root }));
77
77
  }
78
78
 
79
- /** Ref-safe session segment. */
79
+ /**
80
+ * Ref-safe session segment. Beyond the charset filter, enforce git's per-
81
+ * component ref rules so a legit-looking name never makes every checkpoint op
82
+ * throw: a component may not begin with `.`, contain `..`, or end with `.lock`
83
+ * (`/` is already collapsed to `-`, so the session is always a single segment
84
+ * and cannot traverse the ref namespace).
85
+ */
80
86
  function sanitizeSession(session) {
81
- const s = String(session || "default").replace(/[^A-Za-z0-9._-]/g, "-");
87
+ let s = String(session || "default").replace(/[^A-Za-z0-9._-]/g, "-");
88
+ s = s.replace(/\.{2,}/g, "."); // git forbids ".." in a refname
89
+ s = s.replace(/^\.+/, ""); // a component may not begin with "."
90
+ s = s.replace(/\.lock$/i, "-lock"); // nor end with ".lock"
91
+ s = s.replace(/\.+$/, ""); // nor end with a dot
82
92
  return s || "default";
83
93
  }
84
94
 
@@ -86,9 +96,20 @@ function sessionPrefix(session) {
86
96
  return `${REF_NS}/${sanitizeSession(session)}`;
87
97
  }
88
98
 
99
+ // Monotonic per-process counter so two temp-index paths minted in the same
100
+ // millisecond never collide. pid disambiguates across processes; Date.now() +
101
+ // this counter disambiguate within one. A collision would mean two operations
102
+ // sharing one GIT_INDEX_FILE (corrupt snapshot) or one's cleanup deleting the
103
+ // other's live index.
104
+ let _indexSeq = 0;
105
+
89
106
  /** Unique temp index path inside .git (never the real index). */
90
107
  function tempIndexPath(dir) {
91
- return path.join(dir, `cc-checkpoint-index-${process.pid}-${Date.now()}`);
108
+ _indexSeq = (_indexSeq + 1) >>> 0;
109
+ return path.join(
110
+ dir,
111
+ `cc-checkpoint-index-${process.pid}-${Date.now()}-${_indexSeq}`,
112
+ );
92
113
  }
93
114
 
94
115
  /**
@@ -580,4 +601,10 @@ export function clearCheckpoints(cwd = process.cwd(), opts = {}) {
580
601
  }
581
602
 
582
603
  // Exposed for unit tests / advanced callers.
583
- export const _internals = { git, repoRoot, sanitizeSession, snapshotTree };
604
+ export const _internals = {
605
+ git,
606
+ repoRoot,
607
+ sanitizeSession,
608
+ snapshotTree,
609
+ tempIndexPath,
610
+ };
@@ -29,6 +29,13 @@ export const AGENT_STATUS = {
29
29
  TIMEOUT: "timeout",
30
30
  };
31
31
 
32
+ // Bound how much of a child's stdout/stderr we retain in memory. A verbose or
33
+ // runaway `claude -p` can stream unbounded stream-json; without a cap,
34
+ // outputChunks.join("") OOMs the orchestrator. We keep the TAIL (oldest chunks
35
+ // dropped) because stream-json's terminal `result` / last-assistant line — the
36
+ // only thing _parseStreamJson needs — is at the end.
37
+ export const MAX_AGENT_OUTPUT_BYTES = 32 * 1024 * 1024;
38
+
32
39
  // ─── Detection ───────────────────────────────────────────────────
33
40
 
34
41
  /**
@@ -98,6 +105,7 @@ export class ClaudeCodeAgent extends EventEmitter {
98
105
  context = "",
99
106
  allowedTools = null,
100
107
  killGraceMs = 3000,
108
+ maxOutputBytes = MAX_AGENT_OUTPUT_BYTES,
101
109
  } = options;
102
110
 
103
111
  const fullPrompt = context
@@ -126,6 +134,19 @@ export class ClaudeCodeAgent extends EventEmitter {
126
134
  const outDecoder = new TextDecoder("utf-8");
127
135
  const errDecoder = new TextDecoder("utf-8");
128
136
  let timedOut = false;
137
+ // Tail-bounded byte counters for the two buffers (see MAX_AGENT_OUTPUT_BYTES).
138
+ let outBytes = 0;
139
+ let errBytes = 0;
140
+ let outputTruncated = false;
141
+ // Drop oldest chunks until the buffer is back under the cap, keeping ≥1
142
+ // chunk (the most recent, where the result line lives).
143
+ const trim = (chunks, bytesRef) => {
144
+ let bytes = bytesRef;
145
+ while (bytes > maxOutputBytes && chunks.length > 1) {
146
+ bytes -= Buffer.byteLength(chunks.shift());
147
+ }
148
+ return bytes;
149
+ };
129
150
 
130
151
  const proc = _deps.spawn(this.cliCommand, args, {
131
152
  cwd,
@@ -155,15 +176,26 @@ export class ClaudeCodeAgent extends EventEmitter {
155
176
  ? data
156
177
  : outDecoder.decode(data, { stream: true });
157
178
  outputChunks.push(chunk);
179
+ outBytes += Buffer.byteLength(chunk);
180
+ if (outBytes > maxOutputBytes) {
181
+ outputTruncated = true;
182
+ outBytes = trim(outputChunks, outBytes);
183
+ }
184
+ // The live `output` event still streams every chunk verbatim — the cap
185
+ // only bounds what we RETAIN for the final rawOutput.
158
186
  this.emit("output", { agentId: this.id, chunk });
159
187
  });
160
188
 
161
189
  proc.stderr.on("data", (data) => {
162
- errorChunks.push(
190
+ const chunk =
163
191
  typeof data === "string"
164
192
  ? data
165
- : errDecoder.decode(data, { stream: true }),
166
- );
193
+ : errDecoder.decode(data, { stream: true });
194
+ errorChunks.push(chunk);
195
+ errBytes += Buffer.byteLength(chunk);
196
+ if (errBytes > maxOutputBytes) {
197
+ errBytes = trim(errorChunks, errBytes);
198
+ }
167
199
  });
168
200
 
169
201
  proc.on("close", (code) => {
@@ -188,6 +220,7 @@ export class ClaudeCodeAgent extends EventEmitter {
188
220
  success: code === 0 && !timedOut,
189
221
  output: parsedOutput || rawOutput.slice(-4000), // last 4K chars fallback
190
222
  rawOutput,
223
+ outputTruncated, // true when the child exceeded maxOutputBytes
191
224
  exitCode: code,
192
225
  duration,
193
226
  timedOut,
@@ -257,6 +290,15 @@ export class ClaudeCodePool extends EventEmitter {
257
290
  this.model = options.model || null;
258
291
  this.agentTimeout = options.agentTimeout || 300_000;
259
292
 
293
+ // Bound retained completion history. dispatch() returns results directly,
294
+ // so _completed is only a recent-history buffer — but it was an unbounded
295
+ // array, so a long-lived pool dispatching many tasks leaked memory forever.
296
+ // Keep a recent window (FIFO). Default 1000; 0/invalid → 1000.
297
+ this.maxCompleted =
298
+ Number.isFinite(options.maxCompleted) && options.maxCompleted > 0
299
+ ? options.maxCompleted
300
+ : 1000;
301
+
260
302
  /** @type {Map<string, ClaudeCodeAgent>} */
261
303
  this._agents = new Map();
262
304
  this._completed = [];
@@ -315,6 +357,9 @@ export class ClaudeCodePool extends EventEmitter {
315
357
 
316
358
  this._agents.delete(agent.id);
317
359
  this._completed.push({ ...result, taskId: task.id });
360
+ if (this._completed.length > this.maxCompleted) {
361
+ this._completed.splice(0, this._completed.length - this.maxCompleted);
362
+ }
318
363
 
319
364
  this.emit("agent:complete", { taskId: task.id, ...result });
320
365
  return { taskId: task.id, ...result };
@@ -11,6 +11,16 @@ import { getConfigPath } from "./paths.js";
11
11
  import { DEFAULT_CONFIG } from "../constants.js";
12
12
  import { withFileLock } from "./with-file-lock.js";
13
13
 
14
+ // Warn at most once per config path per process. Injectable seam: the default
15
+ // stays silent under vitest so test output isn't polluted; tests override
16
+ // `_deps.warn` to assert it fired.
17
+ const _warnedConfigPaths = new Set();
18
+ export const _deps = {
19
+ warn: (msg) => {
20
+ if (!process.env.VITEST) process.stderr.write(msg);
21
+ },
22
+ };
23
+
14
24
  export function loadConfig() {
15
25
  const configPath = getConfigPath();
16
26
  if (!existsSync(configPath)) {
@@ -20,7 +30,21 @@ export function loadConfig() {
20
30
  const raw = readFileSync(configPath, "utf-8");
21
31
  const parsed = JSON.parse(raw);
22
32
  return deepMerge(structuredClone(DEFAULT_CONFIG), parsed);
23
- } catch {
33
+ } catch (err) {
34
+ // The file EXISTS but couldn't be read/parsed (a typo, trailing comma, or
35
+ // truncated write). Silently returning defaults drops the user's entire
36
+ // config — provider, model, baseUrl, API key — and falls back to the
37
+ // default provider, producing a baffling "configured X but it runs Y / says
38
+ // no API key". Warn once so the failure is visible; still fall back so cc
39
+ // keeps working.
40
+ if (!_warnedConfigPaths.has(configPath)) {
41
+ _warnedConfigPaths.add(configPath);
42
+ _deps.warn(
43
+ `⚠️ Could not read config at ${configPath} (${err.message}). ` +
44
+ `Using defaults — your saved settings (provider / model / API key) are being IGNORED. ` +
45
+ `Fix the JSON to restore them.\n`,
46
+ );
47
+ }
24
48
  return { ...structuredClone(DEFAULT_CONFIG) };
25
49
  }
26
50
  }
@@ -86,8 +86,19 @@ export class CostBudget {
86
86
  if (est.free) {
87
87
  this.sawFree = true;
88
88
  } else if (est.matched) {
89
- this.spentUsd = round(this.spentUsd + est.totalCost);
90
- this.priced = true;
89
+ // Defense-in-depth for a SAFETY control: a non-finite/negative cost (e.g.
90
+ // a malformed price table entry, or a non-numeric token count in a
91
+ // provider's usage event) must NOT poison spentUsd into NaN — `NaN >=
92
+ // limit` is always false, which would silently DISABLE the hard cap and
93
+ // allow unbounded spend on an unattended run. Only fold a clean cost;
94
+ // otherwise treat the record as unpriced.
95
+ const cost = Number(est.totalCost);
96
+ if (Number.isFinite(cost) && cost >= 0) {
97
+ this.spentUsd = round(this.spentUsd + cost);
98
+ this.priced = true;
99
+ } else {
100
+ this.sawUnpriced = true;
101
+ }
91
102
  } else if (tokens > 0) {
92
103
  this.sawUnpriced = true;
93
104
  }
@@ -25,8 +25,16 @@
25
25
  */
26
26
 
27
27
  import { createRequire } from "node:module";
28
+ import fsDefault from "node:fs";
29
+ import pathDefault from "node:path";
28
30
 
29
31
  const require_ = createRequire(import.meta.url);
32
+
33
+ // Injectable for tests (realpath of a symlink without touching the real fs).
34
+ export const _deps = {
35
+ realpathSync: fsDefault.realpathSync,
36
+ resolve: pathDefault.resolve,
37
+ };
30
38
  // Reuse the shell policy's compound-command splitter (separators + $()/backtick
31
39
  // extraction). It is regex-based on separators and leaves backslashes intact,
32
40
  // so Windows paths survive (unlike the escaping tokenizer, which strips `\`).
@@ -108,10 +116,43 @@ export function credentialFileReason(targetPath) {
108
116
  return null;
109
117
  }
110
118
 
119
+ /**
120
+ * Like credentialFileReason, but also follows a symlink to its real target and
121
+ * re-checks. The name-only guard is bypassable: `read_file({path:"notes.txt"})`
122
+ * where notes.txt → ~/.ssh/id_rsa reads the key while the guard sees only the
123
+ * innocent name. fs.readFileSync follows symlinks, so the guard must too —
124
+ * resolve the real path and re-check its basename/patterns. Falls back to the
125
+ * literal check when the path can't be resolved (non-existent → the read fails
126
+ * anyway, nothing to gate). `cwd` anchors a relative target before realpath.
127
+ *
128
+ * @param {string} targetPath
129
+ * @param {object} [opts] { cwd, deps }
130
+ * @returns {string|null}
131
+ */
132
+ export function credentialFileReasonResolved(targetPath, opts = {}) {
133
+ const direct = credentialFileReason(targetPath);
134
+ if (direct) return direct;
135
+ const deps = opts.deps || _deps;
136
+ const cwd = opts.cwd || process.cwd();
137
+ try {
138
+ const real = deps.realpathSync(
139
+ deps.resolve(cwd, String(targetPath || ".")),
140
+ );
141
+ const viaLink = credentialFileReason(real);
142
+ if (viaLink) return `${viaLink} (via symlink)`;
143
+ } catch {
144
+ // Unresolvable / non-existent — the read itself would fail; nothing to gate.
145
+ }
146
+ return null;
147
+ }
148
+
111
149
  // ── secret ENV-VAR + command detection ──────────────────────────────────────
112
150
 
113
151
  // Content-reader commands: when one of these is aimed at a credential file, the
114
- // file's bytes land in the agent's tool output.
152
+ // file's bytes land in the agent's tool output. Includes text processors
153
+ // (awk/sed), encoders (base64), and field/byte slicers — all of which dump a
154
+ // file's content just like `cat`, so an agent told to "read the .env" can't
155
+ // route around the guard with `base64 .env` / `awk '{print}' .env`.
115
156
  const READ_COMMANDS = new Set([
116
157
  "cat",
117
158
  "tac",
@@ -127,8 +168,31 @@ const READ_COMMANDS = new Set([
127
168
  "xxd",
128
169
  "od",
129
170
  "strings",
171
+ "awk",
172
+ "gawk",
173
+ "mawk",
174
+ "sed",
175
+ "base64",
176
+ "base32",
177
+ "cut",
178
+ "rev",
179
+ "sort",
180
+ "uniq",
181
+ "tr",
182
+ "fold",
183
+ "expand",
184
+ "unexpand",
185
+ "column",
186
+ "paste",
187
+ "pr",
130
188
  ]);
131
189
 
190
+ // grep-family is a content-reader too, but its FIRST non-flag arg is the search
191
+ // PATTERN (which may itself look credential-ish, e.g. `grep "id_rsa" notes.txt`)
192
+ // — so it is handled separately, checking only the FILE args after the pattern,
193
+ // to keep the guard precise (no false positive on the pattern).
194
+ const GREP_COMMANDS = new Set(["grep", "egrep", "fgrep", "rg", "ag"]);
195
+
132
196
  // Commands whose job is to print a value into output — the exfil surface for a
133
197
  // secret env var (a tool that merely CONSUMES `$API_KEY` does not echo it).
134
198
  const PRINT_COMMANDS = new Set([
@@ -243,6 +307,22 @@ export function commandReadsCredentials(command) {
243
307
  }
244
308
  }
245
309
 
310
+ // (b2) grep-family: skip the search pattern (first non-flag arg) and check
311
+ // only the file args, so `grep KEY .env` is caught but `grep "id_rsa"
312
+ // notes.txt` (searching FOR that text) is not a false positive.
313
+ if (GREP_COMMANDS.has(first)) {
314
+ for (const tok of nonFlagArgs.slice(1)) {
315
+ const r = credentialFileReason(tok);
316
+ if (r)
317
+ return {
318
+ reason: `reads ${r}`,
319
+ kind: "file",
320
+ target: tok,
321
+ segment: seg,
322
+ };
323
+ }
324
+ }
325
+
246
326
  // (c) printing / reading a secret-looking env var (via $VAR / %VAR% / env:).
247
327
  if (PRINT_COMMANDS.has(first) || READ_COMMANDS.has(first)) {
248
328
  const vars = referencedSecretVars(seg);
@@ -105,6 +105,11 @@ export function gitExec(args, cwd) {
105
105
  return execSync(`git ${args}`, {
106
106
  cwd,
107
107
  encoding: "utf-8",
108
+ // git diff/log/worktree-list output on a large repo easily exceeds
109
+ // execSync's 1 MB default → ENOBUFS. Match gitExecArgs' 64 MB ceiling so
110
+ // a big diff/log doesn't spuriously fail (e.g. worktree-diff's --numstat
111
+ // pass, which isn't wrapped in a degrade-gracefully try/catch).
112
+ maxBuffer: 64 * 1024 * 1024,
108
113
  stdio: ["pipe", "pipe", "pipe"],
109
114
  }).trim();
110
115
  } catch (err) {
@@ -188,7 +193,11 @@ export function gitAutoCommit(dir, message) {
188
193
  message ||
189
194
  `auto: ${status.files.length} file(s) changed — ${new Date().toISOString().slice(0, 16)}`;
190
195
 
191
- gitExec(`commit -m "${commitMsg.replace(/"/g, '\\"')}"`, dir);
196
+ // Free-text message → argv (no shell) so `$()` / backticks / `;` in a
197
+ // caller-supplied message (e.g. `cc git auto-commit --message …`) can't
198
+ // inject a command. The prior `-m "${msg.replace(/"/g,'\\"')}"` only escaped
199
+ // double quotes — same gap fixed in worktree-isolator / mergeWorktree.
200
+ gitExecArgs(["commit", "-m", commitMsg], dir);
192
201
 
193
202
  const hash = gitExec("rev-parse --short HEAD", dir);
194
203
 
@@ -209,12 +218,17 @@ export function gitLog(dir, limit = 10) {
209
218
  }
210
219
 
211
220
  try {
221
+ // Coerce `limit` to a bounded positive integer before it is interpolated
222
+ // into the shell string — a non-numeric value (`5; rm -rf ~`) would
223
+ // otherwise inject. The only in-tree caller already passes an int; this is
224
+ // defense-in-depth at the exported boundary.
225
+ const n = Math.min(10000, Math.max(1, Number.parseInt(limit, 10) || 10));
212
226
  // Subject (%s) is free text that often contains "|"; keep it LAST so a
213
227
  // pipe in the subject can't shift the date/author fields, then re-join the
214
228
  // remainder. (Field order kept as %X|%X — Windows cmd.exe expands adjacent
215
229
  // %VAR% pairs, so a control-char separator like %x1f is not portable here.)
216
230
  const output = gitExec(
217
- `log --oneline --no-decorate -${limit} --format="%H|%h|%ai|%an|%s"`,
231
+ `log --oneline --no-decorate -${n} --format="%H|%h|%ai|%an|%s"`,
218
232
  dir,
219
233
  );
220
234
  return output
@@ -124,6 +124,13 @@ export function createGoal(input = {}, opts = {}) {
124
124
  throw new Error("createGoal requires an objective");
125
125
  }
126
126
  const id = input.id || newId("goal");
127
+ // Generated ids are always safe, but `input.id` is caller-supplied — guard it
128
+ // like every other op (getGoal/saveGoal/deleteGoal) so a crafted id such as
129
+ // `../../etc/x` can't make atomicWriteFileSync write a .json OUTSIDE the
130
+ // goals dir. createGoal was the one mutation missing this check.
131
+ if (isUnsafeGoalId(id)) {
132
+ throw new Error(`非法 goal id: ${String(id).slice(0, 60)}`);
133
+ }
127
134
  const keyResults = (input.keyResults || []).map((kr) => normalizeKr(kr));
128
135
  const goal = {
129
136
  id,
@@ -207,6 +214,10 @@ function mutate(id, fn, opts = {}) {
207
214
  // sharing the goals dir) so concurrent goal edits don't lose an update.
208
215
  // Best-effort lock (with-file-lock): on contention timeout it proceeds anyway,
209
216
  // so the CLI never hangs.
217
+ // Guard before deriving the lock path: an unsafe id would otherwise place the
218
+ // lock file at a traversal path (`<root>/../../x.json.lock`) before getGoal
219
+ // even runs. getGoal/saveGoal also guard, but keep the lock inside the dir.
220
+ if (isUnsafeGoalId(id)) throw new Error(`no such goal: ${id}`);
210
221
  const root = opts.root || defaultRoot();
211
222
  return withFileLock(goalFile(root, id), () => {
212
223
  const goal = getGoal(id, opts);
@@ -285,6 +285,10 @@ export async function executeHook(hook, context = {}) {
285
285
  const output = execSync(cmd, {
286
286
  encoding: "utf-8",
287
287
  timeout: hook.timeout || 5000,
288
+ // execSync defaults maxBuffer to 1 MB; a hook that prints more (a
289
+ // linter/build dumping output) would throw ENOBUFS and be reported as a
290
+ // FAILURE even though it succeeded. Give hook output generous headroom.
291
+ maxBuffer: 16 * 1024 * 1024,
288
292
  env,
289
293
  });
290
294
  const executionTime = Date.now() - start;
@@ -159,7 +159,15 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
159
159
  },
160
160
  options.timeoutMs || 5 * 60 * 1000,
161
161
  );
162
- this._pending.set(requestId, { resolve, reject, timeoutId });
162
+ // `kind` tags what KIND of response settles this request ("question" vs
163
+ // "host-tool"). Checked in _resolvePending so a peer's session-answer can
164
+ // never resolve a host-tool call with a plain string (or vice versa).
165
+ this._pending.set(requestId, {
166
+ resolve,
167
+ reject,
168
+ timeoutId,
169
+ kind: options.kind || null,
170
+ });
163
171
 
164
172
  this._sendWs({
165
173
  ...message,
@@ -170,12 +178,15 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
170
178
  }
171
179
 
172
180
  _ask(questionType, question, extra = {}) {
173
- return this._request({
174
- type: "question",
175
- questionType,
176
- question,
177
- ...extra,
178
- });
181
+ return this._request(
182
+ {
183
+ type: "question",
184
+ questionType,
185
+ question,
186
+ ...extra,
187
+ },
188
+ { kind: "question" },
189
+ );
179
190
  }
180
191
 
181
192
  async askInput(question, options = {}) {
@@ -200,7 +211,7 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
200
211
  * Resolves the corresponding pending promise.
201
212
  */
202
213
  resolveAnswer(requestId, answer) {
203
- this._resolvePending(requestId, answer);
214
+ this._resolvePending(requestId, answer, "question");
204
215
  }
205
216
 
206
217
  async requestHostTool(toolName, args = {}, extra = {}) {
@@ -211,12 +222,12 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
211
222
  args,
212
223
  ...extra,
213
224
  },
214
- { timeoutMs: extra.timeoutMs || 60 * 1000 },
225
+ { timeoutMs: extra.timeoutMs || 60 * 1000, kind: "host-tool" },
215
226
  );
216
227
  }
217
228
 
218
229
  resolveHostTool(requestId, payload) {
219
- this._resolvePending(requestId, payload);
230
+ this._resolvePending(requestId, payload, "host-tool");
220
231
  }
221
232
 
222
233
  rejectAllPending(reason = createAbortError("Interaction interrupted")) {
@@ -310,11 +321,21 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
310
321
  }
311
322
  }
312
323
 
313
- _resolvePending(requestId, payload) {
324
+ _resolvePending(requestId, payload, expectedKind = null) {
314
325
  const pending = this._pending.get(requestId);
315
326
  if (!pending) {
316
327
  return;
317
328
  }
329
+ // Kind guard: a question-answer must not settle a host-tool request (or
330
+ // vice versa). On a mismatch, IGNORE the message — leave the request pending
331
+ // so its correct resolver (or its timeout) can still settle it. Prevents a
332
+ // peer from resolving a host-tool call with a plain question string, or a
333
+ // question with a structured tool payload (type confusion of the awaited
334
+ // value). Backward-compatible: callers passing no expectedKind, and
335
+ // pre-kind pending entries, behave exactly as before.
336
+ if (expectedKind && pending.kind && pending.kind !== expectedKind) {
337
+ return;
338
+ }
318
339
 
319
340
  this._pending.delete(requestId);
320
341
  if (pending.timeoutId) {
@@ -0,0 +1,147 @@
1
+ /**
2
+ * JetBrains / IntelliJ IDEA built-in MCP discovery (cc-side).
3
+ *
4
+ * IntelliJ IDEA 2025.2+ ships a built-in MCP server (Settings | Tools | MCP
5
+ * Server) exposing the IDE's own indexed operations (find usages, file-by-path,
6
+ * search, run configs, VCS, …). Letting the agent call those instead of reading
7
+ * and grepping files itself is cheaper (fewer tokens) and faster (rides the IDE
8
+ * index) — that is the whole point of this bridge.
9
+ *
10
+ * Unlike our own ide/pdh bridges (ide-bridge.js / pdh-bridge.js), IDEA writes NO
11
+ * discovery lockfile, so cc cannot scan for it. Instead the ChainlessChain
12
+ * JetBrains plugin — which runs INSIDE the IDE and therefore knows whether MCP
13
+ * is supported (IDE >= 2025.2) and enabled, and on which endpoint — injects the
14
+ * exact endpoint into the env of the `cc agent` it spawns:
15
+ *
16
+ * CHAINLESSCHAIN_JETBRAINS_MCP_URL http://127.0.0.1:<port>/<path>
17
+ * CHAINLESSCHAIN_JETBRAINS_TOKEN optional bearer token
18
+ * CHAINLESSCHAIN_JETBRAINS_TRANSPORT optional: http | https | sse (else inferred)
19
+ *
20
+ * When the IDE does NOT support MCP (older than 2025.2) or it is disabled, the
21
+ * plugin injects nothing -> cc connects nothing. This is the product decision:
22
+ * if the IDE's MCP is unsupported, do not try to connect (no port scan, no
23
+ * external proxy fallback). Reserved server name `idea` -> tools `mcp__idea__*`.
24
+ *
25
+ * Pure CLI, no network scan, no external dependency: a deterministic
26
+ * env -> MCP-config mapping, fully unit-testable. The connect path reuses the
27
+ * existing MCP client (Streamable-HTTP) via mcp-config.js `loadJetbrainsMcp`.
28
+ */
29
+
30
+ /** Transports the CLI's MCP client can actually talk (see mcp-client.js). */
31
+ const SUPPORTED_TRANSPORTS = new Set(["http", "https", "sse"]);
32
+
33
+ /**
34
+ * Only ever connect to a loopback endpoint. IDEA's built-in server binds
35
+ * localhost; refusing anything else keeps an injected env var from pointing cc
36
+ * at an arbitrary host. `new URL(...).hostname` strips the brackets from an
37
+ * IPv6 literal on most Node builds but keeps them on some — accept both forms.
38
+ */
39
+ function isLoopbackUrl(url) {
40
+ try {
41
+ const h = new URL(url).hostname.toLowerCase();
42
+ return (
43
+ h === "127.0.0.1" || h === "::1" || h === "[::1]" || h === "localhost"
44
+ );
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ function inferTransportFromUrl(url) {
51
+ if (/\/sse(\b|\/|$)/i.test(url)) return "sse";
52
+ if (/^https:/i.test(url)) return "https";
53
+ if (/^http:/i.test(url)) return "http";
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Are we running under the ChainlessChain JetBrains plugin with IDE MCP
59
+ * available? True iff the plugin injected an MCP URL — which it only does when
60
+ * the IDE is >= 2025.2 and the MCP server is enabled. No URL = treat as
61
+ * unsupported and stay out of the way.
62
+ */
63
+ export function isInJetbrainsContext(env = process.env) {
64
+ return !!(env && env.CHAINLESSCHAIN_JETBRAINS_MCP_URL);
65
+ }
66
+
67
+ /**
68
+ * Resolve the IDEA built-in MCP server to connect to, or null.
69
+ *
70
+ * Deterministic: the plugin-injected `CHAINLESSCHAIN_JETBRAINS_MCP_URL` (a
71
+ * loopback http/https/sse endpoint) is the single source of truth. A missing,
72
+ * non-loopback, or unsupported-transport URL yields null (don't connect).
73
+ *
74
+ * @param {object} opts { env? }
75
+ * @returns {{url:string, transport:string, token:(string|null)}|null}
76
+ */
77
+ export function discoverJetbrainsServer({ env = process.env } = {}) {
78
+ const url = env && env.CHAINLESSCHAIN_JETBRAINS_MCP_URL;
79
+ if (!url || !isLoopbackUrl(url)) return null;
80
+
81
+ const transport = String(
82
+ env.CHAINLESSCHAIN_JETBRAINS_TRANSPORT || inferTransportFromUrl(url) || "",
83
+ ).toLowerCase();
84
+ if (!SUPPORTED_TRANSPORTS.has(transport)) return null;
85
+
86
+ return {
87
+ url,
88
+ transport,
89
+ token:
90
+ typeof env.CHAINLESSCHAIN_JETBRAINS_TOKEN === "string"
91
+ ? env.CHAINLESSCHAIN_JETBRAINS_TOKEN
92
+ : null,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * A discovered server -> an MCP server config row for `setupMcpFromConfig`.
98
+ * `longRunning` exempts it from the agent loop's per-call timeout (some IDE
99
+ * operations — e.g. an indexing-gated search — can take a while), matching the
100
+ * ide/pdh bridges.
101
+ */
102
+ export function jetbrainsServerToMcpConfig(found) {
103
+ if (!found || !found.url) return null;
104
+ const headers = {};
105
+ if (found.token) headers.Authorization = `Bearer ${found.token}`;
106
+ return {
107
+ url: found.url,
108
+ transport: found.transport,
109
+ headers,
110
+ longRunning: true,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Human-readable explanation of why discovery did / didn't pick a server —
116
+ * backs `cc ide jetbrains`. Never surfaces the raw token.
117
+ */
118
+ export function diagnoseJetbrains({ env = process.env } = {}) {
119
+ const rawUrl = (env && env.CHAINLESSCHAIN_JETBRAINS_MCP_URL) || null;
120
+ const chosen = discoverJetbrainsServer({ env });
121
+
122
+ let reason;
123
+ if (chosen) {
124
+ reason = "CHAINLESSCHAIN_JETBRAINS_MCP_URL injected by the IDE plugin";
125
+ } else if (!rawUrl) {
126
+ reason =
127
+ "no CHAINLESSCHAIN_JETBRAINS_MCP_URL — IDE MCP is unsupported (IDEA < " +
128
+ "2025.2) / disabled, or cc was not launched from the JetBrains plugin";
129
+ } else if (!isLoopbackUrl(rawUrl)) {
130
+ reason =
131
+ "injected URL is not a loopback (127.0.0.1/::1/localhost) endpoint";
132
+ } else {
133
+ reason = "injected URL is not a supported transport (http/https/sse)";
134
+ }
135
+
136
+ return {
137
+ supported: !!rawUrl,
138
+ chosen: chosen
139
+ ? {
140
+ url: chosen.url,
141
+ transport: chosen.transport,
142
+ hasToken: !!chosen.token,
143
+ }
144
+ : null,
145
+ reason,
146
+ };
147
+ }
@@ -30,5 +30,7 @@ export {
30
30
  validateAllJsonlSessions,
31
31
  sampleMigratedSessionsValidation,
32
32
  sessionPath,
33
+ isUnsafeSessionId,
33
34
  appendTokenUsage,
35
+ toIsoSafe,
34
36
  } from "../harness/jsonl-session-store.js";