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
@@ -21,7 +21,32 @@ function getSessionsDir() {
21
21
  return dir;
22
22
  }
23
23
 
24
+ /**
25
+ * A session id must be a single safe path segment. Ids are generated as
26
+ * `session-<ts>-<hex>`, but also arrive from CLI args (`cc agent --resume <id>`,
27
+ * `cc insights <id>`, `cc session show <id>`), so an id like `../../etc/x` would
28
+ * otherwise let sessionPath() read / append / delete a .jsonl OUTSIDE the
29
+ * sessions dir. Reject any separator or `..` (mirrors goal-store's
30
+ * isUnsafeGoalId / FileUploadService.isUnsafeSegment).
31
+ */
32
+ export function isUnsafeSessionId(id) {
33
+ return (
34
+ id == null ||
35
+ id === "" ||
36
+ typeof id !== "string" ||
37
+ id.includes("/") ||
38
+ id.includes("\\") ||
39
+ id.includes("..")
40
+ );
41
+ }
42
+
24
43
  export function sessionPath(sessionId) {
44
+ // Fail closed for path building: every write/delete goes through here, so a
45
+ // traversal id can never escape the sessions dir. Reads guard separately and
46
+ // degrade to not-found instead of throwing.
47
+ if (isUnsafeSessionId(sessionId)) {
48
+ throw new Error(`unsafe session id: ${String(sessionId).slice(0, 60)}`);
49
+ }
25
50
  return join(getSessionsDir(), `${sessionId}.jsonl`);
26
51
  }
27
52
 
@@ -69,6 +94,7 @@ export function appendCompactEvent(sessionId, stats) {
69
94
  }
70
95
 
