chainlesschain 0.162.123 → 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 (263) 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-BugJtiJc.js → ActionButton-BigtmPoq.js} +1 -1
  5. package/src/assets/web-panel/assets/Analytics-1J_X_HF4.js +3 -0
  6. package/src/assets/web-panel/assets/{AppLayout-Cb0eHetM.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-S2Df-50Y.js +1 -0
  9. package/src/assets/web-panel/assets/{BaseInput-CZ0c7QHk.js → BaseInput-D5kgWJfk.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-CaKDT3wV.js → Chat-aUcp3kN1.js} +6 -6
  11. package/src/assets/web-panel/assets/ChatBubbleRenderer-Pg7dIsiE.js +1 -0
  12. package/src/assets/web-panel/assets/{Checkbox-C6-Iu3bG.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-DVBrO7AN.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-DM4ZgFSE.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-CJwiWkUY.js → Cowork-CJe3vyMS.js} +2 -2
  19. package/src/assets/web-panel/assets/{Cron-dsdXHvrO.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-BB_LBLYT.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-dTFtVnKk.js → Dropdown-K5bTaCYN.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-yqdttzXV.js → EmailListRenderer-C890uErx.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DfX3YpUU.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-DNMwIh7X.js → FormItemContext-D-LTfGWd.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-C-T8UQf0.js → GenericCardRenderer-C80DK4W7.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-SURFhkLi.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-DjuWREBJ.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-D0waMomb.js → McpTools-AYYDZZK7.js} +5 -5
  36. package/src/assets/web-panel/assets/{Memory-Ci768GQ3.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-RpFuAiNz.js → MobileProjects-BT-2komQ.js} +1 -1
  39. package/src/assets/web-panel/assets/{Mtc-DJdw8btT.js → Mtc-BFwfC63n.js} +5 -5
  40. package/src/assets/web-panel/assets/{MtcAudit-Dc596XgH.js → MtcAudit-DYG3CWdH.js} +4 -4
  41. package/src/assets/web-panel/assets/{Multisig--Qs7eGsH.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-uMxjj66_.js → Notes-DmkaVaMc.js} +3 -3
  44. package/src/assets/web-panel/assets/{NotificationSettings-C4ikBOsm.js → NotificationSettings-tmVQszDZ.js} +1 -1
  45. package/src/assets/web-panel/assets/{OrderTableRenderer-BpmdunAz.js → OrderTableRenderer-BtrR7anf.js} +1 -1
  46. package/src/assets/web-panel/assets/Organization-pppktQyW.js +4 -0
  47. package/src/assets/web-panel/assets/{Overflow-B-4_IpbB.js → Overflow-D3lBoQDA.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-DdOf9BZs.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-BR8no2Xe.js +4 -0
  51. package/src/assets/web-panel/assets/{PersonalDataHub-B3WcvKni.js → PersonalDataHub-DabbyQEF.js} +2 -2
  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-BQjMDkML.js → ProjectInit-CAVA5VsX.js} +2 -2
  55. package/src/assets/web-panel/assets/ProjectSettings-GCgkfM7A.js +2 -0
  56. package/src/assets/web-panel/assets/Projects-BYK17p52.js +1 -0
  57. package/src/assets/web-panel/assets/{Providers-Bl0dw6cI.js → Providers-DpUT5W-d.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-BDrk7l3t.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-9-Fxh3_w.js → RssFeed-SSgcPPl4.js} +2 -2
  63. package/src/assets/web-panel/assets/Search-CcTMOGNY.js +1 -0
  64. package/src/assets/web-panel/assets/{Security-D6CySV7r.js → Security-BA1KMaDx.js} +3 -3
  65. package/src/assets/web-panel/assets/{Services-Co8Mk8qE.js → Services-_aj7czZn.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-BdON8M-1.js → Skeleton-BWgg_0Zr.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-CEJE1Go1.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-52NXsfyj.js → SyncSettings-svGeswiw.js} +2 -2
  71. package/src/assets/web-panel/assets/{Tasks-He7CweW6.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-DyJIRh81.js +3 -0
  75. package/src/assets/web-panel/assets/{TimelineRenderer-BdfqBQmM.js → TimelineRenderer-acXS5N5h.js} +1 -1
  76. package/src/assets/web-panel/assets/Tokens-DJW0KqV4.js +1 -0
  77. package/src/assets/web-panel/assets/{Trigger-Dt_N_rvo.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-CW8YdFxg.js → UkeySign-CYhszHJv.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CU6o7qvp.js → VideoEditing-BbJxPF9X.js} +1 -1
  81. package/src/assets/web-panel/assets/Wallet-DDthEwiu.js +4 -0
  82. package/src/assets/web-panel/assets/WebAuthn-DQ6McYPg.js +5 -0
  83. package/src/assets/web-panel/assets/{WorkflowEditor-BcCA2U8H.js → WorkflowEditor-CnF8zRsi.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-Cxl5DGsc.js → chat-Dzkx2gTP.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-FS_7Xggs.js → colors-35T51Oo5.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-BzXjd3wJ.js → compact-item-TtzNrlu1.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-DhF9bi_u.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-Y1a0aV9N.js → hasIn-BOYw1BgS.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-JUukFpxi.js → index-8jffdb6b.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-BBtULjKY.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-Dmjxpu0L.js → index-BSpFe6j5.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-1iacQgo5.js → index-BWgNn9As.js} +28 -26
  96. package/src/assets/web-panel/assets/{index-Na733kBa.js → index-Bs69SI96.js} +2 -2
  97. package/src/assets/web-panel/assets/{index-B48PZ-xM.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-DVTex5M8.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-DxGVbuqr.js → index-CPGNc2tF.js} +2 -2
  104. package/src/assets/web-panel/assets/{index-DfaSKEVD.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-CELVZPNt.js → index-CdyFg4FI.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CETvk0NN.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-D3IVAG9l.js → index-D2od7Bgx.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Sl-sLj3T.js → index-D47robkX.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Ci7PSoQn.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-GCd5hs58.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-BnvaUocg.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-BNmWkPqm.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-Cn9lbqaS.js → index-KgXrlfNF.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DUcVF8e6.js → index-NM9Oke2k.js} +2 -2
  126. package/src/assets/web-panel/assets/{index-DLyIxnjc.js → index-XIuZV9by.js} +3 -3
  127. package/src/assets/web-panel/assets/{index-BTltEtoG.js → index-hyqEKkBT.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-uLoKviUT.js → index-zF77jnI2.js} +2 -2
  129. package/src/assets/web-panel/assets/{initDefaultProps-D6QmlnQ-.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-Bpph6B_N.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-Dx0YMbWn.js → omit-DftUE0Sz.js} +1 -1
  134. package/src/assets/web-panel/assets/{pickAttrs-D-a8Fm6C.js → pickAttrs-DDRb9FIu.js} +1 -1
  135. package/src/assets/web-panel/assets/{placementArrow-DXFSVgxv.js → placementArrow-DPgtxOOD.js} +1 -1
  136. package/src/assets/web-panel/assets/{responsiveObserve-CxhQ1EaK.js → responsiveObserve-BQFRzFeZ.js} +1 -1
  137. package/src/assets/web-panel/assets/{slide-IlBI0XU8.js → slide-DQ4lmUSz.js} +1 -1
  138. package/src/assets/web-panel/assets/{statusUtils-COoXVzje.js → statusUtils-C1TR4ZiQ.js} +1 -1
  139. package/src/assets/web-panel/assets/{styleChecker-DCuq89M_.js → styleChecker-B-Suj978.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFlexGapSupport-tfNE1ALO.js → useFlexGapSupport-Q7bDZ3EI.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFs-DD4bzBkU.js → useFs-DHjRLyQH.js} +1 -1
  142. package/src/assets/web-panel/assets/{usePersonalDataHub-DVkH1hbT.js → usePersonalDataHub-lQOvCvCr.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-D1ZmXyxM.js → vnode-Ie0g4-p3.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-DblBUHfi.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/ask.js +24 -1
  148. package/src/commands/ide.js +60 -5
  149. package/src/commands/incentive.js +22 -22
  150. package/src/commands/init.js +5 -4
  151. package/src/commands/instinct.js +18 -5
  152. package/src/commands/mcp.js +30 -3
  153. package/src/commands/memory.js +18 -5
  154. package/src/commands/mtc.js +5 -1
  155. package/src/gateways/http/envelope-http-server.js +17 -2
  156. package/src/gateways/terminal/PtyManager.js +15 -2
  157. package/src/gateways/ws/message-dispatcher.js +4 -1
  158. package/src/gateways/ws/ws-server.js +20 -0
  159. package/src/gateways/ws/ws-session-gateway.js +72 -4
  160. package/src/harness/background-task-manager.js +5 -1
  161. package/src/harness/jsonl-session-store.js +51 -0
  162. package/src/harness/mcp-client.js +119 -21
  163. package/src/harness/prompt-compressor.js +24 -5
  164. package/src/harness/worktree-isolator.js +25 -10
  165. package/src/lib/agent-core.js +4 -0
  166. package/src/lib/autonomous-agent.js +36 -6
  167. package/src/lib/chat-intent-service.js +18 -4
  168. package/src/lib/checkpoint-store.js +54 -4
  169. package/src/lib/claude-code-bridge.js +48 -3
  170. package/src/lib/cost-budget.js +13 -2
  171. package/src/lib/credential-guard.js +45 -0
  172. package/src/lib/git-integration.js +68 -3
  173. package/src/lib/goal-store.js +11 -0
  174. package/src/lib/hook-manager.js +4 -0
  175. package/src/lib/hook-runner.cjs +11 -1
  176. package/src/lib/interactive-planner.js +4 -3
  177. package/src/lib/jetbrains-bridge.js +147 -0
  178. package/src/lib/json-schema-output.js +47 -0
  179. package/src/lib/jsonl-session-store.js +1 -0
  180. package/src/lib/learning/reflection-engine.js +4 -3
  181. package/src/lib/learning/skill-improver.js +4 -3
  182. package/src/lib/learning/skill-synthesizer.js +4 -3
  183. package/src/lib/llm-pricing.js +11 -4
  184. package/src/lib/llm-providers.js +11 -1
  185. package/src/lib/mcp-oauth.js +220 -20
  186. package/src/lib/mcp-serve.js +85 -6
  187. package/src/lib/orchestrator.js +42 -4
  188. package/src/lib/process-manager.js +31 -3
  189. package/src/lib/repl-bang-memorize.js +9 -1
  190. package/src/lib/repl-completer.js +29 -0
  191. package/src/lib/runnable-provider.js +7 -1
  192. package/src/lib/session-insights.js +13 -6
  193. package/src/lib/session-usage.js +21 -3
  194. package/src/lib/slot-filler.js +4 -3
  195. package/src/lib/status-line.cjs +4 -1
  196. package/src/lib/sub-agent-context.js +15 -0
  197. package/src/lib/web-fetch.js +54 -3
  198. package/src/lib/workflow-engine.js +35 -11
  199. package/src/repl/agent-repl.js +29 -0
  200. package/src/runtime/agent-core.js +252 -28
  201. package/src/runtime/headless-runner.js +13 -0
  202. package/src/runtime/headless-stream.js +133 -17
  203. package/src/runtime/mcp-config.js +72 -3
  204. package/src/runtime/pipe-safety.js +51 -0
  205. package/src/runtime/policies/agent-policy.js +3 -0
  206. package/src/assets/web-panel/assets/AIOps-C5CSd8pY.js +0 -1
  207. package/src/assets/web-panel/assets/Analytics-CzxpcV9E.js +0 -3
  208. package/src/assets/web-panel/assets/Audit-qjq04wh8.js +0 -1
  209. package/src/assets/web-panel/assets/Backup-C2Y_XNBA.js +0 -1
  210. package/src/assets/web-panel/assets/ChatBubbleRenderer-TeKnMwPj.js +0 -1
  211. package/src/assets/web-panel/assets/Codegen-TGLFk6P1.js +0 -1
  212. package/src/assets/web-panel/assets/Community-tg6-Rik1.js +0 -1
  213. package/src/assets/web-panel/assets/Compliance-DFeWryxt.js +0 -1
  214. package/src/assets/web-panel/assets/Crosschain-p7_5Gq-7.js +0 -1
  215. package/src/assets/web-panel/assets/Dashboard-Bh_HkNSD.js +0 -3
  216. package/src/assets/web-panel/assets/Federation-BJApfPV4.js +0 -1
  217. package/src/assets/web-panel/assets/Governance-B4u_KsfE.js +0 -1
  218. package/src/assets/web-panel/assets/Inference-2kr_rKQd.js +0 -1
  219. package/src/assets/web-panel/assets/KnowledgeGraph-h-nL7J6x.js +0 -1
  220. package/src/assets/web-panel/assets/Marketplace-W9Iz8b2m.js +0 -1
  221. package/src/assets/web-panel/assets/MobileBridge-4jAehfcz.js +0 -3
  222. package/src/assets/web-panel/assets/NLProgramming-D3y71FDX.js +0 -1
  223. package/src/assets/web-panel/assets/Organization-BBEmFNGM.js +0 -4
  224. package/src/assets/web-panel/assets/PdhVaultBrowser-DDU2Zc9Q.js +0 -7
  225. package/src/assets/web-panel/assets/Permissions-B1ebgevF.js +0 -4
  226. package/src/assets/web-panel/assets/Pipeline-DASuPDc3.js +0 -1
  227. package/src/assets/web-panel/assets/Privacy-DnixwmhJ.js +0 -1
  228. package/src/assets/web-panel/assets/ProjectSettings-dHyZ3SxW.js +0 -2
  229. package/src/assets/web-panel/assets/Projects-BGWEJIVT.js +0 -1
  230. package/src/assets/web-panel/assets/Recommend-BPKof_CA.js +0 -1
  231. package/src/assets/web-panel/assets/Reputation-6MNJLklK.js +0 -1
  232. package/src/assets/web-panel/assets/Row-DmGJwpfd.js +0 -1
  233. package/src/assets/web-panel/assets/Search-CCHjey_D.js +0 -1
  234. package/src/assets/web-panel/assets/Sla-BSxFvFX9.js +0 -1
  235. package/src/assets/web-panel/assets/SpeechSettings-C3rShYlV.js +0 -1
  236. package/src/assets/web-panel/assets/Templates-UtjBlJIO.js +0 -1
  237. package/src/assets/web-panel/assets/Tenant-Dd5XNbYF.js +0 -1
  238. package/src/assets/web-panel/assets/Terminal-pFyh42cu.js +0 -3
  239. package/src/assets/web-panel/assets/Tokens-CsgNsrdQ.js +0 -1
  240. package/src/assets/web-panel/assets/Trust-B1lJzsPn.js +0 -1
  241. package/src/assets/web-panel/assets/Wallet-DPxjEhbN.js +0 -4
  242. package/src/assets/web-panel/assets/WebAuthn-CuJ29YAF.js +0 -5
  243. package/src/assets/web-panel/assets/community-parser-Cuyslaku.js +0 -3
  244. package/src/assets/web-panel/assets/devWarning-B1j78JeH.js +0 -1
  245. package/src/assets/web-panel/assets/index--fcP34eT.js +0 -1
  246. package/src/assets/web-panel/assets/index-1khPb4ZS.js +0 -1
  247. package/src/assets/web-panel/assets/index-B_Z0PHC0.js +0 -12
  248. package/src/assets/web-panel/assets/index-BaN6wKWx.js +0 -1
  249. package/src/assets/web-panel/assets/index-BepInmSi.js +0 -1
  250. package/src/assets/web-panel/assets/index-BllceSdr.js +0 -1
  251. package/src/assets/web-panel/assets/index-Bt_0ygjA.js +0 -1
  252. package/src/assets/web-panel/assets/index-BzQkdgQW.js +0 -1
  253. package/src/assets/web-panel/assets/index-C2S8McM1.js +0 -55
  254. package/src/assets/web-panel/assets/index-CRSkKj9M.js +0 -1
  255. package/src/assets/web-panel/assets/index-Cgo1J_Kf.js +0 -1
  256. package/src/assets/web-panel/assets/index-CuTKOay7.js +0 -1
  257. package/src/assets/web-panel/assets/index-CxYHV8nb.js +0 -13
  258. package/src/assets/web-panel/assets/index-CzlCP4jP.js +0 -3
  259. package/src/assets/web-panel/assets/index-DZJMsVI1.js +0 -1
  260. package/src/assets/web-panel/assets/index-DgHWZVjv.js +0 -21
  261. package/src/assets/web-panel/assets/index-N7JKRLCC.js +0 -1
  262. package/src/assets/web-panel/assets/motion-BHrTGY1_.js +0 -11
  263. 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);
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import chalk from "chalk";
9
+ import { firstBalancedJson } from "../lib/json-schema-output.js";
9
10
  import fs from "fs";
10
11
  import path from "path";
11
12
  import { logger } from "../lib/logger.js";
@@ -1841,8 +1842,8 @@ with open(r"""\${tmpJson}""", "w", encoding="utf-8") as f:
1841
1842
  // Parse LLM result
1842
1843
  let updatedCells;
1843
1844
  try {
1844
- const jsonMatch = llmResult.match(/\\[\\s*\\{[\\s\\S]*\\}\\s*\\]/);
1845
- updatedCells = JSON.parse(jsonMatch ? jsonMatch[0] : llmResult);
1845
+ const jsonText = firstBalancedJson(llmResult, "[");
1846
+ updatedCells = JSON.parse(jsonText || llmResult);
1846
1847
  } catch (_e) {
1847
1848
  return { success: false, error: "LLM 返回格式不正确,无法解析 JSON", hint: "请重试或简化修改指令" };
1848
1849
  }
@@ -1947,8 +1948,8 @@ with open(r"""\${tmpJson}""", "w", encoding="utf-8") as f:
1947
1948
 
1948
1949
  let updatedRuns;
1949
1950
  try {
1950
- const jsonMatch = llmResult.match(/\\[\\s*\\{[\\s\\S]*\\}\\s*\\]/);
1951
- updatedRuns = JSON.parse(jsonMatch ? jsonMatch[0] : llmResult);
1951
+ const jsonText = firstBalancedJson(llmResult, "[");
1952
+ updatedRuns = JSON.parse(jsonText || llmResult);
1952
1953
  } catch (_e) {
1953
1954
  return { success: false, error: "LLM 返回格式不正确,无法解析 JSON", hint: "请重试或简化修改指令" };
1954
1955
  }
@@ -86,9 +86,14 @@ export function registerInstinctCommand(program) {
86
86
  logger.log(chalk.bold(`Learned Instincts (${instincts.length}):\n`));
87
87
  for (const inst of instincts) {
88
88
  const pct = (inst.confidence * 100).toFixed(0);
89
- const bar =
90
- "█".repeat(Math.round(inst.confidence * 10)) +
91
- "░".repeat(10 - Math.round(inst.confidence * 10));
89
+ // Clamp to [0,10] so a stored confidence outside [0,1]
90
+ // (recordBatch upserts caller-supplied values unclamped) can't
91
+ // make repeat() go negative and crash the whole list.
92
+ const lvl = Math.max(
93
+ 0,
94
+ Math.min(10, Math.round(inst.confidence * 10) || 0),
95
+ );
96
+ const bar = "█".repeat(lvl) + "░".repeat(10 - lvl);
92
97
  logger.log(
93
98
  ` ${chalk.gray(inst.id.slice(0, 8))} ${chalk.cyan(inst.category.padEnd(18))} ${bar} ${pct}%`,
94
99
  );
@@ -124,7 +129,13 @@ export function registerInstinctCommand(program) {
124
129
  const fs = await import("fs");
125
130
  fs.writeFileSync(options.output, JSON.stringify(rows), "utf-8");
126
131
  if (options.json) {
127
- console.log(JSON.stringify({ ok: true, count: rows.length, output: options.output }));
132
+ console.log(
133
+ JSON.stringify({
134
+ ok: true,
135
+ count: rows.length,
136
+ output: options.output,
137
+ }),
138
+ );
128
139
  } else {
129
140
  logger.log(`exported ${rows.length} instincts → ${options.output}`);
130
141
  }
@@ -141,7 +152,9 @@ export function registerInstinctCommand(program) {
141
152
  // instinct import — §8.3 restore: idempotent upsert by id from a JSON-array file
142
153
  instinct
143
154
  .command("import")
144
- .description("Import instincts from a JSON array file (idempotent upsert by id; §8.3)")
155
+ .description(
156
+ "Import instincts from a JSON array file (idempotent upsert by id; §8.3)",
157
+ )
145
158
  .requiredOption("--input <file>", "JSON array of instincts to import")
146
159
  .option("--json", "Output JSON result")
147
160
  .action(async (options) => {
@@ -42,6 +42,25 @@ export function loginWantsNoBrowser(options = {}) {
42
42
  return options.open === false || options.browser === false;
43
43
  }
44
44
 
45
+ /**
46
+ * Detect a headless environment where opening a browser is futile, so `mcp
47
+ * login` prints the authorize URL for manual opening instead of launching a
48
+ * popup that can never appear (Claude-Code 2.1.191 parity: "headless
49
+ * environments skip the browser popup and go straight to the paste-the-URL
50
+ * prompt"). The localhost callback still catches the code, so login completes.
51
+ *
52
+ * Heuristics: a CI marker forces headless on any OS; macOS/Windows otherwise
53
+ * always have a window server; on Linux/BSD a browser needs a display server
54
+ * (`DISPLAY`/`WAYLAND_DISPLAY`) — its absence (e.g. SSH without X11 forwarding)
55
+ * means headless. `CI=false`/`CI=0` are treated as not-CI.
56
+ */
57
+ export function isHeadlessEnv(env = process.env, platform = process.platform) {
58
+ const ci = env.CI;
59
+ if (ci && ci !== "false" && ci !== "0") return true;
60
+ if (platform === "darwin" || platform === "win32") return false;
61
+ return !(env.DISPLAY || env.WAYLAND_DISPLAY);
62
+ }
63
+
45
64
  // Singleton MCP client for session reuse
46
65
  let mcpClient = null;
47
66
 
@@ -146,7 +165,7 @@ export function registerMcpCommand(program) {
146
165
  mcp
147
166
  .command("login <target>")
148
167
  .description(
149
- "Authorize a remote MCP server via OAuth (opens a browser); stores the token. <target> is a URL or a configured server name.",
168
+ "Authorize a remote MCP server via OAuth (opens a browser, or prints the URL in headless/SSH environments); stores the token. <target> is a URL or a configured server name.",
150
169
  )
151
170
  .option("--scope <scope>", "OAuth scope(s) to request")
152
171
  .option(
@@ -168,8 +187,16 @@ export function registerMcpCommand(program) {
168
187
  try {
169
188
  const url = await resolveAuthTargetUrl(program, target);
170
189
  const oauth = await import("../lib/mcp-oauth.js");
171
- if (loginWantsNoBrowser(options)) {
172
- oauth._deps.openBrowser = () => false; // --no-open / --no-browser
190
+ const explicitNoBrowser = loginWantsNoBrowser(options);
191
+ if (explicitNoBrowser || isHeadlessEnv()) {
192
+ oauth._deps.openBrowser = () => false; // --no-open / --no-browser / headless
193
+ if (!explicitNoBrowser) {
194
+ logger.log(
195
+ chalk.gray(
196
+ "Headless environment — printing the authorize URL instead of opening a browser.",
197
+ ),
198
+ );
199
+ }
173
200
  }
174
201
  logger.log(chalk.gray(`Authorizing ${url} …`));
175
202
  const rec = await oauth.authorizeInteractive(url, {
@@ -143,8 +143,11 @@ export function registerMemoryCommand(program) {
143
143
  } else {
144
144
  logger.log(chalk.bold(`Memory (${entries.length} entries):\n`));
145
145
  for (const e of entries) {
146
- const stars =
147
- "★".repeat(e.importance) + "☆".repeat(5 - e.importance);
146
+ // Clamp to [0,5] so an out-of-range stored importance (rows
147
+ // written outside the clamped `memory add` path) can't make
148
+ // repeat() go negative and crash the whole list.
149
+ const imp = Math.max(0, Math.min(5, Math.round(e.importance) || 0));
150
+ const stars = "★".repeat(imp) + "☆".repeat(5 - imp);
148
151
  logger.log(
149
152
  ` ${chalk.gray(e.id.slice(0, 12))} ${chalk.yellow(stars)} ${chalk.cyan(e.category)}`,
150
153
  );
@@ -180,9 +183,17 @@ export function registerMemoryCommand(program) {
180
183
  const fs = await import("fs");
181
184
  fs.writeFileSync(options.output, JSON.stringify(entries), "utf-8");
182
185
  if (options.json) {
183
- console.log(JSON.stringify({ ok: true, count: entries.length, output: options.output }));
186
+ console.log(
187
+ JSON.stringify({
188
+ ok: true,
189
+ count: entries.length,
190
+ output: options.output,
191
+ }),
192
+ );
184
193
  } else {
185
- logger.log(`exported ${entries.length} memory entries → ${options.output}`);
194
+ logger.log(
195
+ `exported ${entries.length} memory entries → ${options.output}`,
196
+ );
186
197
  }
187
198
  } else {
188
199
  console.log(JSON.stringify(entries)); // data command: emit the array
@@ -197,7 +208,9 @@ export function registerMemoryCommand(program) {
197
208
  // memory import — §8.3 restore: idempotent upsert by id from a JSON-array file
198
209
  memory
199
210
  .command("import")
200
- .description("Import memory entries from a JSON array file (idempotent upsert by id; §8.3)")
211
+ .description(
212
+ "Import memory entries from a JSON array file (idempotent upsert by id; §8.3)",
213
+ )
201
214
  .requiredOption("--input <file>", "JSON array of memory entries to import")
202
215
  .option("--json", "Output JSON result")
203
216
  .action(async (options) => {
@@ -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)"}`,
@@ -150,8 +150,23 @@ export function createEnvelopeHttpServer(options = {}) {
150
150
  }
151
151
  if (url === "/v1/health") return handleHealth(req, res);
152
152
  const match = SESSION_EVENTS_RE.exec(url);
153
- if (match)
154
- return handleSessionEvents(req, res, decodeURIComponent(match[1]));
153
+ if (match) {
154
+ let sessionId;
155
+ try {
156
+ sessionId = decodeURIComponent(match[1]);
157
+ } catch {
158
+ // Malformed percent-encoding (e.g. `/v1/sessions/%/events`) would
159
+ // otherwise throw URIError synchronously inside this request
160
+ // listener — an uncaught exception that crashes the whole server,
161
+ // unauthenticated. Reject as a bad request instead.
162
+ res.writeHead(400, {
163
+ "Content-Type": "application/json; charset=utf-8",
164
+ });
165
+ res.end(JSON.stringify({ error: "bad_request" }));
166
+ return;
167
+ }
168
+ return handleSessionEvents(req, res, sessionId);
169
+ }
155
170
  res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
156
171
  res.end(JSON.stringify({ error: "not_found" }));
157
172
  }
@@ -75,7 +75,14 @@ export class PtyManager extends EventEmitter {
75
75
 
76
76
  create(req = {}) {
77
77
  if (this._stopped) throw new Error("pty_manager_stopped");
78
- if (this._sessions.size >= this.config.maxConcurrentSessions) {
78
+ // Count only LIVE sessions against the cap. A just-exited session lingers
79
+ // in _sessions for ~60s before the reaper removes it (see onExit), so
80
+ // counting `_sessions.size` would spuriously reject new sessions right
81
+ // after a client closes shells — painful over a reconnecting Android link
82
+ // where open/close churn is normal.
83
+ let aliveCount = 0;
84
+ for (const s of this._sessions.values()) if (s.alive) aliveCount++;
85
+ if (aliveCount >= this.config.maxConcurrentSessions) {
79
86
  throw new Error("max_concurrent_sessions_exceeded");
80
87
  }
81
88
  const shell = req.shell || this.config.defaultShell;
@@ -97,12 +104,18 @@ export class PtyManager extends EventEmitter {
97
104
  const rows = Number.isFinite(req.rows) ? req.rows : 24;
98
105
  const cmd = resolveShellCmd(shell);
99
106
 
107
+ // req.env arrives from a remote WS frame; only merge a plain object, else a
108
+ // string/array would spread into garbage numeric env keys.
109
+ const extraEnv =
110
+ req.env && typeof req.env === "object" && !Array.isArray(req.env)
111
+ ? req.env
112
+ : {};
100
113
  const proc = pty.spawn(cmd, [], {
101
114
  name: "xterm-256color",
102
115
  cols,
103
116
  rows,
104
117
  cwd,
105
- env: { ...process.env, ...(req.env || {}) },
118
+ env: { ...process.env, ...extraEnv },
106
119
  });
107
120
 
108
121
  const sessionId = randomUUID();
@@ -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",
@@ -184,6 +184,14 @@ export class ChainlessChainWSServer extends EventEmitter {
184
184
  this.token = options.token || null;
185
185
  this.maxConnections = options.maxConnections || 10;
186
186
  this.timeout = options.timeout || 30000;
187
+ // Cap inbound message size. The `ws` default is 100 MiB, and every frame is
188
+ // decoded + JSON.parsed in _handleConnection BEFORE the dispatcher's auth
189
+ // gate — so without a cap an UNAUTHENTICATED client could force a ~100 MB
190
+ // allocation per frame (×maxConnections) on a LAN-exposed (Android remote)
191
+ // deployment: a pre-auth memory-DoS. 32 MiB is generous for control frames
192
+ // and base64 image pastes while bounding abuse; `ws` closes oversize frames
193
+ // with code 1009 without buffering past the limit. Override per server.
194
+ this.maxPayloadBytes = options.maxPayloadBytes || 32 * 1024 * 1024;
187
195
 
188
196
  /** Optional Phase-5 envelope bus for fan-out to hosted HTTP SSE. */
189
197
  this.envelopeBus = options.envelopeBus || null;
@@ -214,6 +222,7 @@ export class ChainlessChainWSServer extends EventEmitter {
214
222
  this.wss = new WebSocketServer({
215
223
  port: this.port,
216
224
  host: this.host,
225
+ maxPayload: this.maxPayloadBytes,
217
226
  });
218
227
 
219
228
  this.wss.on("listening", () => {
@@ -341,6 +350,17 @@ export class ChainlessChainWSServer extends EventEmitter {
341
350
  this.emit("disconnection", { clientId });
342
351
  });
343
352
 
353
+ // A misbehaving client (oversize frame → 1009, malformed protocol, abrupt
354
+ // reset) makes the per-connection socket emit 'error'. With no listener Node
355
+ // treats it as an uncaught exception and could crash the whole server — so
356
+ // handle it here; the 'close' that follows removes the client from the map.
357
+ ws.on("error", (err) => {
358
+ this.emit("client-error", {
359
+ clientId,
360
+ error: (err && err.message) || String(err),
361
+ });
362
+ });
363
+
344
364
  ws.on("pong", () => {
345
365
  const client = this.clients.get(clientId);
346
366
  if (client) client.alive = true;
@@ -98,6 +98,31 @@ export class WSSessionManager {
98
98
 
99
99
  /** @type {Map<string, Session>} */
100
100
  this.sessions = new Map();
101
+
102
+ // Cap concurrent in-memory sessions. Each session holds a handler +
103
+ // interaction adapter + agent context, and nothing reaps them on client
104
+ // disconnect (they persist for resume; only closeSession removes one). An
105
+ // authenticated WS client could otherwise spam session-create to exhaust
106
+ // memory — this bounds it (mirrors the PTY manager's cap). Generous enough
107
+ // for legit multi-session use; resume does not create, so reconnecting to
108
+ // an existing session is never blocked by the cap. Override per manager.
109
+ this.maxSessions = Number.isFinite(options.maxSessions)
110
+ ? options.maxSessions
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;
101
126
  }
102
127
 
103
128
  _normalizeEnabledToolNames(enabledToolNames) {
@@ -264,6 +289,11 @@ export class WSSessionManager {
264
289
  * @returns {{ sessionId: string }}
265
290
  */
266
291
  createSession(options = {}) {
292
+ if (this.sessions.size >= this.maxSessions) {
293
+ throw new Error(
294
+ `max_sessions_exceeded: ${this.maxSessions} concurrent sessions reached — close some before creating more`,
295
+ );
296
+ }
267
297
  const sessionId = this._generateId();
268
298
  const type = options.type || "agent";
269
299
  const baseProjectRoot = options.projectRoot || this.defaultProjectRoot;
@@ -483,9 +513,7 @@ export class WSSessionManager {
483
513
  permanentMemory,
484
514
  reviewState: metadata.reviewState || null,
485
515
  pendingPatches: this._hydratePendingPatches(metadata.pendingPatches),
486
- patchHistory: Array.isArray(metadata.patchHistory)
487
- ? metadata.patchHistory
488
- : [],
516
+ patchHistory: this._boundPatchHistory(metadata.patchHistory),
489
517
  taskGraph: this._hydrateTaskGraph(metadata.taskGraph),
490
518
  interaction: null,
491
519
  createdAt: dbSession.created_at,
@@ -873,6 +901,15 @@ export class WSSessionManager {
873
901
  if (!(session.pendingPatches instanceof Map)) {
874
902
  session.pendingPatches = new Map();
875
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
+ }
876
913
  session.pendingPatches.set(patchId, patch);
877
914
  session.lastActivity = now;
878
915
  this._persistSessionState(sessionId);
@@ -901,11 +938,35 @@ export class WSSessionManager {
901
938
  session.patchHistory = [];
902
939
  }
903
940
  session.patchHistory.push(patch);
941
+ this._trimPatchHistory(session);
904
942
  session.lastActivity = patch.resolvedAt;
905
943
  this._persistSessionState(sessionId);
906
944
  return patch;
907
945
  }
908
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
+
909
970
  /**
910
971
  * Discard a pending patch. Same bookkeeping as applyPatch but records a
911
972
  * "rejected" decision instead.
@@ -928,6 +989,7 @@ export class WSSessionManager {
928
989
  session.patchHistory = [];
929
990
  }
930
991
  session.patchHistory.push(patch);
992
+ this._trimPatchHistory(session);
931
993
  session.lastActivity = patch.resolvedAt;
932
994
  this._persistSessionState(sessionId);
933
995
  return patch;
@@ -1323,7 +1385,13 @@ export class WSSessionManager {
1323
1385
  _hydratePendingPatches(list) {
1324
1386
  const map = new Map();
1325
1387
  if (Array.isArray(list)) {
1326
- 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) {
1327
1395
  if (patch && patch.patchId) {
1328
1396
  map.set(patch.patchId, patch);
1329
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,