chainlesschain 0.162.124 → 0.162.125

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 (237) 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-DcqNhZhA.js +1 -0
  4. package/src/assets/web-panel/assets/{ActionButton-COjZZKWG.js → ActionButton-BigtmPoq.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-4ZDZE7lc.js → Analytics-1J_X_HF4.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-CYH5k2Rp.js → AppLayout-DlcDG_a_.js} +5 -5
  7. package/src/assets/web-panel/assets/Audit-Do-y8_3w.js +1 -0
  8. package/src/assets/web-panel/assets/{Backup-CiX61Qc2.js → Backup-S2Df-50Y.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-CDxIQokd.js → BaseInput-D5kgWJfk.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-CaKgdRab.js → Chat-aUcp3kN1.js} +5 -5
  11. package/src/assets/web-panel/assets/ChatBubbleRenderer-Pg7dIsiE.js +1 -0
  12. package/src/assets/web-panel/assets/{Checkbox-CjiWfu4s.js → Checkbox-DTg1U7Xs.js} +1 -1
  13. package/src/assets/web-panel/assets/Codegen-YO9ZZ4Ky.js +1 -0
  14. package/src/assets/web-panel/assets/{Col-ZKWREyNs.js → Col-BHonE9fU.js} +1 -1
  15. package/src/assets/web-panel/assets/Community-DA2AlEFh.js +1 -0
  16. package/src/assets/web-panel/assets/{Compact-DqknM98n.js → Compact-DULxLRNg.js} +1 -1
  17. package/src/assets/web-panel/assets/Compliance-BZqfuuZL.js +1 -0
  18. package/src/assets/web-panel/assets/{Cowork-Dp29kHsC.js → Cowork-CJe3vyMS.js} +2 -2
  19. package/src/assets/web-panel/assets/{Cron-zmEJAetz.js → Cron-CeV8AtRu.js} +2 -2
  20. package/src/assets/web-panel/assets/Crosschain-CAg66zS5.js +1 -0
  21. package/src/assets/web-panel/assets/{DID-D_UFNzJ5.js → DID-Dtup5JZm.js} +2 -2
  22. package/src/assets/web-panel/assets/Dashboard-DAR_8PNy.js +3 -0
  23. package/src/assets/web-panel/assets/{Dropdown-CH7l3KCs.js → Dropdown-K5bTaCYN.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-Bud2M3XX.js → EmailListRenderer-C890uErx.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bd2_8uME.js → FamilyGuardDashboard-B-Tj_8yM.js} +1 -1
  26. package/src/assets/web-panel/assets/Federation-C6WZ98ni.js +1 -0
  27. package/src/assets/web-panel/assets/{FormItemContext-CpSH464Y.js → FormItemContext-D-LTfGWd.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-CANo8O-y.js → GenericCardRenderer-C80DK4W7.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-hvuZVoD3.js → Git-Cjvvv3zW.js} +2 -2
  30. package/src/assets/web-panel/assets/Governance-C0BND77H.js +1 -0
  31. package/src/assets/web-panel/assets/Inference-BL9kTtSu.js +1 -0
  32. package/src/assets/web-panel/assets/KnowledgeGraph-CnVTBmJf.js +1 -0
  33. package/src/assets/web-panel/assets/{Logs-DDkTnedu.js → Logs-CbCbg7K6.js} +2 -2
  34. package/src/assets/web-panel/assets/Marketplace-CG5On-fH.js +1 -0
  35. package/src/assets/web-panel/assets/{McpTools-Qx78XyOT.js → McpTools-AYYDZZK7.js} +5 -5
  36. package/src/assets/web-panel/assets/{Memory-CXYDO1oT.js → Memory-tMWbMDQq.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileBridge-BAOsf4wB.js +1 -0
  38. package/src/assets/web-panel/assets/{MobileProjects-DEDbWNIk.js → MobileProjects-BT-2komQ.js} +1 -1
  39. package/src/assets/web-panel/assets/{Mtc-Dwa_vlR8.js → Mtc-BFwfC63n.js} +5 -5
  40. package/src/assets/web-panel/assets/{MtcAudit-DAVkxhJv.js → MtcAudit-DYG3CWdH.js} +4 -4
  41. package/src/assets/web-panel/assets/{Multisig-C3upmgPN.js → Multisig-KJwd0yWT.js} +3 -3
  42. package/src/assets/web-panel/assets/NLProgramming-BmL5dNWm.js +1 -0
  43. package/src/assets/web-panel/assets/{Notes-B1Oy3FbC.js → Notes-DmkaVaMc.js} +4 -4
  44. package/src/assets/web-panel/assets/{NotificationSettings-Bs4-Fc6b.js → NotificationSettings-tmVQszDZ.js} +1 -1
  45. package/src/assets/web-panel/assets/{OrderTableRenderer-CEy1pIeq.js → OrderTableRenderer-BtrR7anf.js} +1 -1
  46. package/src/assets/web-panel/assets/{Organization-D4cJ1UNo.js → Organization-pppktQyW.js} +4 -4
  47. package/src/assets/web-panel/assets/{Overflow-fdzvlF7x.js → Overflow-D3lBoQDA.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-Y13PwXBA.js → P2P-BgE2qGlZ.js} +2 -2
  49. package/src/assets/web-panel/assets/PdhVaultBrowser-D26Bz5gS.js +7 -0
  50. package/src/assets/web-panel/assets/{Permissions-CvEXP6LC.js → Permissions-BR8no2Xe.js} +3 -3
  51. package/src/assets/web-panel/assets/{PersonalDataHub-JeP7IWMW.js → PersonalDataHub-DabbyQEF.js} +3 -3
  52. package/src/assets/web-panel/assets/Pipeline-n4sNpEz8.js +1 -0
  53. package/src/assets/web-panel/assets/Privacy-CGNp4n8M.js +1 -0
  54. package/src/assets/web-panel/assets/{ProjectInit-DeWd9UcS.js → ProjectInit-CAVA5VsX.js} +2 -2
  55. package/src/assets/web-panel/assets/{ProjectSettings-DUpVuU9F.js → ProjectSettings-GCgkfM7A.js} +2 -2
  56. package/src/assets/web-panel/assets/Projects-BYK17p52.js +1 -0
  57. package/src/assets/web-panel/assets/{Providers-DECW00ek.js → Providers-DpUT5W-d.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-Dl-pK9Io.js → QuickAsk-ZJxTb7vi.js} +1 -1
  59. package/src/assets/web-panel/assets/Recommend-CIzFRDk1.js +1 -0
  60. package/src/assets/web-panel/assets/Reputation-lQ_WDNZn.js +1 -0
  61. package/src/assets/web-panel/assets/Row-1MEtrgtF.js +1 -0
  62. package/src/assets/web-panel/assets/{RssFeed-iyQT5q9l.js → RssFeed-SSgcPPl4.js} +3 -3
  63. package/src/assets/web-panel/assets/Search-CcTMOGNY.js +1 -0
  64. package/src/assets/web-panel/assets/{Security-c4Jxf5Ih.js → Security-BA1KMaDx.js} +4 -4
  65. package/src/assets/web-panel/assets/{Services-CRuisolj.js → Services-_aj7czZn.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-CCnzJkb-.js → Skeleton-BWgg_0Zr.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-CZURCwZg.js → Skills-C2ZIziYM.js} +1 -1
  68. package/src/assets/web-panel/assets/Sla-CYxp4axY.js +1 -0
  69. package/src/assets/web-panel/assets/SpeechSettings-C7Csp1jV.js +1 -0
  70. package/src/assets/web-panel/assets/{SyncSettings-CJWVtd87.js → SyncSettings-svGeswiw.js} +2 -2
  71. package/src/assets/web-panel/assets/{Tasks-61lrQ_lS.js → Tasks-ClISj6oK.js} +1 -1
  72. package/src/assets/web-panel/assets/Templates-CmO-Fp7r.js +1 -0
  73. package/src/assets/web-panel/assets/Tenant-a3Ac3lxz.js +1 -0
  74. package/src/assets/web-panel/assets/{Terminal-CP15hcNS.js → Terminal-DyJIRh81.js} +2 -2
  75. package/src/assets/web-panel/assets/TimelineRenderer-acXS5N5h.js +1 -0
  76. package/src/assets/web-panel/assets/Tokens-DJW0KqV4.js +1 -0
  77. package/src/assets/web-panel/assets/{Trigger-BLCQEOF2.js → Trigger-0HeTi4r6.js} +1 -1
  78. package/src/assets/web-panel/assets/Trust-aFym34eo.js +1 -0
  79. package/src/assets/web-panel/assets/{UkeySign-Dwp0zXQ6.js → UkeySign-CYhszHJv.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CIHA55Sx.js → VideoEditing-BbJxPF9X.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-Hjajvnto.js → Wallet-DDthEwiu.js} +3 -3
  82. package/src/assets/web-panel/assets/{WebAuthn-DqSURUvI.js → WebAuthn-DQ6McYPg.js} +4 -4
  83. package/src/assets/web-panel/assets/{WorkflowEditor-B5bHRwUG.js → WorkflowEditor-CnF8zRsi.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-DsheLKkG.js → chat-Dzkx2gTP.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-CST2UkgL.js → colors-35T51Oo5.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-yqEoy1L7.js → compact-item-TtzNrlu1.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-Jwp78HFY.js → createContext-Y1LkWrYb.js} +1 -1
  88. package/src/assets/web-panel/assets/devWarning-D_lwLZ6O.js +1 -0
  89. package/src/assets/web-panel/assets/{hasIn-CVS5mFEP.js → hasIn-BOYw1BgS.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-Crk4KWLW.js → index-8jffdb6b.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-CDAPnZUs.js → index-B7aKAZzS.js} +1 -1
  92. package/src/assets/web-panel/assets/index-BGPEaeB1.js +1 -0
  93. package/src/assets/web-panel/assets/index-BKRlWHUp.js +1 -0
  94. package/src/assets/web-panel/assets/{index-BCHAUdel.js → index-BSpFe6j5.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Cjig5i3a.js → index-BWgNn9As.js} +28 -26
  96. package/src/assets/web-panel/assets/{index-Dxljh9Fu.js → index-Bs69SI96.js} +2 -2
  97. package/src/assets/web-panel/assets/{index-BLTUVd0V.js → index-BtlJWaSP.js} +2 -2
  98. package/src/assets/web-panel/assets/index-Bw1iOGhM.js +1 -0
  99. package/src/assets/web-panel/assets/{index-CjfN2CpJ.js → index-BxWUEFKy.js} +1 -1
  100. package/src/assets/web-panel/assets/index-C7Wt5feN.js +1 -0
  101. package/src/assets/web-panel/assets/index-CEwuCM44.js +1 -0
  102. package/src/assets/web-panel/assets/index-CLqC2sRT.js +3 -0
  103. package/src/assets/web-panel/assets/{index-Dgyn8-wN.js → index-CPGNc2tF.js} +2 -2
  104. package/src/assets/web-panel/assets/{index-AnW25bEq.js → index-CVrUs6rf.js} +1 -1
  105. package/src/assets/web-panel/assets/index-CZ16GlOs.js +1 -0
  106. package/src/assets/web-panel/assets/{index-Cy0dNzkp.js → index-CdyFg4FI.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C0FZScMe.js → index-CqhwG1ox.js} +4 -4
  108. package/src/assets/web-panel/assets/index-Cs82BHAj.js +1 -0
  109. package/src/assets/web-panel/assets/{index-CRBbVS43.js → index-D2od7Bgx.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DxaU_lza.js → index-D47robkX.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CSBPc-sI.js → index-D4BgCBa3.js} +2 -2
  112. package/src/assets/web-panel/assets/index-D6tG4B25.js +55 -0
  113. package/src/assets/web-panel/assets/index-D7dCBw_M.js +1 -0
  114. package/src/assets/web-panel/assets/index-DAizH273.js +21 -0
  115. package/src/assets/web-panel/assets/{index-CzsKK0tQ.js → index-DFhlelzN.js} +2 -2
  116. package/src/assets/web-panel/assets/index-DhMSOd_2.js +1 -0
  117. package/src/assets/web-panel/assets/index-Dhfw-BXs.js +12 -0
  118. package/src/assets/web-panel/assets/index-DjeUZxE5.js +1 -0
  119. package/src/assets/web-panel/assets/{index-DDAigsel.js → index-DmkG479D.js} +2 -2
  120. package/src/assets/web-panel/assets/index-Dof7lWA_.js +13 -0
  121. package/src/assets/web-panel/assets/{index-De600Jjm.js → index-Dx0hIfC-.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DzzgU4IU.js +1 -0
  123. package/src/assets/web-panel/assets/index-G4ClSmJc.js +1 -0
  124. package/src/assets/web-panel/assets/{index-CtyEOGuj.js → index-KgXrlfNF.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-SjxCCf5h.js → index-NM9Oke2k.js} +2 -2
  126. package/src/assets/web-panel/assets/{index-R-VGFpi6.js → index-XIuZV9by.js} +3 -3
  127. package/src/assets/web-panel/assets/{index-BzetOasL.js → index-hyqEKkBT.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BuI27WSx.js → index-zF77jnI2.js} +2 -2
  129. package/src/assets/web-panel/assets/{initDefaultProps-ClAjrsQ-.js → initDefaultProps-DD5y5msp.js} +1 -1
  130. package/src/assets/web-panel/assets/motion-BuoutMia.js +11 -0
  131. package/src/assets/web-panel/assets/{move-DMelzIW-.js → move-DkpjqOXg.js} +1 -1
  132. package/src/assets/web-panel/assets/mtc-parser-BmGJD-v-.js +1 -0
  133. package/src/assets/web-panel/assets/{omit-oxecfU3b.js → omit-DftUE0Sz.js} +1 -1
  134. package/src/assets/web-panel/assets/{pickAttrs-DJ5F5M6x.js → pickAttrs-DDRb9FIu.js} +1 -1
  135. package/src/assets/web-panel/assets/{placementArrow-DdbKsant.js → placementArrow-DPgtxOOD.js} +1 -1
  136. package/src/assets/web-panel/assets/{responsiveObserve-BUuM5cmS.js → responsiveObserve-BQFRzFeZ.js} +1 -1
  137. package/src/assets/web-panel/assets/{slide-DSV0ccvR.js → slide-DQ4lmUSz.js} +1 -1
  138. package/src/assets/web-panel/assets/{statusUtils-B-6oLAXf.js → statusUtils-C1TR4ZiQ.js} +1 -1
  139. package/src/assets/web-panel/assets/{styleChecker-DdCAHtsZ.js → styleChecker-B-Suj978.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFlexGapSupport-ZORkcoZd.js → useFlexGapSupport-Q7bDZ3EI.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFs-BFp46LoP.js → useFs-DHjRLyQH.js} +1 -1
  142. package/src/assets/web-panel/assets/{usePersonalDataHub-H7rxgbdW.js → usePersonalDataHub-lQOvCvCr.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-eIq4Tk0J.js → vnode-Ie0g4-p3.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-CTNrv8-H.js → zoom-CHP0YEoH.js} +1 -1
  145. package/src/assets/web-panel/index.html +1 -1
  146. package/src/commands/agent.js +10 -0
  147. package/src/commands/ide.js +60 -5
  148. package/src/commands/incentive.js +22 -22
  149. package/src/commands/mtc.js +5 -1
  150. package/src/gateways/ws/message-dispatcher.js +4 -1
  151. package/src/gateways/ws/ws-session-gateway.js +56 -4
  152. package/src/harness/background-task-manager.js +5 -1
  153. package/src/harness/jsonl-session-store.js +51 -0
  154. package/src/harness/mcp-client.js +62 -21
  155. package/src/harness/prompt-compressor.js +24 -5
  156. package/src/harness/worktree-isolator.js +5 -1
  157. package/src/lib/agent-core.js +1 -0
  158. package/src/lib/autonomous-agent.js +29 -0
  159. package/src/lib/chat-intent-service.js +14 -2
  160. package/src/lib/checkpoint-store.js +31 -4
  161. package/src/lib/claude-code-bridge.js +48 -3
  162. package/src/lib/cost-budget.js +13 -2
  163. package/src/lib/credential-guard.js +38 -0
  164. package/src/lib/git-integration.js +11 -2
  165. package/src/lib/goal-store.js +11 -0
  166. package/src/lib/hook-manager.js +4 -0
  167. package/src/lib/jetbrains-bridge.js +147 -0
  168. package/src/lib/jsonl-session-store.js +1 -0
  169. package/src/lib/llm-pricing.js +11 -4
  170. package/src/lib/llm-providers.js +11 -1
  171. package/src/lib/mcp-oauth.js +12 -0
  172. package/src/lib/mcp-serve.js +29 -0
  173. package/src/lib/orchestrator.js +38 -2
  174. package/src/lib/repl-bang-memorize.js +9 -1
  175. package/src/lib/runnable-provider.js +7 -1
  176. package/src/lib/session-insights.js +13 -6
  177. package/src/lib/session-usage.js +21 -3
  178. package/src/lib/status-line.cjs +4 -1
  179. package/src/lib/sub-agent-context.js +9 -0
  180. package/src/repl/agent-repl.js +29 -0
  181. package/src/runtime/agent-core.js +4 -2
  182. package/src/runtime/headless-runner.js +13 -0
  183. package/src/runtime/headless-stream.js +9 -0
  184. package/src/runtime/mcp-config.js +72 -3
  185. package/src/runtime/pipe-safety.js +51 -0
  186. package/src/runtime/policies/agent-policy.js +3 -0
  187. package/src/assets/web-panel/assets/AIOps-BsDJlD_l.js +0 -1
  188. package/src/assets/web-panel/assets/Audit-BKlFbLdl.js +0 -1
  189. package/src/assets/web-panel/assets/ChatBubbleRenderer-DgvpwU5o.js +0 -1
  190. package/src/assets/web-panel/assets/Codegen-tKWGzajw.js +0 -1
  191. package/src/assets/web-panel/assets/Community-CmLuPBUU.js +0 -1
  192. package/src/assets/web-panel/assets/Compliance-PZw0aBLI.js +0 -1
  193. package/src/assets/web-panel/assets/Crosschain-Sb-uM7Ty.js +0 -1
  194. package/src/assets/web-panel/assets/Dashboard-Btag698v.js +0 -3
  195. package/src/assets/web-panel/assets/Federation-1jhstF5P.js +0 -1
  196. package/src/assets/web-panel/assets/Governance-BQrWksKt.js +0 -1
  197. package/src/assets/web-panel/assets/Inference-jEBTp12v.js +0 -1
  198. package/src/assets/web-panel/assets/KnowledgeGraph-tDiV2taf.js +0 -1
  199. package/src/assets/web-panel/assets/Marketplace-9Yi32avt.js +0 -1
  200. package/src/assets/web-panel/assets/MobileBridge-MfUfkHA0.js +0 -1
  201. package/src/assets/web-panel/assets/NLProgramming-DO3CZ0_z.js +0 -1
  202. package/src/assets/web-panel/assets/PdhVaultBrowser-Ck1zCLe6.js +0 -7
  203. package/src/assets/web-panel/assets/Pipeline-BZ7wRzG1.js +0 -1
  204. package/src/assets/web-panel/assets/Privacy-BJ6qj9Hv.js +0 -1
  205. package/src/assets/web-panel/assets/Projects-y1WbI337.js +0 -1
  206. package/src/assets/web-panel/assets/Recommend-Bju04wTd.js +0 -1
  207. package/src/assets/web-panel/assets/Reputation-BUPa3nEk.js +0 -1
  208. package/src/assets/web-panel/assets/Row-WYqqaq_5.js +0 -1
  209. package/src/assets/web-panel/assets/Search-CrfwJF0R.js +0 -1
  210. package/src/assets/web-panel/assets/Sla-CrMzaEi2.js +0 -1
  211. package/src/assets/web-panel/assets/SpeechSettings-jTDOM5eY.js +0 -1
  212. package/src/assets/web-panel/assets/Templates-Cg-iF2g1.js +0 -1
  213. package/src/assets/web-panel/assets/Tenant-DrDv1FFc.js +0 -1
  214. package/src/assets/web-panel/assets/TimelineRenderer-Sl5uXrkN.js +0 -1
  215. package/src/assets/web-panel/assets/Tokens-yRIZTVsR.js +0 -1
  216. package/src/assets/web-panel/assets/Trust-D5xq8z6s.js +0 -1
  217. package/src/assets/web-panel/assets/community-parser-Cuyslaku.js +0 -3
  218. package/src/assets/web-panel/assets/devWarning-C8to6gQk.js +0 -1
  219. package/src/assets/web-panel/assets/index-BeblNJDH.js +0 -1
  220. package/src/assets/web-panel/assets/index-C130LLPq.js +0 -1
  221. package/src/assets/web-panel/assets/index-C8rq85gu.js +0 -1
  222. package/src/assets/web-panel/assets/index-CIE2n8k_.js +0 -1
  223. package/src/assets/web-panel/assets/index-CTd3lDxp.js +0 -13
  224. package/src/assets/web-panel/assets/index-CUWj5L2s.js +0 -1
  225. package/src/assets/web-panel/assets/index-ChRL6rgA.js +0 -3
  226. package/src/assets/web-panel/assets/index-CrlRa054.js +0 -1
  227. package/src/assets/web-panel/assets/index-Cu6Ql64n.js +0 -1
  228. package/src/assets/web-panel/assets/index-Cx_t5rWw.js +0 -12
  229. package/src/assets/web-panel/assets/index-DZfnYE6e.js +0 -1
  230. package/src/assets/web-panel/assets/index-D_1b7uh6.js +0 -55
  231. package/src/assets/web-panel/assets/index-D_e9gwfn.js +0 -1
  232. package/src/assets/web-panel/assets/index-DbxAlpPC.js +0 -21
  233. package/src/assets/web-panel/assets/index-DiK4vSZa.js +0 -1
  234. package/src/assets/web-panel/assets/index-DpYn5v2k.js +0 -1
  235. package/src/assets/web-panel/assets/index-diWkx2Wq.js +0 -1
  236. package/src/assets/web-panel/assets/motion-BalXv_60.js +0 -11
  237. package/src/assets/web-panel/assets/mtc-parser-CEQffxds.js +0 -1
@@ -482,8 +482,8 @@ export function registerIncentiveCommand(program) {
482
482
  .command("register-account-v2 <account-id>")
483
483
  .description("V2: register a tracked account (tags ACTIVE)")
484
484
  .option("-m, --metadata <meta>", "Metadata (JSON)")
485
- .action((accountId, opts) => {
486
- const ctx = bootstrap();
485
+ .action(async (accountId, opts) => {
486
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
487
487
  try {
488
488
  const db = _dbFromCtx(ctx);
489
489
  const metadata = parseJsonOption(opts.metadata, "--metadata");
@@ -511,8 +511,8 @@ export function registerIncentiveCommand(program) {
511
511
  .description("V2: transition account status (active|frozen|closed)")
512
512
  .option("-r, --reason <reason>", "Reason")
513
513
  .option("-m, --metadata <meta>", "Metadata (JSON)")
514
- .action((accountId, status, opts) => {
515
- const ctx = bootstrap();
514
+ .action(async (accountId, status, opts) => {
515
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
516
516
  try {
517
517
  const db = _dbFromCtx(ctx);
518
518
  const metadata = parseJsonOption(opts.metadata, "--metadata");
@@ -530,8 +530,8 @@ export function registerIncentiveCommand(program) {
530
530
  .command("freeze-account <account-id>")
531
531
  .description("V2: shortcut → frozen")
532
532
  .option("-r, --reason <reason>", "Reason")
533
- .action((accountId, opts) => {
534
- const ctx = bootstrap();
533
+ .action(async (accountId, opts) => {
534
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
535
535
  try {
536
536
  const db = _dbFromCtx(ctx);
537
537
  const entry = freezeAccount(db, accountId, opts.reason);
@@ -545,8 +545,8 @@ export function registerIncentiveCommand(program) {
545
545
  .command("unfreeze-account <account-id>")
546
546
  .description("V2: shortcut → active (from frozen)")
547
547
  .option("-r, --reason <reason>", "Reason")
548
- .action((accountId, opts) => {
549
- const ctx = bootstrap();
548
+ .action(async (accountId, opts) => {
549
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
550
550
  try {
551
551
  const db = _dbFromCtx(ctx);
552
552
  const entry = unfreezeAccount(db, accountId, opts.reason);
@@ -560,8 +560,8 @@ export function registerIncentiveCommand(program) {
560
560
  .command("close-account <account-id>")
561
561
  .description("V2: shortcut → closed")
562
562
  .option("-r, --reason <reason>", "Reason")
563
- .action((accountId, opts) => {
564
- const ctx = bootstrap();
563
+ .action(async (accountId, opts) => {
564
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
565
565
  try {
566
566
  const db = _dbFromCtx(ctx);
567
567
  const entry = closeAccount(db, accountId, opts.reason);
@@ -578,8 +578,8 @@ export function registerIncentiveCommand(program) {
578
578
  .requiredOption("-a, --amount <amount>", "Amount")
579
579
  .option("-c, --contribution <id>", "Contribution ID")
580
580
  .option("-m, --metadata <meta>", "Metadata (JSON)")
581
- .action((claimId, opts) => {
582
- const ctx = bootstrap();
581
+ .action(async (claimId, opts) => {
582
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
583
583
  try {
584
584
  const db = _dbFromCtx(ctx);
585
585
  const metadata = parseJsonOption(opts.metadata, "--metadata");
@@ -613,8 +613,8 @@ export function registerIncentiveCommand(program) {
613
613
  .description("V2: transition claim (pending|approved|paid|rejected)")
614
614
  .option("-r, --reason <reason>", "Reason")
615
615
  .option("-m, --metadata <meta>", "Metadata (JSON)")
616
- .action((claimId, status, opts) => {
617
- const ctx = bootstrap();
616
+ .action(async (claimId, status, opts) => {
617
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
618
618
  try {
619
619
  const db = _dbFromCtx(ctx);
620
620
  const metadata = parseJsonOption(opts.metadata, "--metadata");
@@ -632,8 +632,8 @@ export function registerIncentiveCommand(program) {
632
632
  .command("approve-claim <claim-id>")
633
633
  .description("V2: shortcut → approved")
634
634
  .option("-r, --reason <reason>", "Reason")
635
- .action((claimId, opts) => {
636
- const ctx = bootstrap();
635
+ .action(async (claimId, opts) => {
636
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
637
637
  try {
638
638
  const db = _dbFromCtx(ctx);
639
639
  const entry = approveClaim(db, claimId, opts.reason);
@@ -647,8 +647,8 @@ export function registerIncentiveCommand(program) {
647
647
  .command("reject-claim <claim-id>")
648
648
  .description("V2: shortcut → rejected")
649
649
  .option("-r, --reason <reason>", "Reason")
650
- .action((claimId, opts) => {
651
- const ctx = bootstrap();
650
+ .action(async (claimId, opts) => {
651
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
652
652
  try {
653
653
  const db = _dbFromCtx(ctx);
654
654
  const entry = rejectClaim(db, claimId, opts.reason);
@@ -662,8 +662,8 @@ export function registerIncentiveCommand(program) {
662
662
  .command("pay-claim <claim-id>")
663
663
  .description("V2: shortcut → paid (stamps paidAt)")
664
664
  .option("-r, --reason <reason>", "Reason")
665
- .action((claimId, opts) => {
666
- const ctx = bootstrap();
665
+ .action(async (claimId, opts) => {
666
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
667
667
  try {
668
668
  const db = _dbFromCtx(ctx);
669
669
  const entry = payClaim(db, claimId, opts.reason);
@@ -676,8 +676,8 @@ export function registerIncentiveCommand(program) {
676
676
  inc
677
677
  .command("auto-expire-unclaimed-claims")
678
678
  .description("V2: bulk-reject stale PENDING claims")
679
- .action(() => {
680
- const ctx = bootstrap();
679
+ .action(async () => {
680
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
681
681
  try {
682
682
  const db = _dbFromCtx(ctx);
683
683
  const expired = autoExpireUnclaimedClaims(db);
@@ -595,7 +595,11 @@ export function registerMtcCommand(program) {
595
595
  return;
596
596
  }
597
597
  if (result.ok) {
598
- logger.log(signerInfo ? signerInfo.banner : STOPGAP_BANNER);
598
+ // `verify` reads envelope+landmark from disk and creates no signer,
599
+ // so signerInfo is never defined here — referencing it threw a
600
+ // ReferenceError that the catch reported as "verify failed" on a
601
+ // SUCCESSFUL verify. Use the stopgap banner directly.
602
+ logger.log(STOPGAP_BANNER);
599
603
  logger.success(`Envelope verified`);
600
604
  logger.log(
601
605
  ` ${chalk.bold("Subject:")} ${result.leaf.subject || "(no subject)"}`,
@@ -23,7 +23,10 @@ export function createWsMessageDispatcher(server) {
23
23
  }
24
24
 
25
25
  const client = server.clients.get(clientId);
26
- if (server.token && !client.authenticated && type !== "auth") {
26
+ // client may be undefined if it was removed mid-dispatch (heartbeat sweep
27
+ // / disconnect); treat a missing client as unauthenticated rather than
28
+ // throwing a TypeError (every sibling call site null-checks the client).
29
+ if (server.token && !client?.authenticated && type !== "auth") {
27
30
  server._send(ws, {
28
31
  id,
29
32
  type: "error",
@@ -109,6 +109,20 @@ export class WSSessionManager {
109
109
  this.maxSessions = Number.isFinite(options.maxSessions)
110
110
  ? options.maxSessions
111
111
  : 100;
112
+
113
+ // Cap patch bookkeeping per session. `patchHistory` is append-only (every
114
+ // applyPatch/rejectPatch pushes, nothing trims) and `pendingPatches` grows
115
+ // with each proposePatch — both are serialized in full by
116
+ // _persistSessionState on every session op, so unbounded growth means
117
+ // O(n) memory AND an O(n²) rewrite cost over a long session (and a tool
118
+ // proposing many large before/after/diff blobs amplifies it). Keep a
119
+ // recent window; oldest entries fall off (FIFO).
120
+ this.maxPatchHistory = Number.isFinite(options.maxPatchHistory)
121
+ ? options.maxPatchHistory
122
+ : 200;
123
+ this.maxPendingPatches = Number.isFinite(options.maxPendingPatches)
124
+ ? options.maxPendingPatches
125
+ : 50;
112
126
  }
113
127
 
114
128
  _normalizeEnabledToolNames(enabledToolNames) {
@@ -499,9 +513,7 @@ export class WSSessionManager {
499
513
  permanentMemory,
500
514
  reviewState: metadata.reviewState || null,
501
515
  pendingPatches: this._hydratePendingPatches(metadata.pendingPatches),
502
- patchHistory: Array.isArray(metadata.patchHistory)
503
- ? metadata.patchHistory
504
- : [],
516
+ patchHistory: this._boundPatchHistory(metadata.patchHistory),
505
517
  taskGraph: this._hydrateTaskGraph(metadata.taskGraph),
506
518
  interaction: null,
507
519
  createdAt: dbSession.created_at,
@@ -889,6 +901,15 @@ export class WSSessionManager {
889
901
  if (!(session.pendingPatches instanceof Map)) {
890
902
  session.pendingPatches = new Map();
891
903
  }
904
+ // Bound un-reviewed proposals: evict the oldest pending (Map preserves
905
+ // insertion order) once at cap. 50 unresolved patches is already
906
+ // pathological; dropping the stalest keeps memory + persisted state bounded
907
+ // without blocking a legitimate new proposal.
908
+ while (session.pendingPatches.size >= this.maxPendingPatches) {
909
+ const oldestKey = session.pendingPatches.keys().next().value;
910
+ if (oldestKey === undefined) break;
911
+ session.pendingPatches.delete(oldestKey);
912
+ }
892
913
  session.pendingPatches.set(patchId, patch);
893
914
  session.lastActivity = now;
894
915
  this._persistSessionState(sessionId);
@@ -917,11 +938,35 @@ export class WSSessionManager {
917
938
  session.patchHistory = [];
918
939
  }
919
940
  session.patchHistory.push(patch);
941
+ this._trimPatchHistory(session);
920
942
  session.lastActivity = patch.resolvedAt;
921
943
  this._persistSessionState(sessionId);
922
944
  return patch;
923
945
  }
924
946
 
947
+ /**
948
+ * Keep `patchHistory` bounded to the most recent maxPatchHistory entries.
949
+ * Oldest fall off the front (FIFO) so memory + persisted JSON stay bounded.
950
+ */
951
+ _trimPatchHistory(session) {
952
+ if (!Array.isArray(session.patchHistory)) return;
953
+ const overflow = session.patchHistory.length - this.maxPatchHistory;
954
+ if (overflow > 0) {
955
+ session.patchHistory.splice(0, overflow);
956
+ }
957
+ }
958
+
959
+ /**
960
+ * Bound a (possibly persisted/tampered) patchHistory array on load, keeping
961
+ * the most recent entries. Returns a fresh array (never the input).
962
+ */
963
+ _boundPatchHistory(list) {
964
+ if (!Array.isArray(list)) return [];
965
+ return list.length > this.maxPatchHistory
966
+ ? list.slice(list.length - this.maxPatchHistory)
967
+ : list.slice();
968
+ }
969
+
925
970
  /**
926
971
  * Discard a pending patch. Same bookkeeping as applyPatch but records a
927
972
  * "rejected" decision instead.
@@ -944,6 +989,7 @@ export class WSSessionManager {
944
989
  session.patchHistory = [];
945
990
  }
946
991
  session.patchHistory.push(patch);
992
+ this._trimPatchHistory(session);
947
993
  session.lastActivity = patch.resolvedAt;
948
994
  this._persistSessionState(sessionId);
949
995
  return patch;
@@ -1339,7 +1385,13 @@ export class WSSessionManager {
1339
1385
  _hydratePendingPatches(list) {
1340
1386
  const map = new Map();
1341
1387
  if (Array.isArray(list)) {
1342
- for (const patch of list) {
1388
+ // Trim from the front so the most recent proposals survive a reload even
1389
+ // if a prior (or tampered) persisted state exceeded the cap.
1390
+ const bounded =
1391
+ list.length > this.maxPendingPatches
1392
+ ? list.slice(list.length - this.maxPendingPatches)
1393
+ : list;
1394
+ for (const patch of bounded) {
1343
1395
  if (patch && patch.patchId) {
1344
1396
  map.set(patch.patchId, patch);
1345
1397
  }
@@ -194,7 +194,11 @@ export class BackgroundTaskManager extends EventEmitter {
194
194
  return history;
195
195
  }
196
196
 
197
- const items = history.slice(offset, offset + limit);
197
+ // offset + null === offset, so an offset-only page (no limit) used to
198
+ // slice(offset, offset) === [] and lose every item past the offset. Use
199
+ // history.length as the end when limit is null.
200
+ const end = limit === null ? history.length : offset + limit;
201
+ const items = history.slice(offset, end);
198
202
  return {
199
203
  items,
200
204
  total: history.length,
@@ -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
 
@@ -184,6 +210,7 @@ export function forkSession(sourceId) {
184
210
  }
185
211
 
186
212
  export function sessionExists(sessionId) {
213
+ if (isUnsafeSessionId(sessionId)) return false; // never resolve a traversal id
187
214
  return existsSync(sessionPath(sessionId));
188
215
  }
189
216
 
@@ -266,6 +293,15 @@ export function migrateLegacySessionFile(filePath, options = {}) {
266
293
  }
267
294
 
268
295
  export function validateJsonlSession(sessionId) {
296
+ if (isUnsafeSessionId(sessionId)) {
297
+ return {
298
+ sessionId,
299
+ valid: false,
300
+ reason: "invalid session id",
301
+ malformedLines: 0,
302
+ eventCount: 0,
303
+ };
304
+ }
269
305
  const filePath = sessionPath(sessionId);
270
306
  if (!existsSync(filePath)) {
271
307
  return {
@@ -335,6 +371,21 @@ function performLegacySessionMigration(sourcePath, options) {
335
371
  const legacy = normalizeLegacySession(parsed, basename(sourcePath, ".json"));
336
372
  const sessionId = legacy.id;
337
373
 
374
+ // A legacy file carries its OWN `id` (payload.id), so a crafted file could
375
+ // name a traversal target like "../../evil". sessionPath() throws on write
376
+ // (the backstop), but fail-fast HERE with a clear reason so the migration
377
+ // doesn't burn retry attempts on a deterministic error.
378
+ if (isUnsafeSessionId(sessionId)) {
379
+ return {
380
+ file: sourcePath,
381
+ sessionId,
382
+ migrated: false,
383
+ failed: true,
384
+ dryRun: Boolean(options.dryRun),
385
+ reason: "unsafe session id in legacy file",
386
+ };
387
+ }
388
+
338
389
  if (!options.force && sessionExists(sessionId)) {
339
390
  return {
340
391
  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;
@@ -28,6 +28,7 @@ export {
28
28
  listBackgroundShellTasks,
29
29
  killBackgroundShellTask,
30
30
  killAllBackgroundShellTasks,
31
+ reapIdleBackgroundShellTasks,
31
32
  writeFileVerified,
32
33
  formatProviderHttpError,
33
34
  _accumulateOllamaStream,
@@ -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
  */
@@ -319,7 +319,12 @@ const FOLLOWUP_RULES = {
319
319
  /(颜色|字体|大小|位置|标题).*(是|用|为)/,
320
320
  /^.{1,20}(应该|具体)(是|为)/,
321
321
  /^数据(来源|是)/,
322
- /.*(用|采用).*(字体|颜色|大小)/,
322
+ // De-catastrophized: the old `/.*(用|采用).*(字体…)/` had a leading greedy
323
+ // `.*` overlapping `(用)`, causing exponential backtracking (a 2 KB input
324
+ // of "用" took ~3 s). `.test()` already searches anywhere, so the leading
325
+ // `.*` is redundant; a lazy middle drops it to quadratic, and the input
326
+ // cap in ruleBasedClassify bounds that.
327
+ /(用|采用)[\s\S]*?(字体|颜色|大小)/,
323
328
  ],
324
329
  },
325
330
  CANCEL_TASK: {
@@ -334,8 +339,15 @@ const FOLLOWUP_RULES = {
334
339
  },
335
340
  };
336
341
 
342
+ // A follow-up intent (continue / modify / clarify / cancel) is determinable
343
+ // from the start of the message; cap the text the heuristic regexes see so a
344
+ // huge paste can't drive their (quadratic) backtracking into a DoS. The full
345
+ // input is still forwarded to the LLM path / downstream — only this local
346
+ // classification is truncated.
347
+ const MAX_CLASSIFY_INPUT = 2000;
348
+
337
349
  function ruleBasedClassify(userInput) {
338
- const input = (userInput || "").trim();
350
+ const input = (userInput || "").trim().slice(0, MAX_CLASSIFY_INPUT);
339
351
 
340
352
  if (input.length === 0) {
341
353
  return {