chainlesschain 0.162.124 → 0.162.126

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/AIOps-BrTOcqIJ.js +1 -0
  4. package/src/assets/web-panel/assets/{ActionButton-COjZZKWG.js → ActionButton-na9cocmf.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-4ZDZE7lc.js → Analytics-BtDygkpt.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-CYH5k2Rp.js → AppLayout-D_t5CS18.js} +5 -5
  7. package/src/assets/web-panel/assets/Audit-BYMZccCy.js +1 -0
  8. package/src/assets/web-panel/assets/{Backup-CiX61Qc2.js → Backup-D2qDfAdL.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-CDxIQokd.js → BaseInput-kj456Ju9.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-CaKgdRab.js → Chat-BY0A0ZLE.js} +4 -4
  11. package/src/assets/web-panel/assets/ChatBubbleRenderer-D1VvrOPQ.js +1 -0
  12. package/src/assets/web-panel/assets/{Checkbox-CjiWfu4s.js → Checkbox-DiBhtXsh.js} +1 -1
  13. package/src/assets/web-panel/assets/Codegen-B1ER0L0j.js +1 -0
  14. package/src/assets/web-panel/assets/{Col-ZKWREyNs.js → Col-CjLmza7D.js} +1 -1
  15. package/src/assets/web-panel/assets/Community-BNId05U7.js +1 -0
  16. package/src/assets/web-panel/assets/{Compact-DqknM98n.js → Compact-BuuMj7K1.js} +1 -1
  17. package/src/assets/web-panel/assets/Compliance-DUuDycaJ.js +1 -0
  18. package/src/assets/web-panel/assets/{Cowork-Dp29kHsC.js → Cowork-BKHiF6uL.js} +2 -2
  19. package/src/assets/web-panel/assets/{Cron-zmEJAetz.js → Cron-C7p-lHnC.js} +2 -2
  20. package/src/assets/web-panel/assets/Crosschain-BvSOx8a6.js +1 -0
  21. package/src/assets/web-panel/assets/{DID-D_UFNzJ5.js → DID-D1k9jgHv.js} +2 -2
  22. package/src/assets/web-panel/assets/Dashboard-B9mCCnqY.js +3 -0
  23. package/src/assets/web-panel/assets/{Dropdown-CH7l3KCs.js → Dropdown-ge9UHSXF.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-Bud2M3XX.js → EmailListRenderer-TZO7ppXi.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bd2_8uME.js → FamilyGuardDashboard-DkhLJmzx.js} +1 -1
  26. package/src/assets/web-panel/assets/Federation-JLuA_8zp.js +1 -0
  27. package/src/assets/web-panel/assets/{FormItemContext-CpSH464Y.js → FormItemContext-C4w-rzNg.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-CANo8O-y.js → GenericCardRenderer-Btns4sV9.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-hvuZVoD3.js → Git-BheXMjzC.js} +2 -2
  30. package/src/assets/web-panel/assets/Governance-D9rLlatH.js +1 -0
  31. package/src/assets/web-panel/assets/Inference-DiklIMcd.js +1 -0
  32. package/src/assets/web-panel/assets/KnowledgeGraph-C70EWL03.js +1 -0
  33. package/src/assets/web-panel/assets/{Logs-DDkTnedu.js → Logs-Zt_MLI10.js} +2 -2
  34. package/src/assets/web-panel/assets/Marketplace-suQxES99.js +1 -0
  35. package/src/assets/web-panel/assets/{McpTools-Qx78XyOT.js → McpTools-CclpCDpY.js} +3 -3
  36. package/src/assets/web-panel/assets/{Memory-CXYDO1oT.js → Memory-B2lHOUwF.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileBridge-BMXKoDAD.js +1 -0
  38. package/src/assets/web-panel/assets/{MobileProjects-DEDbWNIk.js → MobileProjects-a6mWVdBV.js} +1 -1
  39. package/src/assets/web-panel/assets/{Mtc-Dwa_vlR8.js → Mtc-BFpM4Fkj.js} +2 -2
  40. package/src/assets/web-panel/assets/{MtcAudit-DAVkxhJv.js → MtcAudit-DDQkxkbS.js} +4 -4
  41. package/src/assets/web-panel/assets/{Multisig-C3upmgPN.js → Multisig-Crkd_zKQ.js} +3 -3
  42. package/src/assets/web-panel/assets/NLProgramming-Dpk5An7B.js +1 -0
  43. package/src/assets/web-panel/assets/{Notes-B1Oy3FbC.js → Notes-BrFnOMNz.js} +4 -4
  44. package/src/assets/web-panel/assets/{NotificationSettings-Bs4-Fc6b.js → NotificationSettings-CM0iApFM.js} +1 -1
  45. package/src/assets/web-panel/assets/{OrderTableRenderer-CEy1pIeq.js → OrderTableRenderer-XXjeqkdz.js} +1 -1
  46. package/src/assets/web-panel/assets/{Organization-D4cJ1UNo.js → Organization-CHFW_38T.js} +2 -2
  47. package/src/assets/web-panel/assets/{Overflow-fdzvlF7x.js → Overflow-C-vl3EjV.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-Y13PwXBA.js → P2P-BT7Slavq.js} +2 -2
  49. package/src/assets/web-panel/assets/{PdhVaultBrowser-Ck1zCLe6.js → PdhVaultBrowser-DRUaULap.js} +3 -3
  50. package/src/assets/web-panel/assets/{Permissions-CvEXP6LC.js → Permissions-DkhOAvMz.js} +2 -2
  51. package/src/assets/web-panel/assets/{PersonalDataHub-JeP7IWMW.js → PersonalDataHub-Dl7dSOvV.js} +3 -3
  52. package/src/assets/web-panel/assets/Pipeline-B3nVI4p6.js +1 -0
  53. package/src/assets/web-panel/assets/Privacy-Dv1SXx1Q.js +1 -0
  54. package/src/assets/web-panel/assets/{ProjectInit-DeWd9UcS.js → ProjectInit-DrAhVi5p.js} +2 -2
  55. package/src/assets/web-panel/assets/{ProjectSettings-DUpVuU9F.js → ProjectSettings-h-ggCYN_.js} +2 -2
  56. package/src/assets/web-panel/assets/Projects-CM91hYdr.js +1 -0
  57. package/src/assets/web-panel/assets/{Providers-DECW00ek.js → Providers-rfkZel3N.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-Dl-pK9Io.js → QuickAsk-XepZB7h_.js} +1 -1
  59. package/src/assets/web-panel/assets/Recommend-DzQWsh1a.js +1 -0
  60. package/src/assets/web-panel/assets/Reputation-CDeaal0j.js +1 -0
  61. package/src/assets/web-panel/assets/Row-BN4aKhFH.js +1 -0
  62. package/src/assets/web-panel/assets/{RssFeed-iyQT5q9l.js → RssFeed-INP6VYAA.js} +3 -3
  63. package/src/assets/web-panel/assets/Search-ChPbwatu.js +1 -0
  64. package/src/assets/web-panel/assets/{Security-c4Jxf5Ih.js → Security-C97ZCY8N.js} +4 -4
  65. package/src/assets/web-panel/assets/{Services-CRuisolj.js → Services-Cbtl2Ajs.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-CCnzJkb-.js → Skeleton-B13sFxBQ.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-CZURCwZg.js → Skills-vI9zSIKX.js} +1 -1
  68. package/src/assets/web-panel/assets/Sla-Cr3oBH39.js +1 -0
  69. package/src/assets/web-panel/assets/SpeechSettings-BwemqPPV.js +1 -0
  70. package/src/assets/web-panel/assets/{SyncSettings-CJWVtd87.js → SyncSettings-CLA78P-Z.js} +2 -2
  71. package/src/assets/web-panel/assets/{Tasks-61lrQ_lS.js → Tasks-DBjQ_DOM.js} +1 -1
  72. package/src/assets/web-panel/assets/Templates-BkSLDkBs.js +1 -0
  73. package/src/assets/web-panel/assets/Tenant-Dn0nSlib.js +1 -0
  74. package/src/assets/web-panel/assets/{Terminal-CP15hcNS.js → Terminal-C8f4boru.js} +2 -2
  75. package/src/assets/web-panel/assets/{TimelineRenderer-Sl5uXrkN.js → TimelineRenderer-DW_rMtJR.js} +1 -1
  76. package/src/assets/web-panel/assets/Tokens-BdffrKYV.js +1 -0
  77. package/src/assets/web-panel/assets/{Trigger-BLCQEOF2.js → Trigger-CX4CUg-Q.js} +1 -1
  78. package/src/assets/web-panel/assets/Trust-Cq9Bl7Od.js +1 -0
  79. package/src/assets/web-panel/assets/{UkeySign-Dwp0zXQ6.js → UkeySign-BQy18_VY.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CIHA55Sx.js → VideoEditing-h5hzK7Vw.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-Hjajvnto.js → Wallet-BLotT3gw.js} +3 -3
  82. package/src/assets/web-panel/assets/{WebAuthn-DqSURUvI.js → WebAuthn-8cCANYLE.js} +4 -4
  83. package/src/assets/web-panel/assets/{WorkflowEditor-B5bHRwUG.js → WorkflowEditor-Db6NwtAv.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-DsheLKkG.js → chat-BPk1LbpL.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-CST2UkgL.js → colors-BnNal71p.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-yqEoy1L7.js → compact-item-W2VC0tcy.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-Jwp78HFY.js → createContext-DceVsgnZ.js} +1 -1
  88. package/src/assets/web-panel/assets/devWarning--rwFDBhx.js +1 -0
  89. package/src/assets/web-panel/assets/{hasIn-CVS5mFEP.js → hasIn-BEq6EDcp.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-BLTUVd0V.js → index-5gIBUFBn.js} +2 -2
  91. package/src/assets/web-panel/assets/index-APCHxOkM.js +3 -0
  92. package/src/assets/web-panel/assets/index-B2dMhwZi.js +1 -0
  93. package/src/assets/web-panel/assets/{index-C0FZScMe.js → index-B8cPjrEH.js} +4 -4
  94. package/src/assets/web-panel/assets/{index-CjfN2CpJ.js → index-BDC5HsxZ.js} +1 -1
  95. package/src/assets/web-panel/assets/index-BPFuPGKi.js +1 -0
  96. package/src/assets/web-panel/assets/{index-SjxCCf5h.js → index-BUkDcIwJ.js} +2 -2
  97. package/src/assets/web-panel/assets/{index-Cjig5i3a.js → index-BWcCyH6-.js} +28 -26
  98. package/src/assets/web-panel/assets/{index-AnW25bEq.js → index-BgZikQoh.js} +1 -1
  99. package/src/assets/web-panel/assets/index-Bwjus13Y.js +1 -0
  100. package/src/assets/web-panel/assets/index-BzSzRWsw.js +55 -0
  101. package/src/assets/web-panel/assets/index-C1K9CsGr.js +1 -0
  102. package/src/assets/web-panel/assets/{index-DDAigsel.js → index-CBZVISK6.js} +2 -2
  103. package/src/assets/web-panel/assets/index-CEJN-R2L.js +1 -0
  104. package/src/assets/web-panel/assets/index-CG5dGC9E.js +1 -0
  105. package/src/assets/web-panel/assets/{index-DxaU_lza.js → index-CMGLpstm.js} +1 -1
  106. package/src/assets/web-panel/assets/index-CWrGCndd.js +1 -0
  107. package/src/assets/web-panel/assets/index-CbcvfpCV.js +1 -0
  108. package/src/assets/web-panel/assets/{index-De600Jjm.js → index-Ceqv4lI2.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Cy0dNzkp.js → index-CsT6frT8.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CDAPnZUs.js → index-D8PQKsDT.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Crk4KWLW.js → index-DGncdA-j.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-R-VGFpi6.js → index-DHanQHO0.js} +3 -3
  113. package/src/assets/web-panel/assets/index-DaUBk28E.js +12 -0
  114. package/src/assets/web-panel/assets/{index-BzetOasL.js → index-Di2J4b4V.js} +1 -1
  115. package/src/assets/web-panel/assets/index-DtuLvWZN.js +21 -0
  116. package/src/assets/web-panel/assets/{index-Dgyn8-wN.js → index-DvS1J8xc.js} +2 -2
  117. package/src/assets/web-panel/assets/{index-CtyEOGuj.js → index-DxePJdwP.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CzsKK0tQ.js → index-Et4XvL49.js} +2 -2
  119. package/src/assets/web-panel/assets/{index-BuI27WSx.js → index-GlFxgcj4.js} +2 -2
  120. package/src/assets/web-panel/assets/index-HV119Oui.js +1 -0
  121. package/src/assets/web-panel/assets/index-InGW87pj.js +13 -0
  122. package/src/assets/web-panel/assets/{index-Dxljh9Fu.js → index-P1-s8JR7.js} +2 -2
  123. package/src/assets/web-panel/assets/{index-CSBPc-sI.js → index-SdABCNoi.js} +2 -2
  124. package/src/assets/web-panel/assets/{index-BCHAUdel.js → index-WTAZbN-c.js} +1 -1
  125. package/src/assets/web-panel/assets/index-_2I-KzOj.js +1 -0
  126. package/src/assets/web-panel/assets/index-m1sVaWVU.js +1 -0
  127. package/src/assets/web-panel/assets/index-s37wwyeN.js +1 -0
  128. package/src/assets/web-panel/assets/{index-CRBbVS43.js → index-tyWABqp9.js} +1 -1
  129. package/src/assets/web-panel/assets/{initDefaultProps-ClAjrsQ-.js → initDefaultProps-DzrCDb3j.js} +1 -1
  130. package/src/assets/web-panel/assets/motion-BQERVnE3.js +11 -0
  131. package/src/assets/web-panel/assets/{move-DMelzIW-.js → move-I9ACA5XJ.js} +1 -1
  132. package/src/assets/web-panel/assets/mtc-parser-Dd2Pw-tr.js +1 -0
  133. package/src/assets/web-panel/assets/{omit-oxecfU3b.js → omit-DwzYRzWV.js} +1 -1
  134. package/src/assets/web-panel/assets/{pickAttrs-DJ5F5M6x.js → pickAttrs-BSGULyIL.js} +1 -1
  135. package/src/assets/web-panel/assets/{placementArrow-DdbKsant.js → placementArrow-tSYYjAts.js} +1 -1
  136. package/src/assets/web-panel/assets/{responsiveObserve-BUuM5cmS.js → responsiveObserve-Dy2lqikT.js} +1 -1
  137. package/src/assets/web-panel/assets/{slide-DSV0ccvR.js → slide-RaB4Uq0c.js} +1 -1
  138. package/src/assets/web-panel/assets/{statusUtils-B-6oLAXf.js → statusUtils-CBpC_wZg.js} +1 -1
  139. package/src/assets/web-panel/assets/{styleChecker-DdCAHtsZ.js → styleChecker-BEKwPWt5.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFlexGapSupport-ZORkcoZd.js → useFlexGapSupport-C4CnYJH1.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFs-BFp46LoP.js → useFs-n24jutw5.js} +1 -1
  142. package/src/assets/web-panel/assets/{usePersonalDataHub-H7rxgbdW.js → usePersonalDataHub-DMgS8F6G.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-eIq4Tk0J.js → vnode-BSp2KYcj.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-CTNrv8-H.js → zoom-7UfQtTPh.js} +1 -1
  145. package/src/assets/web-panel/index.html +1 -1
  146. package/src/commands/agent.js +51 -2
  147. package/src/commands/compliance.js +3 -1
  148. package/src/commands/ide.js +60 -5
  149. package/src/commands/incentive.js +22 -22
  150. package/src/commands/mtc.js +5 -1
  151. package/src/commands/review.js +47 -17
  152. package/src/commands/session.js +11 -2
  153. package/src/gateways/ws/message-dispatcher.js +149 -166
  154. package/src/gateways/ws/ws-session-gateway.js +95 -9
  155. package/src/harness/background-task-manager.js +5 -1
  156. package/src/harness/jsonl-session-store.js +90 -12
  157. package/src/harness/mcp-client.js +62 -21
  158. package/src/harness/prompt-compressor.js +24 -5
  159. package/src/harness/worktree-isolator.js +10 -1
  160. package/src/lib/agent-core.js +2 -0
  161. package/src/lib/audit-logger.js +3 -1
  162. package/src/lib/autonomous-agent.js +29 -0
  163. package/src/lib/chat-core.js +5 -2
  164. package/src/lib/chat-intent-service.js +82 -23
  165. package/src/lib/checkpoint-store.js +31 -4
  166. package/src/lib/claude-code-bridge.js +48 -3
  167. package/src/lib/config-manager.js +25 -1
  168. package/src/lib/cost-budget.js +13 -2
  169. package/src/lib/credential-guard.js +81 -1
  170. package/src/lib/git-integration.js +16 -2
  171. package/src/lib/goal-store.js +11 -0
  172. package/src/lib/hook-manager.js +4 -0
  173. package/src/lib/interaction-adapter.js +32 -11
  174. package/src/lib/jetbrains-bridge.js +147 -0
  175. package/src/lib/jsonl-session-store.js +2 -0
  176. package/src/lib/llm-config-defaults.js +37 -0
  177. package/src/lib/llm-pricing.js +11 -4
  178. package/src/lib/llm-providers.js +11 -1
  179. package/src/lib/mcp-oauth.js +88 -10
  180. package/src/lib/mcp-serve.js +29 -0
  181. package/src/lib/orchestrator.js +38 -2
  182. package/src/lib/permission-engine.js +4 -1
  183. package/src/lib/pipeline-orchestrator.js +20 -2
  184. package/src/lib/project-instructions.js +13 -0
  185. package/src/lib/repl-bang-memorize.js +9 -1
  186. package/src/lib/repl-denials.js +137 -0
  187. package/src/lib/runnable-provider.js +7 -1
  188. package/src/lib/safe-json.js +22 -0
  189. package/src/lib/sandbox-v2.js +7 -6
  190. package/src/lib/search-command.js +65 -0
  191. package/src/lib/session-insights.js +13 -6
  192. package/src/lib/session-usage.js +21 -3
  193. package/src/lib/settings-hooks.cjs +133 -0
  194. package/src/lib/settings-loader.cjs +16 -7
  195. package/src/lib/social-graph.js +3 -1
  196. package/src/lib/status-line.cjs +4 -1
  197. package/src/lib/stream-retry.js +27 -0
  198. package/src/lib/sub-agent-context.js +9 -0
  199. package/src/lib/turn-context.js +15 -5
  200. package/src/repl/agent-repl.js +110 -8
  201. package/src/runtime/__tests__/message-roles.test.js +36 -3
  202. package/src/runtime/agent-core.js +179 -52
  203. package/src/runtime/coding-agent-contract-shared.cjs +9 -2
  204. package/src/runtime/coding-agent-shell-policy.cjs +23 -2
  205. package/src/runtime/file-ref-expander.js +51 -7
  206. package/src/runtime/headless-runner.js +96 -2
  207. package/src/runtime/headless-stream.js +78 -3
  208. package/src/runtime/mcp-config.js +114 -4
  209. package/src/runtime/message-roles.js +14 -1
  210. package/src/runtime/pipe-safety.js +51 -0
  211. package/src/runtime/policies/agent-policy.js +3 -0
  212. package/src/assets/web-panel/assets/AIOps-BsDJlD_l.js +0 -1
  213. package/src/assets/web-panel/assets/Audit-BKlFbLdl.js +0 -1
  214. package/src/assets/web-panel/assets/ChatBubbleRenderer-DgvpwU5o.js +0 -1
  215. package/src/assets/web-panel/assets/Codegen-tKWGzajw.js +0 -1
  216. package/src/assets/web-panel/assets/Community-CmLuPBUU.js +0 -1
  217. package/src/assets/web-panel/assets/Compliance-PZw0aBLI.js +0 -1
  218. package/src/assets/web-panel/assets/Crosschain-Sb-uM7Ty.js +0 -1
  219. package/src/assets/web-panel/assets/Dashboard-Btag698v.js +0 -3
  220. package/src/assets/web-panel/assets/Federation-1jhstF5P.js +0 -1
  221. package/src/assets/web-panel/assets/Governance-BQrWksKt.js +0 -1
  222. package/src/assets/web-panel/assets/Inference-jEBTp12v.js +0 -1
  223. package/src/assets/web-panel/assets/KnowledgeGraph-tDiV2taf.js +0 -1
  224. package/src/assets/web-panel/assets/Marketplace-9Yi32avt.js +0 -1
  225. package/src/assets/web-panel/assets/MobileBridge-MfUfkHA0.js +0 -1
  226. package/src/assets/web-panel/assets/NLProgramming-DO3CZ0_z.js +0 -1
  227. package/src/assets/web-panel/assets/Pipeline-BZ7wRzG1.js +0 -1
  228. package/src/assets/web-panel/assets/Privacy-BJ6qj9Hv.js +0 -1
  229. package/src/assets/web-panel/assets/Projects-y1WbI337.js +0 -1
  230. package/src/assets/web-panel/assets/Recommend-Bju04wTd.js +0 -1
  231. package/src/assets/web-panel/assets/Reputation-BUPa3nEk.js +0 -1
  232. package/src/assets/web-panel/assets/Row-WYqqaq_5.js +0 -1
  233. package/src/assets/web-panel/assets/Search-CrfwJF0R.js +0 -1
  234. package/src/assets/web-panel/assets/Sla-CrMzaEi2.js +0 -1
  235. package/src/assets/web-panel/assets/SpeechSettings-jTDOM5eY.js +0 -1
  236. package/src/assets/web-panel/assets/Templates-Cg-iF2g1.js +0 -1
  237. package/src/assets/web-panel/assets/Tenant-DrDv1FFc.js +0 -1
  238. package/src/assets/web-panel/assets/Tokens-yRIZTVsR.js +0 -1
  239. package/src/assets/web-panel/assets/Trust-D5xq8z6s.js +0 -1
  240. package/src/assets/web-panel/assets/community-parser-Cuyslaku.js +0 -3
  241. package/src/assets/web-panel/assets/devWarning-C8to6gQk.js +0 -1
  242. package/src/assets/web-panel/assets/index-BeblNJDH.js +0 -1
  243. package/src/assets/web-panel/assets/index-C130LLPq.js +0 -1
  244. package/src/assets/web-panel/assets/index-C8rq85gu.js +0 -1
  245. package/src/assets/web-panel/assets/index-CIE2n8k_.js +0 -1
  246. package/src/assets/web-panel/assets/index-CTd3lDxp.js +0 -13
  247. package/src/assets/web-panel/assets/index-CUWj5L2s.js +0 -1
  248. package/src/assets/web-panel/assets/index-ChRL6rgA.js +0 -3
  249. package/src/assets/web-panel/assets/index-CrlRa054.js +0 -1
  250. package/src/assets/web-panel/assets/index-Cu6Ql64n.js +0 -1
  251. package/src/assets/web-panel/assets/index-Cx_t5rWw.js +0 -12
  252. package/src/assets/web-panel/assets/index-DZfnYE6e.js +0 -1
  253. package/src/assets/web-panel/assets/index-D_1b7uh6.js +0 -55
  254. package/src/assets/web-panel/assets/index-D_e9gwfn.js +0 -1
  255. package/src/assets/web-panel/assets/index-DbxAlpPC.js +0 -21
  256. package/src/assets/web-panel/assets/index-DiK4vSZa.js +0 -1
  257. package/src/assets/web-panel/assets/index-DpYn5v2k.js +0 -1
  258. package/src/assets/web-panel/assets/index-diWkx2Wq.js +0 -1
  259. package/src/assets/web-panel/assets/motion-BalXv_60.js +0 -11
  260. package/src/assets/web-panel/assets/mtc-parser-CEQffxds.js +0 -1
@@ -17,6 +17,43 @@
17
17
  * lesson as the --settings "opus"→404 vision trap.
18
18
  * - No configured provider → unchanged (runner defaults apply: ollama).
19
19
  */
20
+ /**
21
+ * Reconcile a MISLABELED llm config/options object. The `baseUrl` is the
22
+ * authoritative signal of which provider you actually reach, so when `provider`
23
+ * disagrees with the provider its `baseUrl` belongs to, the config is simply
24
+ * mislabeled — the recurring "provider:anthropic + volces baseUrl + model:haiku"
25
+ * corruption that makes runnable-provider relabel (and emit "provider 配置与
26
+ * baseUrl 不一致") on EVERY run because the on-disk config never gets fixed.
27
+ *
28
+ * Correct `provider` to match the endpoint, and if the model is foreign to the
29
+ * corrected provider, replace it with that provider's default. Pure: the input
30
+ * is never mutated — returns `{ llm, changed }` where `llm` is the original
31
+ * object (changed:false) or a corrected shallow copy (changed:true). Works for
32
+ * both a `config.llm` block and a resolved request-`options` (same shape).
33
+ *
34
+ * `inferProviderFromBaseUrl` returns null for unknown/custom hosts (proxies),
35
+ * so an intentionally-custom endpoint is left untouched.
36
+ */
37
+ export async function reconcileConfigLlmProvider(cfgLlm = {}) {
38
+ if (!cfgLlm || !cfgLlm.provider || !cfgLlm.baseUrl) {
39
+ return { llm: cfgLlm, changed: false };
40
+ }
41
+ const { inferProviderFromBaseUrl, modelForeignToProvider } =
42
+ await import("./runnable-provider.js");
43
+ const inferred = inferProviderFromBaseUrl(cfgLlm.baseUrl);
44
+ if (!inferred || inferred === cfgLlm.provider) {
45
+ return { llm: cfgLlm, changed: false };
46
+ }
47
+ const corrected = { ...cfgLlm, provider: inferred };
48
+ if (modelForeignToProvider(inferred, cfgLlm.model)) {
49
+ const { selectModelForTask, TaskType } =
50
+ await import("./task-model-selector.js");
51
+ const def = selectModelForTask(inferred, TaskType.CHAT);
52
+ if (def) corrected.model = def;
53
+ }
54
+ return { llm: corrected, changed: true };
55
+ }
56
+
20
57
  export function applyConfigLlmDefaults(options = {}, cfgLlm = {}, opts = {}) {
21
58
  // The IDE chat panel pins --provider/--model (read from config) but DROPS
22
59
  // --base-url/--api-key. With an explicit --provider the block below bails, so
@@ -96,9 +96,10 @@ const round = (n, dp = 6) => {
96
96
  *
97
97
  * Per provider, a user entry whose `match` equals a built-in pattern REPLACES
98
98
  * it; brand-new patterns are prepended so they win ties; unknown providers are
99
- * added. Malformed entries (missing match / non-numeric rate) are skipped so a
100
- * bad config line can't crash cost reporting. Returns a new table; never
101
- * mutates PRICE_TABLE or the input.
99
+ * added. Malformed entries (missing match / non-numeric / negative / Infinity
100
+ * rate) are skipped so a bad config line can't crash cost reporting or produce a
101
+ * negative cost that undercounts spend. Returns a new table; never mutates
102
+ * PRICE_TABLE or the input.
102
103
  *
103
104
  * @param {object} [overrides]
104
105
  * @param {object} [base=PRICE_TABLE]
@@ -123,8 +124,14 @@ export function mergePricing(overrides, base = PRICE_TABLE) {
123
124
  e &&
124
125
  typeof e.match === "string" &&
125
126
  e.match.trim() &&
127
+ // Rates must be finite AND non-negative — a negative price would
128
+ // produce a negative cost that undercounts spend (and could keep a
129
+ // CostBudget under its cap). `>= 0` also rejects NaN (NaN >= 0 is
130
+ // false); isFinite additionally rejects Infinity.
126
131
  Number.isFinite(Number(e.in)) &&
127
- Number.isFinite(Number(e.out)),
132
+ Number(e.in) >= 0 &&
133
+ Number.isFinite(Number(e.out)) &&
134
+ Number(e.out) >= 0,
128
135
  )
129
136
  .map((e) => ({
130
137
  match: e.match.toLowerCase(),
@@ -181,12 +181,22 @@ export class LLMProviderRegistry {
181
181
  .prepare("SELECT * FROM llm_providers WHERE custom = 1")
182
182
  .all();
183
183
  for (const row of rows) {
184
+ // Tolerate a corrupt `models` TEXT column: `|| "[]"` only covers null,
185
+ // not malformed JSON, and an unguarded throw here would abort the whole
186
+ // custom-provider load (and the registry constructor) over one bad row.
187
+ let models = [];
188
+ try {
189
+ const parsed = JSON.parse(row.models || "[]");
190
+ if (Array.isArray(parsed)) models = parsed;
191
+ } catch {
192
+ /* corrupt models column — fall back to no models for this provider */
193
+ }
184
194
  this.providers.set(row.name, {
185
195
  name: row.name,
186
196
  displayName: row.display_name,
187
197
  baseUrl: row.base_url,
188
198
  apiKeyEnv: row.api_key_env,
189
- models: JSON.parse(row.models || "[]"),
199
+ models,
190
200
  custom: true,
191
201
  free: false,
192
202
  });
@@ -38,6 +38,10 @@ export const _deps = {
38
38
  now: () => Date.now(),
39
39
  // Backoff sleep seam (tests override with a no-op so the retry doesn't wait).
40
40
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
41
+ // Visible-warning seam (silent under vitest; tests override to assert).
42
+ warn: (msg) => {
43
+ if (!process.env.VITEST) process.stderr.write(msg);
44
+ },
41
45
  };
42
46
 
43
47
  /** Discovery/token requests retry ONCE after a transient error (CC 2.1.191). */
@@ -194,6 +198,12 @@ export async function discoverAuthMetadata(
194
198
  { resourceMetadataUrl } = {},
195
199
  ) {
196
200
  const origin = new URL(serverUrl).origin;
201
+ // Discovery itself carries no secrets, but its responses dictate the
202
+ // authorization/token endpoints — so a cleartext-remote discovery is a
203
+ // downgrade vector (a MITM could inject endpoints). Refuse a non-loopback
204
+ // http MCP URL up front (loopback http stays allowed for local dev), matching
205
+ // the endpoint enforcement below.
206
+ assertSecureEndpoint(origin, "MCP server URL");
197
207
  // 1. protected-resource metadata (RFC 9728) → authorization_servers[]
198
208
  let authServer = origin;
199
209
  try {
@@ -209,6 +219,12 @@ export async function discoverAuthMetadata(
209
219
  } catch {
210
220
  // no protected-resource doc → assume the origin is its own auth server
211
221
  }
222
+ // The authorization-server location can come from the REMOTE protected-
223
+ // resource doc, so a malicious/MITM'd doc could point discovery at a
224
+ // cleartext host. Refuse a non-loopback http authServer before fetching its
225
+ // metadata (the discovered endpoints are HTTPS-checked too, but this fails
226
+ // closed earlier and blocks the downgrade).
227
+ assertSecureEndpoint(authServer, "authorization server");
212
228
  // 2. authorization-server metadata (RFC 8414)
213
229
  const candidates = [
214
230
  `${authServer}/.well-known/oauth-authorization-server`,
@@ -359,17 +375,56 @@ export function tokenStorePath() {
359
375
  return pathDefault.join(_deps.homedir(), ".chainlesschain", "mcp-oauth.json");
360
376
  }
361
377
 
362
- export function loadTokenStore() {
363
- const file = tokenStorePath();
378
+ /**
379
+ * Read the token store, distinguishing "absent / empty" (ok) from "exists but
380
+ * unreadable / corrupt" (ok:false). The distinction matters on the WRITE path:
381
+ * a read-modify-write that treats an unreadable store as `{}` would overwrite
382
+ * and silently destroy every other server's token.
383
+ */
384
+ function _readStore(file) {
385
+ if (!_deps.fs.existsSync(file)) return { ok: true, store: {} };
364
386
  try {
365
- if (!_deps.fs.existsSync(file)) return {};
366
387
  const data = JSON.parse(_deps.fs.readFileSync(file, "utf-8"));
367
- return data && typeof data === "object" ? data : {};
388
+ return { ok: true, store: data && typeof data === "object" ? data : {} };
389
+ } catch (err) {
390
+ return { ok: false, store: {}, err };
391
+ }
392
+ }
393
+
394
+ const _warnedCorruptStore = new Set();
395
+ function _warnCorruptStoreOnce(file, err, archived) {
396
+ if (_warnedCorruptStore.has(file)) return;
397
+ _warnedCorruptStore.add(file);
398
+ _deps.warn(
399
+ `⚠️ MCP OAuth token store at ${file} is unreadable (${err?.message || "parse error"}). ` +
400
+ `Saved MCP logins are unavailable${archived ? `; the unreadable file was preserved as ${archived}` : ""} — ` +
401
+ "re-run `cc mcp login <url>` for the servers you use.\n",
402
+ );
403
+ }
404
+
405
+ /** Move an unreadable store aside so a fresh write doesn't destroy it. Returns
406
+ * the archive path, or "" when it couldn't be moved (best-effort). */
407
+ function _archiveCorruptStore(file) {
408
+ if (typeof _deps.fs.renameSync !== "function") return "";
409
+ const ts = typeof _deps.now === "function" ? _deps.now() : Date.now();
410
+ const dest = `${file}.corrupt-${ts}`;
411
+ try {
412
+ _deps.fs.renameSync(file, dest);
413
+ return dest;
368
414
  } catch {
369
- return {};
415
+ return "";
370
416
  }
371
417
  }
372
418
 
419
+ export function loadTokenStore() {
420
+ const file = tokenStorePath();
421
+ const r = _readStore(file);
422
+ // Read callers (getStoredToken) treat an unreadable store as "no token", but
423
+ // the user should still know their saved logins silently vanished.
424
+ if (!r.ok) _warnCorruptStoreOnce(file, r.err, "");
425
+ return r.store;
426
+ }
427
+
373
428
  export function getStoredToken(serverUrl) {
374
429
  return loadTokenStore()[serverKey(serverUrl)] || null;
375
430
  }
@@ -515,7 +570,15 @@ function withStoreLock(file, fn) {
515
570
  export function saveStoredToken(serverUrl, record) {
516
571
  const file = tokenStorePath();
517
572
  return withStoreLock(file, () => {
518
- const store = loadTokenStore(); // re-read inside the lock (latest state)
573
+ const r = _readStore(file); // re-read inside the lock (latest state)
574
+ if (!r.ok) {
575
+ // The existing store is unreadable. Writing a fresh single-entry store
576
+ // here would silently destroy every other server's token — move the
577
+ // unreadable file aside first so it can be recovered, and surface it.
578
+ const archived = _archiveCorruptStore(file);
579
+ _warnCorruptStoreOnce(file, r.err, archived);
580
+ }
581
+ const store = r.store;
519
582
  store[serverKey(serverUrl)] = { server: serverKey(serverUrl), ...record };
520
583
  _writeStoreSecure(file, store);
521
584
  return store[serverKey(serverUrl)];
@@ -548,13 +611,28 @@ export function isTokenExpired(record, { skewMs = 60_000 } = {}) {
548
611
  /**
549
612
  * Return a valid bearer token for a server, refreshing if expired. Returns the
550
613
  * access_token string, or null if there's no stored token / refresh failed.
614
+ *
615
+ * `forceRefresh` bypasses the local-expiry check and exchanges the refresh
616
+ * token unconditionally. Use it when the server REJECTED a stored token that
617
+ * still looked unexpired locally (mid-session 401/403 from a rotated/revoked
618
+ * grant): the cached access_token is known-bad, so reusing it is pointless —
619
+ * we either mint a fresh one or return null to signal "re-login needed".
620
+ *
621
+ * @param {string} serverUrl
622
+ * @param {{ forceRefresh?: boolean }} [opts]
551
623
  */
552
- export async function ensureValidToken(serverUrl) {
624
+ export async function ensureValidToken(
625
+ serverUrl,
626
+ { forceRefresh = false } = {},
627
+ ) {
553
628
  const record = getStoredToken(serverUrl);
554
629
  if (!record) return null;
555
- if (!isTokenExpired(record)) return record.access_token;
630
+ if (!forceRefresh && !isTokenExpired(record)) return record.access_token;
556
631
  if (!record.refresh_token || !record.endpoints?.token_endpoint) {
557
- return record.access_token || null; // can't refresh use what we have
632
+ // Can't refresh. On a forced refresh the cached token is known-rejected, so
633
+ // hand back null (caller should stop retrying and prompt re-login); on the
634
+ // proactive path keep the existing behaviour of using what we have.
635
+ return forceRefresh ? null : record.access_token || null;
558
636
  }
559
637
  try {
560
638
  const tok = await refreshAccessToken(
@@ -564,7 +642,7 @@ export async function ensureValidToken(serverUrl) {
564
642
  const updated = saveStoredToken(serverUrl, { ...record, ...tok });
565
643
  return updated.access_token;
566
644
  } catch {
567
- return record.access_token || null;
645
+ return forceRefresh ? null : record.access_token || null;
568
646
  }
569
647
  }
570
648
 
@@ -108,6 +108,35 @@ export function buildTools({ root, readOnly = false, deps = _deps }) {
108
108
  },
109
109
  handler: ({ path: rel }) => {
110
110
  const abs = confine(root, rel, deps);
111
+ // Bounded read: pull at most MAX_READ_BYTES+1 bytes into memory. The
112
+ // old `readFileSync(abs)` loaded the WHOLE file before truncating, so
113
+ // MAX_READ_BYTES only capped the RESPONSE — a multi-GB file under the
114
+ // serve root would OOM the process. The +1 byte detects "longer than
115
+ // the cap" without reading the rest. Fall back to readFileSync only
116
+ // when the fd API isn't available on an injected fs.
117
+ if (typeof fs.openSync === "function") {
118
+ const fd = fs.openSync(abs, "r");
119
+ try {
120
+ const buf = Buffer.alloc(MAX_READ_BYTES + 1);
121
+ const n = fs.readSync(fd, buf, 0, MAX_READ_BYTES + 1, 0);
122
+ const truncated = n > MAX_READ_BYTES;
123
+ const text = buf
124
+ .slice(0, truncated ? MAX_READ_BYTES : n)
125
+ .toString("utf-8");
126
+ if (!truncated) return ok(text);
127
+ let total = null;
128
+ try {
129
+ total = fs.statSync(abs).size;
130
+ } catch {
131
+ /* size is best-effort for the message */
132
+ }
133
+ return ok(
134
+ `${text}\n… [truncated${total != null ? ` ${total} bytes` : ""}]`,
135
+ );
136
+ } finally {
137
+ fs.closeSync(fd);
138
+ }
139
+ }
111
140
  const buf = fs.readFileSync(abs);
112
141
  const truncated = buf.length > MAX_READ_BYTES;
113
142
  const text = (truncated ? buf.slice(0, MAX_READ_BYTES) : buf).toString(
@@ -82,6 +82,16 @@ export class Orchestrator extends EventEmitter {
82
82
  this.ciCommand = options.ciCommand || "npm test";
83
83
  this.verbose = options.verbose || false;
84
84
 
85
+ // Cap retained task history. Every addTask stores a record in _tasks and
86
+ // nothing ever removes it, so a long-running orchestrator (cron-watch /
87
+ // daemon) accumulates tasks without bound. Prune the oldest TERMINAL tasks
88
+ // beyond this cap (in-flight tasks are never dropped). Generous default;
89
+ // 0/invalid → 500.
90
+ this.maxTasks =
91
+ Number.isFinite(options.maxTasks) && options.maxTasks > 0
92
+ ? options.maxTasks
93
+ : 500;
94
+
85
95
  /** @type {Map<string, object>} taskId → task */
86
96
  this._tasks = new Map();
87
97
 
@@ -151,6 +161,7 @@ export class Orchestrator extends EventEmitter {
151
161
  };
152
162
 
153
163
  this._tasks.set(task.id, task);
164
+ this._pruneTasks();
154
165
  this.emit("task:added", task);
155
166
 
156
167
  if (this.notifier.isConfigured && task.notify) {
@@ -183,7 +194,7 @@ export class Orchestrator extends EventEmitter {
183
194
  return {
184
195
  tasks,
185
196
  pool: this._router.summary(),
186
- cliCommand: this.cliCommand,
197
+ ciCommand: this.ciCommand,
187
198
  cronActive: this._cronTimer !== null,
188
199
  };
189
200
  }
@@ -278,7 +289,7 @@ export class Orchestrator extends EventEmitter {
278
289
  async _dispatch(task) {
279
290
  this.emit("agents:dispatched", { task, count: task.subtasks.length });
280
291
  this._log(
281
- `Dispatching ${task.subtasks.length} subtask(s) to ${this.cliCommand} agents`,
292
+ `Dispatching ${task.subtasks.length} subtask(s) to ${this._router.summary().length} agent backend(s)`,
282
293
  );
283
294
 
284
295
  const results = await this._router.dispatch(task.subtasks, {
@@ -372,6 +383,11 @@ export class Orchestrator extends EventEmitter {
372
383
  cwd,
373
384
  encoding: "utf-8",
374
385
  timeout: 180_000,
386
+ // execSync defaults maxBuffer to 1 MB; a verbose-but-PASSING test run
387
+ // that prints more than that throws ENOBUFS, which the catch below
388
+ // would misreport as a CI failure (triggering needless retries +
389
+ // re-dispatch). Give CI output generous headroom.
390
+ maxBuffer: 64 * 1024 * 1024,
375
391
  stdio: "pipe",
376
392
  });
377
393
  return {
@@ -387,6 +403,26 @@ export class Orchestrator extends EventEmitter {
387
403
  }
388
404
  }
389
405
 
406
+ /**
407
+ * Bound retained task history to maxTasks. Evicts the oldest TERMINAL tasks
408
+ * first (Map preserves insertion order = FIFO); an in-flight task is never
409
+ * dropped, so a burst of concurrent runs can briefly exceed the cap rather
410
+ * than losing live work. The dropped records are completed/failed history
411
+ * the caller has already seen via task:complete / task:failed events.
412
+ */
413
+ _pruneTasks() {
414
+ if (this._tasks.size <= this.maxTasks) return;
415
+ for (const [id, t] of this._tasks) {
416
+ if (this._tasks.size <= this.maxTasks) break;
417
+ if (
418
+ t.status === TASK_STATUS.COMPLETED ||
419
+ t.status === TASK_STATUS.FAILED
420
+ ) {
421
+ this._tasks.delete(id);
422
+ }
423
+ }
424
+ }
425
+
390
426
  async _cronTick() {
391
427
  this.emit("cron:tick", { at: new Date().toISOString() });
392
428
  // Override this method or listen to the event to add input source polling
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import crypto from "crypto";
7
+ import { safeJsonParse } from "./safe-json.js";
7
8
 
8
9
  function generateId() {
9
10
  return crypto.randomUUID();
@@ -160,7 +161,9 @@ export function getRoles(db) {
160
161
  .all();
161
162
  return rows.map((r) => ({
162
163
  ...r,
163
- permissions: JSON.parse(r.permissions || "[]"),
164
+ // `|| "[]"` only guarded null — a corrupt permissions cell still threw out
165
+ // of the whole role list (and could break permission checks). Skip it.
166
+ permissions: safeJsonParse(r.permissions, []),
164
167
  isBuiltin: r.is_builtin === 1,
165
168
  }));
166
169
  }
@@ -553,7 +553,25 @@ export function retryStage(db, pipelineId, stageIndex) {
553
553
  const pipeline = _getPipelineRow(db, pipelineId);
554
554
  if (!pipeline) throw new Error(`Pipeline not found: ${pipelineId}`);
555
555
 
556
- const stage = _getStageRow(db, pipelineId, stageIndex);
556
+ // A retry may only re-open the CURRENT stage or an EARLIER one. A forward
557
+ // jump would move current_stage past the intervening stages — skipping their
558
+ // approval GATES (CODE_REVIEW / DEPLOY) — and let a caller (`cc pipeline retry
559
+ // --stage N`, user-supplied) reach a later stage without the gates that guard
560
+ // it. Going backward is safe: advancing from there re-runs through every gate
561
+ // again. Mirrors the state-machine rigor of completeStage / approveGate.
562
+ const idx = Number(stageIndex);
563
+ if (!Number.isInteger(idx) || idx < 0) {
564
+ throw new Error(`Invalid stage index: ${stageIndex}`);
565
+ }
566
+ if (idx > pipeline.current_stage) {
567
+ throw new Error(
568
+ `Cannot retry stage ${idx}: it is ahead of the current stage ` +
569
+ `(${pipeline.current_stage}) — retrying forward would skip the ` +
570
+ `intervening stages and their approval gates`,
571
+ );
572
+ }
573
+
574
+ const stage = _getStageRow(db, pipelineId, idx);
557
575
  if (!stage) throw new Error(`Stage ${stageIndex} not found`);
558
576
 
559
577
  const now = _now();
@@ -572,7 +590,7 @@ export function retryStage(db, pipelineId, stageIndex) {
572
590
  });
573
591
  _updatePipelineFields(db, pipelineId, {
574
592
  status: PIPELINE_STATUS.RUNNING,
575
- current_stage: stageIndex,
593
+ current_stage: idx,
576
594
  error_message: null,
577
595
  completed_at: null,
578
596
  });
@@ -33,6 +33,7 @@
33
33
  import fsDefault from "fs";
34
34
  import pathDefault from "path";
35
35
  import osDefault from "os";
36
+ import { credentialFileReason } from "./credential-guard.js";
36
37
 
37
38
  export const DEFAULT_MAX_FILE_BYTES = 48 * 1024; // per instruction/import file
38
39
  export const DEFAULT_MAX_TOTAL_BYTES = 192 * 1024; // whole block budget
@@ -321,6 +322,18 @@ export function loadProjectInstructions(opts = {}) {
321
322
  const resolved = path.resolve(baseDir, target);
322
323
  if (visited.has(resolved) || !isFile(fs, resolved)) continue; // silent:
323
324
  // non-files are decorative @tokens (npm scopes, emails), not imports.
325
+ // Refuse to import a credential / secret file. `cc agent` auto-loads
326
+ // project memory, so a cloned/untrusted repo's cc.md must NOT be able to
327
+ // pull `@~/.ssh/id_rsa` / `@../.env` into the LLM-bound system prompt
328
+ // (mirrors the read_file/run_shell credential guard).
329
+ const credReason = credentialFileReason(resolved);
330
+ if (credReason) {
331
+ visited.add(resolved); // don't re-warn on a second reference
332
+ warnings.push(
333
+ `refused @import of ${resolved} — ${credReason}; project memory must not pull secrets into the prompt`,
334
+ );
335
+ continue;
336
+ }
324
337
  visited.add(resolved);
325
338
  queue.push({ abs: resolved, scope: "import", depth: depth + 1 });
326
339
  }
@@ -25,6 +25,11 @@ import { findProjectRoot } from "./project-instructions.js";
25
25
  export const BANG_TIMEOUT_MS = 120_000;
26
26
  export const BANG_MAX_BUFFER = 10 * 1024 * 1024;
27
27
  export const BANG_OUTPUT_CAP = 30_000;
28
+ // A `#` note is a terse one-liner, but readline accepts a long pasted line and
29
+ // the note lands verbatim in cc.md — which is auto-loaded into EVERY future
30
+ // session's system prompt. Without a cap, an accidental fat paste silently
31
+ // bloats every later session's context. Truncate to a sane single-note size.
32
+ export const MEMO_NOTE_MAX = 4_000;
28
33
 
29
34
  export const _deps = {
30
35
  spawnSync: spawnSyncDefault,
@@ -138,7 +143,10 @@ export function appendMemoryNote(rawLine, opts = {}) {
138
143
  const fs = opts.deps?.fs || _deps.fs;
139
144
  const path = opts.deps?.path || _deps.path;
140
145
  const cwd = opts.cwd || process.cwd();
141
- const note = String(rawLine).replace(/^#/, "").trim();
146
+ let note = String(rawLine).replace(/^#/, "").trim();
147
+ if (note.length > MEMO_NOTE_MAX) {
148
+ note = `${note.slice(0, MEMO_NOTE_MAX)} …[truncated]`;
149
+ }
142
150
  const stamp = opts.date || new Date().toISOString().slice(0, 10);
143
151
 
144
152
  const root =
@@ -0,0 +1,137 @@
1
+ /**
2
+ * repl-denials — record + render the policy denials seen during an agent run,
3
+ * so the interactive REPL's `/permissions denials` can review what got blocked
4
+ * AND a headless run (`cc agent -p`) can print an end-of-run summary (Claude-
5
+ * Code 2.1.193 parity: "auto-mode denial reasons … /permissions recent
6
+ * denials"). The helpers are generic (not REPL-specific) and shared by both.
7
+ *
8
+ * A *denial* is a tool call the agent was NOT allowed to run — blocked by the
9
+ * shell policy, an ApprovalGate tier, a settings permission rule, or a hook. It
10
+ * is distinct from a tool that ran and *failed* (non-zero exit, missing file):
11
+ * those carry an `error` but none of the denial markers below, so they are not
12
+ * recorded here.
13
+ *
14
+ * Pure + dependency-free so it unit-tests without the REPL runtime.
15
+ */
16
+
17
+ /** Keep the most recent N denials per session (bounded ring buffer). */
18
+ export const MAX_RECENT_DENIALS = 20;
19
+
20
+ /** Error-message prefixes agent-core attaches to a blocked tool call. */
21
+ const DENIAL_PREFIX = /^\s*\[(Shell Policy|Permission|ApprovalGate|Hook)\]/;
22
+
23
+ /** Map a `[Prefix]` to a short `via` label when no structured field carries one. */
24
+ function viaFromPrefix(errStr) {
25
+ const m = DENIAL_PREFIX.exec(errStr);
26
+ if (!m) return null;
27
+ return (
28
+ {
29
+ "Shell Policy": "shell-policy",
30
+ Permission: "settings",
31
+ ApprovalGate: "gate",
32
+ Hook: "hook",
33
+ }[m[1]] || "policy"
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Classify a tool-result as a policy denial and extract a structured record,
39
+ * or return null when it is not a denial. Detection prefers the structured
40
+ * markers agent-core attaches (`approval.decision` / `policy.decision` /
41
+ * `shellCommandPolicy.allowed`) and falls back to the error-message prefix.
42
+ *
43
+ * @param {object} ev { tool, result, error, argsSummary }
44
+ * @returns {{tool:string,summary:string,reason:string,via:string,rule:(string|null)}|null}
45
+ */
46
+ export function classifyDenial(ev = {}) {
47
+ const { tool, result, error, argsSummary } = ev;
48
+ const errStr = String(error || (result && result.error) || "").trim();
49
+ const approval = result && result.approval;
50
+ const policy = result && result.policy;
51
+ const shellPol = result && result.shellCommandPolicy;
52
+ const denied =
53
+ (approval && approval.decision === "deny") ||
54
+ (policy && policy.decision === "deny") ||
55
+ (shellPol && shellPol.allowed === false) ||
56
+ DENIAL_PREFIX.test(errStr);
57
+ if (!denied) return null;
58
+ const via =
59
+ (approval && approval.via) ||
60
+ (policy && policy.via) ||
61
+ (shellPol ? "shell-policy" : viaFromPrefix(errStr)) ||
62
+ "policy";
63
+ const rule = (policy && policy.rule) || (shellPol && shellPol.ruleId) || null;
64
+ return {
65
+ tool: tool || "?",
66
+ summary: String(
67
+ argsSummary || (shellPol && shellPol.normalizedCommand) || "",
68
+ ).slice(0, 200),
69
+ reason: errStr || "(denied)",
70
+ via,
71
+ rule,
72
+ };
73
+ }
74
+
75
+ /** Two denials are "the same" attempt when tool + command + rule + via match. */
76
+ function sameDenial(a, b) {
77
+ return (
78
+ a &&
79
+ b &&
80
+ a.tool === b.tool &&
81
+ a.summary === b.summary &&
82
+ a.via === b.via &&
83
+ a.rule === b.rule
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Append a denial to a bounded most-recent-last ring buffer (mutates + returns
89
+ * it). Drops the oldest entries beyond `max`. No-op on a bad log/record.
90
+ *
91
+ * Consecutive identical denials are COALESCED: a model that hits a policy block
92
+ * usually retries the same command several times, which would otherwise flood
93
+ * the cap and evict other distinct denials. A repeat bumps the last entry's
94
+ * `count` and refreshes its `at` instead of appending.
95
+ */
96
+ export function recordDenial(log, record, max = MAX_RECENT_DENIALS) {
97
+ if (!Array.isArray(log) || !record) return log;
98
+ const last = log[log.length - 1];
99
+ if (sameDenial(last, record)) {
100
+ last.count = (last.count || 1) + 1;
101
+ if (typeof record.at === "number") last.at = record.at;
102
+ return log;
103
+ }
104
+ log.push(record);
105
+ while (log.length > max) log.shift();
106
+ return log;
107
+ }
108
+
109
+ /**
110
+ * Render the denial log most-recent-first for `/permissions denials`. Times are
111
+ * relative ("12s ago") when a record carries a numeric `at`; `now` is injectable
112
+ * for deterministic tests.
113
+ *
114
+ * @param {Array} log
115
+ * @param {{now?:number}} [opts]
116
+ * @returns {string}
117
+ */
118
+ export function formatDenials(log, opts = {}) {
119
+ const now = typeof opts.now === "number" ? opts.now : Date.now();
120
+ if (!Array.isArray(log) || log.length === 0) {
121
+ return " (no tool calls were denied this session)";
122
+ }
123
+ const lines = [` Recent denials (most recent first, ${log.length}):`];
124
+ for (let i = log.length - 1; i >= 0; i--) {
125
+ const d = log[i] || {};
126
+ const ago =
127
+ typeof d.at === "number"
128
+ ? ` · ${Math.max(0, Math.round((now - d.at) / 1000))}s ago`
129
+ : "";
130
+ const where = d.rule ? `${d.via}:${d.rule}` : d.via || "policy";
131
+ const what = d.summary ? `${d.tool} ${d.summary}` : d.tool || "?";
132
+ const times = d.count > 1 ? ` ×${d.count}` : "";
133
+ lines.push(` • ${what}${times} [${where}${ago}]`);
134
+ if (d.reason) lines.push(` ${d.reason}`);
135
+ }
136
+ return lines.join("\n");
137
+ }
@@ -31,7 +31,13 @@ export function isAuthError(err) {
31
31
  : null;
32
32
  if (status === 401 || status === 403) return true;
33
33
  const msg = String(err.message || err).toLowerCase();
34
- return /\b401\b|\b403\b|unauthorized|forbidden|authentication failed|api[\s_-]*key required|invalid api[\s_-]*key|incorrect api[\s_-]*key|expired/.test(
34
+ // `expired` must be AUTH-scoped (key/token/credential/secret), not bare:
35
+ // a TLS "certificate has expired" or "cache expired" is an infra error, and
36
+ // misclassifying it as auth would trigger an env-key VENDOR hijack on a
37
+ // keyless provider — masking the real problem (the same anti-hijack spirit
38
+ // as the rest of this module). A genuine 401/403 is still caught by status
39
+ // and the explicit auth phrases below.
40
+ return /\b401\b|\b403\b|unauthorized|forbidden|authentication failed|api[\s_-]*key required|invalid api[\s_-]*key|incorrect api[\s_-]*key|(?:api[\s_-]*key|token|credentials?|secret)[\s_-]*(?:has\s+|is\s+|was\s+)?expired|expired[\s_-]+(?:api[\s_-]*key|token|credentials?|secret)/.test(
35
41
  msg,
36
42
  );
37
43
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * safeJsonParse — JSON.parse that returns `fallback` instead of throwing on
3
+ * malformed (or null/empty) input.
4
+ *
5
+ * Use it for DB columns parsed inside a list `.map()` / loop: an unguarded
6
+ * `JSON.parse(row.col)` lets ONE corrupt cell throw out of the whole function,
7
+ * crashing the entire list load (the unguarded-JSON.parse-in-list-loop bug
8
+ * class — a single bad row should be skipped, not fail every other row).
9
+ *
10
+ * @param {unknown} text the raw string (or null/undefined)
11
+ * @param {*} [fallback=null] returned on null/empty/parse failure
12
+ * @returns {*}
13
+ */
14
+ export function safeJsonParse(text, fallback = null) {
15
+ if (text == null || text === "") return fallback;
16
+ try {
17
+ const v = JSON.parse(text);
18
+ return v == null ? fallback : v;
19
+ } catch {
20
+ return fallback;
21
+ }
22
+ }