71
96
  export function readEvents(sessionId) {
97
+ if (isUnsafeSessionId(sessionId)) return []; // traversal id → treat as empty
72
98
  const filePath = sessionPath(sessionId);
73
99
  if (!existsSync(filePath)) return [];
74
100
 
@@ -87,20 +113,36 @@ export function readEvents(sessionId) {
87
113
  return events;
88
114
  }
89
115
 
116
+ /**
117
+ * A replayable chat message must be a `{ role, content }` object — guard
118
+ * against a corrupt / partially-written / hand-edited event whose `data` is
119
+ * missing, null, or not a message (it would otherwise inject `undefined` into
120
+ * the resumed history and break the next LLM request).
121
+ */
122
+ function isReplayableMessage(m) {
123
+ return Boolean(m) && typeof m === "object" && typeof m.role === "string";
124
+ }
125
+
90
126
  export function rebuildMessages(sessionId) {
91
127
  const events = readEvents(sessionId);
92
128
  const messages = [];
93
129
  let lastCompactIndex = -1;
94
130
 
131
+ // A `compact` event carries the pre-compaction snapshot in data.messages. A
132
+ // malformed compact line (type present but data null / messages not an array)
133
+ // must not crash the resume — skip it and look further back.
95
134
  for (let i = events.length - 1; i >= 0; i--) {
96
- if (events[i].type === "compact" && events[i].data.messages) {
135
+ const e = events[i];
136
+ if (e && e.type === "compact" && Array.isArray(e.data?.messages)) {
97
137
  lastCompactIndex = i;
98
138
  break;
99
139
  }
100
140
  }
101
141
 
102
- if (lastCompactIndex >= 0 && events[lastCompactIndex].data.messages) {
103
- messages.push(...events[lastCompactIndex].data.messages);
142
+ if (lastCompactIndex >= 0) {
143
+ for (const m of events[lastCompactIndex].data.messages) {
144
+ if (isReplayableMessage(m)) messages.push(m);
145
+ }
104
146
  }
105
147
 
106
148
  const startIndex = lastCompactIndex >= 0 ? lastCompactIndex + 1 : 0;
@@ -108,9 +150,11 @@ export function rebuildMessages(sessionId) {
108
150
  for (let i = startIndex; i < events.length; i++) {
109
151
  const event = events[i];
110
152
  if (
111
- event.type === "user_message" ||
112
- event.type === "assistant_message" ||
113
- event.type === "system"
153
+ event &&
154
+ (event.type === "user_message" ||
155
+ event.type === "assistant_message" ||
156
+ event.type === "system") &&
157
+ isReplayableMessage(event.data)
114
158
  ) {
115
159
  messages.push(event.data);
116
160
  }
@@ -119,6 +163,19 @@ export function rebuildMessages(sessionId) {
119
163
  return messages;
120
164
  }
121
165
 
166
+ /** ISO string for a numeric ms timestamp, or "" when missing / non-finite /
167
+ * invalid — `new Date(undefined).toISOString()` (and `new Date("garbage")`)
168
+ * throw "Invalid time value", and one corrupt event must not crash a whole
169
+ * `cc session list` / `cc session search`. Exported so command-layer readers of
170
+ * the same (hand-editable) JSONL share the guard. */
171
+ export function toIsoSafe(ts) {
172
+ if (ts == null) return ""; // null/undefined → "" (Number(null) is 0 = epoch)
173
+ const n = Number(ts);
174
+ if (!Number.isFinite(n)) return "";
175
+ const d = new Date(n);
176
+ return Number.isNaN(d.getTime()) ? "" : d.toISOString();
177
+ }
178
+
122
179
  export function listJsonlSessions(options = {}) {
123
180
  const dir = getSessionsDir();
124
181
  if (!existsSync(dir)) return [];
@@ -141,12 +198,8 @@ export function listJsonlSessions(options = {}) {
141
198
  provider: startEvent?.data?.provider || "",
142
199
  model: startEvent?.data?.model || "",
143
200
  message_count: messageCount,
144
- created_at: startEvent
145
- ? new Date(startEvent.timestamp).toISOString()
146
- : "",
147
- updated_at: lastEvent
148
- ? new Date(lastEvent.timestamp).toISOString()
149
- : "",
201
+ created_at: toIsoSafe(startEvent?.timestamp),
202
+ updated_at: toIsoSafe(lastEvent?.timestamp),
150
203
  _lastTs: lastEvent?.timestamp || 0,
151
204
  _eventCount: events.length,
152
205
  };
@@ -184,6 +237,7 @@ export function forkSession(sourceId) {
184
237
  }
185
238
 
186
239
  export function sessionExists(sessionId) {
240
+ if (isUnsafeSessionId(sessionId)) return false; // never resolve a traversal id
187
241
  return existsSync(sessionPath(sessionId));
188
242
  }
189
243
 
@@ -266,6 +320,15 @@ export function migrateLegacySessionFile(filePath, options = {}) {
266
320
  }
267
321
 
268
322
  export function validateJsonlSession(sessionId) {
323
+ if (isUnsafeSessionId(sessionId)) {
324
+ return {
325
+ sessionId,
326
+ valid: false,
327
+ reason: "invalid session id",
328
+ malformedLines: 0,
329
+ eventCount: 0,
330
+ };
331
+ }
269
332
  const filePath = sessionPath(sessionId);
270
333
  if (!existsSync(filePath)) {
271
334
  return {
@@ -335,6 +398,21 @@ function performLegacySessionMigration(sourcePath, options) {
335
398
  const legacy = normalizeLegacySession(parsed, basename(sourcePath, ".json"));
336
399
  const sessionId = legacy.id;
337
400
 
401
+ // A legacy file carries its OWN `id` (payload.id), so a crafted file could
402
+ // name a traversal target like "../../evil". sessionPath() throws on write
403
+ // (the backstop), but fail-fast HERE with a clear reason so the migration
404
+ // doesn't burn retry attempts on a deterministic error.
405
+ if (isUnsafeSessionId(sessionId)) {
406
+ return {
407
+ file: sourcePath,
408
+ sessionId,
409
+ migrated: false,
410
+ failed: true,
411
+ dryRun: Boolean(options.dryRun),
412
+ reason: "unsafe session id in legacy file",
413
+ };
414
+ }
415
+
338
416
  if (!options.force && sessionExists(sessionId)) {
339
417
  return {
340
418
  file: sourcePath,
@@ -814,19 +814,18 @@ export class MCPClient extends EventEmitter {
814
814
  const contentType = response.headers?.get
815
815
  ? String(response.headers.get("content-type") || "").toLowerCase()
816
816
  : "";
817
+ // Bound the response body the same way the stdio path bounds its line
818
+ // buffer (per-server maxBufferChars override; 0 disables).
819
+ const cap = Number.isFinite(entry.config?.maxBufferChars)
820
+ ? entry.config.maxBufferChars
821
+ : MCP_MAX_BUFFER_CHARS;
817
822
 
818
823
  let envelope;
819
824
  if (contentType.includes("text/event-stream")) {
820
- envelope = await _extractSseResponse(response, id);
825
+ envelope = await _extractSseResponse(response, id, cap);
821
826
  } else {
822
- envelope =
823
- typeof response.json === "function"
824
- ? await response.json()
825
- : JSON.parse(
826
- typeof response.text === "function"
827
- ? await response.text()
828
- : "",
829
- );
827
+ const text = await _readBodyCapped(response, cap);
828
+ envelope = text ? JSON.parse(text) : null;
830
829
  }
831
830
 
832
831
  if (!envelope || typeof envelope !== "object") {
@@ -957,28 +956,70 @@ export class MCPClient extends EventEmitter {
957
956
  }
958
957
 
959
958
  /**
960
- * Parse a `text/event-stream` HTTP response body and return the first
961
- * JSON-RPC envelope whose id matches `requestId`. Tolerates multiple
962
- * `data:` chunks, comments, and non-JSON-RPC events.
959
+ * Read an HTTP response body to text with a hard size cap, mirroring the stdio
960
+ * transport's MCP_MAX_BUFFER_CHARS guard. The per-call timeout bounds TIME but
961
+ * not MEMORY: a malicious/buggy HTTP MCP server could stream a multi-GB body
962
+ * within the timeout and OOM the client. When the body is a readable stream
963
+ * (real fetch) we accumulate with a running cap and cancel on overflow; for a
964
+ * non-stream response (test mocks) we fall back to text() + a post-hoc check.
965
+ * A declared Content-Length over the cap is rejected before any read. cap<=0
966
+ * disables the limit.
963
967
  */
964
- async function _extractSseResponse(response, requestId) {
965
- let text;
966
- if (typeof response.text === "function") {
967
- text = await response.text();
968
- } else if (response.body && typeof response.body.getReader === "function") {
968
+ async function _readBodyCapped(response, cap) {
969
+ if (
970
+ cap > 0 &&
971
+ response.headers &&
972
+ typeof response.headers.get === "function"
973
+ ) {
974
+ const declared = Number(response.headers.get("content-length"));
975
+ if (Number.isFinite(declared) && declared > cap) {
976
+ throw new Error(
977
+ `MCP HTTP response exceeds the ${cap}-byte cap (content-length ${declared})`,
978
+ );
979
+ }
980
+ }
981
+ if (response.body && typeof response.body.getReader === "function") {
969
982
  const reader = response.body.getReader();
970
983
  const decoder = new TextDecoder("utf-8");
971
984
  const chunks = [];
972
- while (true) {
985
+ let total = 0;
986
+ for (;;) {
973
987
  const { value, done } = await reader.read();
974
988
  if (done) break;
989
+ total += value && value.length ? value.length : 0;
990
+ if (cap > 0 && total > cap) {
991
+ try {
992
+ await reader.cancel();
993
+ } catch {
994
+ /* best-effort — the throw below is what matters */
995
+ }
996
+ throw new Error(
997
+ `MCP HTTP response exceeded the ${cap}-byte cap (runaway server)`,
998
+ );
999
+ }
975
1000
  chunks.push(decoder.decode(value, { stream: true }));
976
1001
  }
977
1002
  chunks.push(decoder.decode());
978
- text = chunks.join("");
979
- } else {
980
- throw new Error("SSE response is not readable");
1003
+ return chunks.join("");
1004
+ }
1005
+ if (typeof response.text !== "function") {
1006
+ throw new Error("HTTP response is not readable");
981
1007
  }
1008
+ const text = await response.text();
1009
+ if (cap > 0 && text.length > cap) {
1010
+ throw new Error(`MCP HTTP response exceeded the ${cap}-char cap`);
1011
+ }
1012
+ return text;
1013
+ }
1014
+
1015
+ /**
1016
+ * Parse a `text/event-stream` HTTP response body and return the first
1017
+ * JSON-RPC envelope whose id matches `requestId`. Tolerates multiple
1018
+ * `data:` chunks, comments, and non-JSON-RPC events. `cap` bounds the body
1019
+ * size (see _readBodyCapped).
1020
+ */
1021
+ async function _extractSseResponse(response, requestId, cap = 0) {
1022
+ const text = await _readBodyCapped(response, cap);
982
1023
 
983
1024
  // Split into events on blank line, parse each event's concatenated `data:` lines.
984
1025
  const events = text.split(/\r?\n\r?\n/);
@@ -12,6 +12,12 @@
12
12
  import { createHash } from "node:crypto";
13
13
  import { feature, featureVariant } from "../lib/feature-flags.js";
14
14
 
15
+ // Bounds for the fuzzy near-dup pass in _deduplicate (see there). Generous
16
+ // enough that real near-dups are still caught; small enough that compaction of
17
+ // a long, tool-heavy history stays sub-second instead of O(n²·content).
18
+ const DEDUP_JACCARD_MAX_CHARS = 4000;
19
+ const DEDUP_FUZZY_WINDOW = 100;
20
+
15
21
  export function estimateTokens(text) {
16
22
  if (!text) return 0;
17
23
  const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
@@ -320,8 +326,18 @@ export class PromptCompressor {
320
326
  const last = [...messages].reverse().find((m) => m.role === "user");
321
327
  const rest = messages.filter((m) => m.role !== "system" && m !== last);
322
328
 
329
+ // Bound the fuzzy pass so it stays usable on the very conversations
330
+ // compaction targets (long + tool-heavy). The naive version compared every
331
+ // message against EVERY prior one (O(n²)) and ran jaccard over full content
332
+ // (O(content) per compare) — 300 msgs × ~8 KB tool results blocked ~3.4 s.
333
+ // • Exact dedup (the md5 hash set) still covers ALL messages.
334
+ // • The fuzzy near-dup compare uses a capped prefix (char-set jaccard is
335
+ // alphabet-bounded, so a prefix barely changes the decision) and only the
336
+ // most-recent kept entries (near-dups cluster; this can only UNDER-dedup,
337
+ // never drop a genuinely-distinct message).
323
338
  const seen = new Map();
324
339
  const deduped = [];
340
+ const recent = []; // rolling window of recent kept {capped} contents
325
341
 
326
342
  for (const msg of rest) {
327
343
  const content = getContent(msg);
@@ -329,12 +345,13 @@ export class PromptCompressor {
329
345
 
330
346
  if (seen.has(hash)) continue;
331
347
 
348
+ const capped =
349
+ content.length > DEDUP_JACCARD_MAX_CHARS
350
+ ? content.slice(0, DEDUP_JACCARD_MAX_CHARS)
351
+ : content;
332
352
  let isDup = false;
333
- for (const [, existing] of seen) {
334
- if (
335
- jaccardSimilarity(content, getContent(existing)) >=
336
- this.similarityThreshold
337
- ) {
353
+ for (const prev of recent) {
354
+ if (jaccardSimilarity(capped, prev) >= this.similarityThreshold) {
338
355
  isDup = true;
339
356
  break;
340
357
  }
@@ -343,6 +360,8 @@ export class PromptCompressor {
343
360
  if (!isDup) {
344
361
  seen.set(hash, msg);
345
362
  deduped.push(msg);
363
+ recent.push(capped);
364
+ if (recent.length > DEDUP_FUZZY_WINDOW) recent.shift();
346
365
  }
347
366
  }
348
367
 
@@ -462,7 +462,11 @@ export function applyWorktreeAutomationCandidate(
462
462
  const commitMessage =
463
463
  options.commitMessage ||
464
464
  `Resolve ${filePath} via ${candidate.label || candidateId}`;
465
- gitExec(`commit -m "${commitMessage.replace(/"/g, '\\"')}"`, worktree.path);
465
+ // Free-text message → argv (no shell) so `$()` / backticks / quotes in a
466
+ // caller-supplied commitMessage can't inject a command. The prior
467
+ // `-m "${msg.replace(/"/g,'\\"')}"` only neutralized double quotes — the
468
+ // same gap already fixed in mergeWorktree.
469
+ gitExecArgs(["commit", "-m", commitMessage], worktree.path);
466
470
 
467
471
  const diff = diffWorktree(repoDir, branchName, { baseBranch });
468
472
  const filesChanged = diff.summary?.filesChanged || 0;
@@ -546,6 +550,11 @@ function _hasUncommittedChanges(worktreePath) {
546
550
  const output = execSync("git status --porcelain", {
547
551
  cwd: worktreePath,
548
552
  encoding: "utf-8",
553
+ // A worktree with many dirty files produces >1 MB of porcelain output and
554
+ // would ENOBUFS on the default cap — the catch below then reports "clean"
555
+ // (fail-OPEN), which could let a caller discard a worktree full of
556
+ // uncommitted work. Give it the same 64 MB headroom as gitExec.
557
+ maxBuffer: 64 * 1024 * 1024,
549
558
  });
550
559
  return output.trim().length > 0;
551
560
  } catch (_e) {
@@ -28,6 +28,8 @@ export {
28
28
  listBackgroundShellTasks,
29
29
  killBackgroundShellTask,
30
30
  killAllBackgroundShellTasks,
31
+ reapIdleBackgroundShellTasks,
32
+ _ingestSearchOutput,
31
33
  writeFileVerified,
32
34
  formatProviderHttpError,
33
35
  _accumulateOllamaStream,
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import crypto from "crypto";
7
+ import { safeJsonParse } from "./safe-json.js";
7
8
 
8
9
  /**
9
10
  * Event types for audit logging.
@@ -232,7 +233,8 @@ export function queryLogs(db, filters = {}) {
232
233
  const rows = db.prepare(sql).all(...params);
233
234
  return rows.map((r) => ({
234
235
  ...r,
235
- details: r.details ? JSON.parse(r.details) : null,
236
+ // One corrupt details cell must not throw out of the whole query.
237
+ details: safeJsonParse(r.details, null),
236
238
  success: r.success === 1,
237
239
  }));
238
240
  }
@@ -48,6 +48,12 @@ export class CLIAutonomousAgent extends EventEmitter {
48
48
  this._initialized = false;
49
49
  this._maxIterations = 20;
50
50
  this._maxRetries = 3;
51
+ // Cap retained goal history. submitGoal stores every goal in _goals and
52
+ // nothing removes it; the REPL keeps one long-lived agent for the whole
53
+ // session, so goals (each holding steps/errors/result) accumulate without
54
+ // bound — and listGoals() returns the whole growing Map. Prune the oldest
55
+ // TERMINAL goals beyond this cap; running/pending/paused goals are kept.
56
+ this._maxGoals = 200;
51
57
  }
52
58
 
53
59
  /**
@@ -58,12 +64,14 @@ export class CLIAutonomousAgent extends EventEmitter {
58
64
  toolExecutor,
59
65
  hookManager,
60
66
  maxIterations,
67
+ maxGoals,
61
68
  iterationBudget,
62
69
  } = {}) {
63
70
  this._llmChat = llmChat || null;
64
71
  this._toolExecutor = toolExecutor || null;
65
72
  this._hookManager = hookManager || null;
66
73
  if (maxIterations) this._maxIterations = maxIterations;
74
+ if (Number.isFinite(maxGoals) && maxGoals > 0) this._maxGoals = maxGoals;
67
75
  this._iterationBudget = iterationBudget || null; // shared budget from caller
68
76
  this._initialized = true;
69
77
  }
@@ -96,6 +104,7 @@ export class CLIAutonomousAgent extends EventEmitter {
96
104
  };
97
105
 
98
106
  this._goals.set(goalId, goal);
107
+ this._pruneGoals();
99
108
  this.emit("goal:submitted", { goalId, description });
100
109
 
101
110
  // Start the ReAct loop asynchronously
@@ -108,6 +117,26 @@ export class CLIAutonomousAgent extends EventEmitter {
108
117
  return { goalId };
109
118
  }
110
119
 
120
+ /**
121
+ * Bound retained goal history to _maxGoals. Evicts the oldest TERMINAL goals
122
+ * (completed/failed/cancelled) FIFO; a still-active goal (pending/running/
123
+ * paused) is never dropped, so a burst of concurrent goals can briefly exceed
124
+ * the cap rather than losing live work.
125
+ */
126
+ _pruneGoals() {
127
+ if (this._goals.size <= this._maxGoals) return;
128
+ for (const [id, g] of this._goals) {
129
+ if (this._goals.size <= this._maxGoals) break;
130
+ if (
131
+ g.status === GoalStatus.COMPLETED ||
132
+ g.status === GoalStatus.FAILED ||
133
+ g.status === GoalStatus.CANCELLED
134
+ ) {
135
+ this._goals.delete(id);
136
+ }
137
+ }
138
+ }
139
+
111
140
  /**
112
141
  * Pause a running goal.
113
142
  */
@@ -13,8 +13,8 @@ import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
13
13
  import { appendTokenUsage } from "../harness/jsonl-session-store.js";
14
14
  import {
15
15
  isRetryableStreamError,
16
- STREAM_RETRY_MAX,
17
16
  STREAM_RETRY_BASE_MS,
17
+ resolveStreamRetryMax,
18
18
  } from "./stream-retry.js";
19
19
 
20
20
  // A streaming chat call must not hang forever if the API accepts the connection
@@ -513,6 +513,9 @@ export async function* chatStream(messages, options) {
513
513
  // any token still terminates the generator cleanly.
514
514
  const streamPromise = (async () => {
515
515
  try {
516
+ // Budget honors CC_MAX_RETRIES / CLAUDE_CODE_MAX_RETRIES (capped 15),
517
+ // same as the agent path.
518
+ const maxRetries = resolveStreamRetryMax();
516
519
  let attempt = 0;
517
520
  for (;;) {
518
521
  try {
@@ -524,7 +527,7 @@ export async function* chatStream(messages, options) {
524
527
  // zero-output retry safety (cc agent parity). Stall aborts (180s) and
525
528
  // HTTP/auth errors are not retryable and surface immediately.
526
529
  if (
527
- attempt >= STREAM_RETRY_MAX ||
530
+ attempt >= maxRetries ||
528
531
  tokensEmitted > 0 ||
529
532
  !isRetryableStreamError(err)
530
533
  ) {
@@ -22,6 +22,40 @@ import { firstBalancedJson } from "./json-schema-output.js";
22
22
 
23
23
  const DEFAULT_INTENT_TIMEOUT_MS = 15000;
24
24
 
25
+ /**
26
+ * Resolve the per-call intent-classification budget. `llmOptions.intentTimeoutMs`
27
+ * overrides the default; an explicit 0 disables the cap; NaN/negative/absent →
28
+ * default.
29
+ */
30
+ function resolveIntentTimeout(llmOptions) {
31
+ const raw = llmOptions?.intentTimeoutMs;
32
+ if (raw === 0) return 0; // explicit disable
33
+ const n = Number(raw);
34
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_INTENT_TIMEOUT_MS;
35
+ }
36
+
37
+ /**
38
+ * Bound an LLM call to the intent budget. chat-core has its OWN stall guard, but
39
+ * it only fires on SILENCE (no bytes for 180s) — a slow trickling stream never
40
+ * trips it, so without this the declared 15s intent budget was never enforced
41
+ * and classification could block for minutes. Racing the deadline makes it fail
42
+ * fast into the caller's graceful rule/verbatim fallback. `ms <= 0` disables.
43
+ * The losing background request is harmless (chat-core releases its own socket);
44
+ * the timer is unref'd so it never holds the process open.
45
+ */
46
+ function withIntentTimeout(promise, ms) {
47
+ if (!(Number(ms) > 0)) return promise;
48
+ let timer;
49
+ const timeout = new Promise((_resolve, reject) => {
50
+ timer = setTimeout(
51
+ () => reject(new Error(`intent LLM call timed out after ${ms}ms`)),
52
+ Number(ms),
53
+ );
54
+ if (timer && typeof timer.unref === "function") timer.unref();
55
+ });
56
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
57
+ }
58
+
25
59
  /**
26
60
  * Build the V5 understandIntent system + user prompt pair.
27
61
  *
@@ -123,17 +157,20 @@ export async function understandIntent({
123
157
  );
124
158
 
125
159
  try {
126
- const fullContent = await chatWithStreaming(
127
- [
128
- { role: "system", content: systemPrompt },
129
- { role: "user", content: userPrompt },
130
- ],
131
- {
132
- ...llmOptions,
133
- // Lower temperature → more deterministic JSON output.
134
- temperature: 0.3,
135
- maxTokens: 500,
136
- },
160
+ const fullContent = await withIntentTimeout(
161
+ chatWithStreaming(
162
+ [
163
+ { role: "system", content: systemPrompt },
164
+ { role: "user", content: userPrompt },
165
+ ],
166
+ {
167
+ ...llmOptions,
168
+ // Lower temperature → more deterministic JSON output.
169
+ temperature: 0.3,
170
+ maxTokens: 500,
171
+ },
172
+ ),
173
+ resolveIntentTimeout(llmOptions),
137
174
  );
138
175
 
139
176
  const jsonText = extractJson(fullContent);
@@ -204,6 +241,11 @@ export async function* understandIntentStream({
204
241
  );
205
242
  let buffer = "";
206
243
  try {
244
+ // Incremental token yielding makes a per-call deadline awkward to apply
245
+ // here; this streaming variant is instead bounded by chat-core's own
246
+ // silence-based stall guard (CC_CHAT_STALL_MS, default 180s). The awaited
247
+ // understandIntent / classifyFollowupIntent paths enforce the tighter
248
+ // intent budget (resolveIntentTimeout) directly.
207
249
  for await (const event of chatStream(
208
250
  [
209
251
  { role: "system", content: systemPrompt },
@@ -319,7 +361,12 @@ const FOLLOWUP_RULES = {
319
361
  /(颜色|字体|大小|位置|标题).*(是|用|为)/,
320
362
  /^.{1,20}(应该|具体)(是|为)/,
321
363
  /^数据(来源|是)/,
322
- /.*(用|采用).*(字体|颜色|大小)/,
364
+ // De-catastrophized: the old `/.*(用|采用).*(字体…)/` had a leading greedy
365
+ // `.*` overlapping `(用)`, causing exponential backtracking (a 2 KB input
366
+ // of "用" took ~3 s). `.test()` already searches anywhere, so the leading
367
+ // `.*` is redundant; a lazy middle drops it to quadratic, and the input
368
+ // cap in ruleBasedClassify bounds that.
369
+ /(用|采用)[\s\S]*?(字体|颜色|大小)/,
323
370
  ],
324
371
  },
325
372
  CANCEL_TASK: {
@@ -334,8 +381,15 @@ const FOLLOWUP_RULES = {
334
381
  },
335
382
  };
336
383
 
384
+ // A follow-up intent (continue / modify / clarify / cancel) is determinable
385
+ // from the start of the message; cap the text the heuristic regexes see so a
386
+ // huge paste can't drive their (quadratic) backtracking into a DoS. The full
387
+ // input is still forwarded to the LLM path / downstream — only this local
388
+ // classification is truncated.
389
+ const MAX_CLASSIFY_INPUT = 2000;
390
+
337
391
  function ruleBasedClassify(userInput) {
338
- const input = (userInput || "").trim();
392
+ const input = (userInput || "").trim().slice(0, MAX_CLASSIFY_INPUT);
339
393
 
340
394
  if (input.length === 0) {
341
395
  return {
@@ -440,16 +494,19 @@ ${
440
494
 
441
495
  async function llmBasedClassify(userInput, context, llmOptions) {
442
496
  const { systemPrompt, userPrompt } = buildFollowupPrompts(userInput, context);
443
- const fullContent = await chatWithStreaming(
444
- [
445
- { role: "system", content: systemPrompt },
446
- { role: "user", content: userPrompt },
447
- ],
448
- {
449
- ...llmOptions,
450
- temperature: 0.1,
451
- maxTokens: 300,
452
- },
497
+ const fullContent = await withIntentTimeout(
498
+ chatWithStreaming(
499
+ [
500
+ { role: "system", content: systemPrompt },
501
+ { role: "user", content: userPrompt },
502
+ ],
503
+ {
504
+ ...llmOptions,
505
+ temperature: 0.1,
506
+ maxTokens: 300,
507
+ },
508
+ ),
509
+ resolveIntentTimeout(llmOptions),
453
510
  );
454
511
  const jsonText = extractJson(fullContent);
455
512
  if (!jsonText) throw new Error("LLM response missing JSON");
@@ -528,5 +585,7 @@ export const _internal = {
528
585
  buildFollowupPrompts,
529
586
  extractJson,
530
587
  ruleBasedClassify,
588
+ resolveIntentTimeout,
589
+ withIntentTimeout,
531
590
  DEFAULT_INTENT_TIMEOUT_MS,
532
591
  };