chainlesschain 0.162.158 → 0.162.160

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 (219) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-BwpTJTN_.js → AIOps-CkRHjW8e.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BtQOoo3l.js → ActionButton-BP3MeNi2.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Duf_rXZa.js → Analytics-CjbjQsOr.js} +2 -2
  6. package/src/assets/web-panel/assets/{AppLayout-XQK3wjvB.js → AppLayout-i0adH1M8.js} +5 -5
  7. package/src/assets/web-panel/assets/Artifacts-CCCG2GOU.js +1 -0
  8. package/src/assets/web-panel/assets/{Audit-DNjOGQLh.js → Audit-DrsgvwHC.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-FH9RAfB5.js → BackgroundAgents-DkXNkbZM.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-ZIbrR4-U.js → Backup-DarTWCex.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-DSt3NaND.js → BaseInput-DaDzhcto.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-CY-wvbjv.js → Chat-C4EzGw6V.js} +5 -5
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-lkavIWMS.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-8JVadE_B.js → Checkbox-gddNgaQi.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CN83nPYI.js → Codegen-BKldFWf9.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-xPSvqdN8.js → Col-ChR-brDs.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-D78cnIjG.js → Community-DlHtZX86.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-CSw6Csbp.js → Compact-BAhfcBpC.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-E_HhjcYR.js → Compliance-gIrKkm6m.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DybeQjmm.js → Cowork-BYnzLBO3.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DS0BJeLY.js → Cron-ByUDIljI.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-BOwVgkbs.js → Crosschain-DOdNbHFp.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-DRUr9YuX.js → DID-BJHaNWD2.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-Cht-yUKW.js → Dashboard-Drm_tClz.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BLr_EJ9X.js → Dropdown-CA1WLzGN.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-Drexuh5W.js → EmailListRenderer-Di1SKjNO.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D9Q2Kq63.js → FamilyGuardDashboard-44Ve7ZFe.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-C-Z1xI-T.js → Federation-CubPwv_N.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-DLHPMkQF.js → FormItemContext-CsUFe5bU.js} +1 -1
  30. package/src/assets/web-panel/assets/GenericCardRenderer-D7xRjPjZ.js +1 -0
  31. package/src/assets/web-panel/assets/{Git-syATcaBt.js → Git-D-Qx9dhO.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CYe_r8Zf.js → Governance-_YMYaU4n.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-GWxErB7k.js → Inference-J7Yzh-K_.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-qQvVCZoi.js → KnowledgeGraph-D37iI_Mo.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-BenAU2W8.js → Logs-oMTSYfGY.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-7agE3Or1.js → MarkdownRenderer-BqaPmC0W.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-D4Jn5fpD.js → Marketplace-DKUqYDaF.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DtAwoqiQ.js → McpTools-BEv3Gx61.js} +4 -4
  39. package/src/assets/web-panel/assets/{Memory-BI_O50I8.js → Memory-CtA8XD2y.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CQWE2lqx.js → MobileBridge-ug7c1mMj.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-Dnr16qsn.js → Mtc-WJaMs4i2.js} +2 -2
  43. package/src/assets/web-panel/assets/{MtcAudit-CQ5Xe4-a.js → MtcAudit-DXnK_-ru.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-Bjr8O-wy.js → Multisig-C4Ev7726.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-CEe-KirU.js → NLProgramming-BMFb9_zk.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-spKJIKF9.js → Notes-Bl93nvBX.js} +4 -4
  47. package/src/assets/web-panel/assets/{NotificationSettings-DRp83LFG.js → NotificationSettings-BN2W5cl6.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-h1A0XB2a.js → OrderTableRenderer-Dxc9oN4o.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BlgB0IYg.js → Organization-CAs48637.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-a_35HqZI.js → Overflow-Bhq27Zoc.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CSNHCNPC.js → P2P-D61-2542.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-C8M1JH-1.js → PdhVaultBrowser-C1j4iUxz.js} +5 -5
  53. package/src/assets/web-panel/assets/{Permissions-DmjJi9Ms.js → Permissions-rJ2-fScM.js} +3 -3
  54. package/src/assets/web-panel/assets/{PersonalDataHub-DfuwRyf4.js → PersonalDataHub-DFIlTok6.js} +2 -2
  55. package/src/assets/web-panel/assets/{Pipeline-B2-S4ekE.js → Pipeline-BjIgEWoZ.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-B1dOiBLC.js → Privacy-BQoH5wcX.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-_pNmc-DX.js → ProjectInit-CYR_-_Ub.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-DmwG2Ii1.js → ProjectSettings-nkatCq-9.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-C6HSjg1d.js → Providers-BnKM9fsF.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-CrDmS7l1.js → QrScannerModal-Cfe82dbz.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-CfKvWt-x.js → QuickAsk-r1RbPufV.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-DMa6Kuzv.js → Recommend-CjCbM2WE.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DrktUo5x.js → RemoteSession-BuMGPH6V.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-BTPQ1vjL.js → Reputation-Bmj3NY1F.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-B6Q7QkMG.js → Row-BIsNcE_1.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-B1IJhAmX.js → RssFeed-BObmojD2.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-xbea1kGD.js → Search-BRmbkQNS.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-7rs0cO3F.js → Security-_rF3DRpY.js} +2 -2
  70. package/src/assets/web-panel/assets/{Services-0fjixK0S.js → Services-D0If9ZV4.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-B_rVq9Cu.js → Skeleton-o7zIksUg.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-C5uM90b9.js → Skills-oUR7zi9u.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-U4pYCIZH.js → Sla-fqKyGi03.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-Cdz4yTWx.js → SpeechSettings-B1wZ_gxx.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-Me3aetHv.js → SyncSettings-B3qJWYXq.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-Cf6aNkL_.js → Tasks-BH5_dUfr.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-CYatk4tU.js → Templates-CCIVpjRP.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DIMKLw7o.js → Tenant-2GZtTGnB.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-C_cp-uSJ.js → Terminal-BlMkfs_u.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-cUPVNADp.js → TimelineRenderer-U6hi5OMc.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-CiaEIXVf.js → Tokens-C-_BerGJ.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DB1WU5lQ.js → Trigger-DaKzBgN3.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-Cfht9Ijy.js → Trust-CLvcsco3.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-K3P4Vt9S.js → UkeySign-BFn_Y1zZ.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-DWjhjXRh.js → VideoEditing-B0V18dLT.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-D9-Q1Kan.js → Wallet-BbmP7Imu.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-BDszy-oV.js → WebAuthn-Dewj_0kt.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-DIhij4rA.js → WorkflowEditor-DOxz3Lwa.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-D7VCiKbh.js → chat-B1OIpZnh.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BvOd9pVn.js → colors-CSGNVdri.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DoqXBiAO.js → compact-item-8S0vcg0u.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-CrKRzUtp.js → createContext-vZyc27TN.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-BWAev4WC.js → hasIn-DWw3-UhA.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DvDOpsJB.js → index-B4pP2ycr.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-oY45XrkG.js → index-BDHyovnK.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CmJ6xKBO.js → index-BKEmQQvR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BoYrS_L2.js → index-BQssf0EQ.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-ww1QtLnX.js → index-BZnauCqN.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BWWsaimP.js → index-BdyokEau.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CoD31Ndm.js → index-BeBv896m.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DlGu7As6.js → index-BjC-TrOb.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-zH-tuuGZ.js → index-BkWd6YL7.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CEXfKSiI.js → index-BoIPHP4R.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CEPHMSih.js → index-BuB5P0TR.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-PDxGjKRu.js → index-BwKrTVV0.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BkgnGX7v.js → index-C6r27A-o.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DV-Rfmfx.js → index-CB2MOS_8.js} +3 -3
  109. package/src/assets/web-panel/assets/{index-KR1-Em9C.js → index-CFQWZvak.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-8g5TJCZ3.js → index-CI3slBKI.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BCyP071m.js → index-CJgKP2cm.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CSb-tnIo.js → index-CLPr2vC_.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-1poMpMU0.js → index-CbZqw7pg.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BZGk8AbZ.js → index-CfEvacz7.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BXyl8O23.js → index-CwXBblm-.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BHOcVYrx.js → index-D1i_8oop.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bq_ctRpz.js → index-D8qXVfoV.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-gmxqu5Kk.js → index-DKwl1AkQ.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D23L1V76.js → index-DLAvUtOH.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CDGLdn12.js → index-DLgkAhEE.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-YGDy7nNq.js → index-DPgY5GLy.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Cx7M7PSF.js → index-Dc40pX3F.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-JzUmJMvn.js → index-DcfLXAW7.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-HwHdTusY.js → index-DeCS12Mh.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B30NvR21.js → index-Dg0Izipr.js} +1 -1
  126. package/src/assets/web-panel/assets/index-DmVFfcNR.js +1 -0
  127. package/src/assets/web-panel/assets/{index-B0vGEbJT.js → index-DtHWHJnj.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Bf9YlbDn.js → index-Q6oyLnKC.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Bj7EgmOp.js → index-dxocDVD_.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-ISvPZ05r.js → index-t-q3rkVm.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C5kRmj3P.js → index-tr1x96W9.js} +1 -1
  132. package/src/assets/web-panel/assets/index-x7YyhMUa.js +1 -0
  133. package/src/assets/web-panel/assets/{index-BN9m4Ux2.js → index-xx5M5Rbj.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-XW-OR3mL.js → initDefaultProps-Di5YFId0.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-CeuEHh20.js → motion-BVSN0aeL.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-Dva79Keg.js → move-DHNiaP2l.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-C0B6bD2j.js → mtc-parser-xJPf3gQD.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-AkZvO-a2.js → omit-C7qzUdkN.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-B4r55qGc.js → pickAttrs-pSpjg3Le.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BLH-z7YE.js → placementArrow-cs_jL_UC.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-ksEGZ2Wf.js → responsiveObserve-DkS_Zs4r.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-Cth4OD1Z.js → slide-B5O1Kpxo.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-9S83ezxw.js → statusUtils-BX02GnZp.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-7yQmQGZ4.js → styleChecker-EmBYRftd.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DpGnMmPc.js → useFlexGapSupport-ZGzN1cU8.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-C4zDZIXH.js → useFs-CfP7M1m7.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-CoLICdUZ.js → usePersonalDataHub-CbIPHgNh.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CKyH-35X.js → vnode-7xh0If_O.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-DRDGit17.js → zoom-DmitvTR2.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-manifest.json +17 -3
  152. package/src/commands/agent.js +72 -6
  153. package/src/commands/background-session.js +173 -3
  154. package/src/commands/browse.js +86 -0
  155. package/src/commands/cloud.js +232 -0
  156. package/src/commands/doctor.js +61 -6
  157. package/src/commands/mcp.js +107 -0
  158. package/src/commands/plugin.js +63 -1
  159. package/src/commands/routine.js +367 -0
  160. package/src/commands/session.js +396 -0
  161. package/src/data/changelog.json +12 -2
  162. package/src/harness/jsonl-session-store.js +152 -3
  163. package/src/harness/mcp-client.js +50 -0
  164. package/src/harness/session-index.js +295 -0
  165. package/src/harness/session-mirror.js +142 -0
  166. package/src/harness/transcript-integrity.js +182 -0
  167. package/src/index.js +8 -0
  168. package/src/lib/agent-sandbox.js +68 -0
  169. package/src/lib/agents.js +12 -4
  170. package/src/lib/api-key-helper.js +62 -0
  171. package/src/lib/background-agent-supervisor.js +158 -2
  172. package/src/lib/channels/channel-manager.js +126 -0
  173. package/src/lib/channels/telegram-channel.js +112 -0
  174. package/src/lib/channels/webhook-channel.js +140 -0
  175. package/src/lib/chrome-connector.js +386 -0
  176. package/src/lib/cloud/bundle.js +110 -0
  177. package/src/lib/cloud/cloud-client.js +152 -0
  178. package/src/lib/computer-use/authorization.js +173 -0
  179. package/src/lib/computer-use/computer-use-server.js +276 -0
  180. package/src/lib/computer-use/control-backend.js +150 -0
  181. package/src/lib/dependency-install-policy.js +130 -0
  182. package/src/lib/doctor-checkup.js +652 -0
  183. package/src/lib/exit-codes.cjs +77 -0
  184. package/src/lib/headless-manifest.js +175 -0
  185. package/src/lib/hook-runner.cjs +70 -4
  186. package/src/lib/ide-context-redaction.js +130 -0
  187. package/src/lib/ide-context.js +89 -13
  188. package/src/lib/jsonl-session-store.js +6 -0
  189. package/src/lib/llm-config-defaults.js +24 -8
  190. package/src/lib/lsp/lsp-server-registry.js +48 -21
  191. package/src/lib/plugin-runtime/governance.js +174 -0
  192. package/src/lib/pr-link-ledger.js +221 -0
  193. package/src/lib/project-mcp-trust.js +87 -0
  194. package/src/lib/routine-store.js +352 -0
  195. package/src/lib/sensitive-file-guard.js +51 -0
  196. package/src/lib/session-store-guard.js +40 -0
  197. package/src/lib/session-usage.js +318 -2
  198. package/src/lib/sub-agent-context.js +30 -3
  199. package/src/repl/agent-repl.js +361 -3
  200. package/src/repl/bg-dashboard.js +355 -0
  201. package/src/repl/fast-mode.js +103 -0
  202. package/src/repl/fullscreen-tui.js +197 -0
  203. package/src/repl/slash-command-registry.js +11 -3
  204. package/src/repl/voice-dictation.js +132 -0
  205. package/src/runtime/agent-core.js +344 -7
  206. package/src/runtime/coding-agent-contract-shared.cjs +107 -2
  207. package/src/runtime/coding-agent-policy.cjs +17 -0
  208. package/src/runtime/headless-runner.js +137 -14
  209. package/src/runtime/headless-stream.js +43 -9
  210. package/src/runtime/mcp-config.js +32 -1
  211. package/src/runtime/policies/agent-policy.js +3 -0
  212. package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +0 -1
  213. package/src/assets/web-panel/assets/ChatBubbleRenderer-BAV9V-t-.js +0 -1
  214. package/src/assets/web-panel/assets/GenericCardRenderer-DxxEdn2r.js +0 -1
  215. package/src/assets/web-panel/assets/MobileProjects-CVtTqgtg.js +0 -1
  216. package/src/assets/web-panel/assets/Projects-COATmHxF.js +0 -1
  217. package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +0 -1
  218. package/src/assets/web-panel/assets/index-B1_pRTtB.js +0 -1
  219. package/src/assets/web-panel/assets/index-TMM9FuW-.js +0 -1
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Headless exit-code taxonomy (gap-analysis 2026-07-11, P0 "确定性 Headless"):
3
+ * scripted callers can tell apart WHY a run ended without parsing stderr.
4
+ *
5
+ * Backward compatibility: every failure is still non-zero, 0 stays success,
6
+ * 2 stays "settings hook blocked the prompt" (pre-existing), 130/143 stay the
7
+ * signal-interrupt codes. The new codes carve budget/turn exhaustion and
8
+ * config errors out of the generic 1.
9
+ */
10
+
11
+ const HEADLESS_EXIT_CODES = Object.freeze({
12
+ OK: 0,
13
+ /** generic failure (tool/loop/unclassified error) */
14
+ ERROR: 1,
15
+ /** a settings hook blocked the prompt (pre-existing semantic) */
16
+ HOOK_BLOCKED: 2,
17
+ /** the turn budget (--max-turns / loop iteration cap) ran out first */
18
+ MAX_TURNS: 3,
19
+ /** the --max-budget-usd cost cap stopped the run */
20
+ MAX_BUDGET: 4,
21
+ /** the LLM call itself failed (provider/model/network error) */
22
+ MODEL_ERROR: 5,
23
+ /** bad run configuration (--mcp-config / --permission-prompt-tool / settings) */
24
+ CONFIG_ERROR: 6,
25
+ /** user interrupt (SIGINT) — 128+2, shell convention */
26
+ INTERRUPT_SIGINT: 130,
27
+ /** termination (SIGTERM) — 128+15, shell convention */
28
+ INTERRUPT_SIGTERM: 143,
29
+ });
30
+
31
+ /**
32
+ * Best-effort classifier for errors thrown out of the agent loop: provider /
33
+ * transport failures → MODEL_ERROR, everything else → generic ERROR. Checks
34
+ * structural markers (HTTP status, network error codes) before falling back —
35
+ * never message-text guessing beyond the explicit LLM markers.
36
+ */
37
+ function classifyLoopError(err) {
38
+ if (!err) return HEADLESS_EXIT_CODES.ERROR;
39
+ const status = err.status ?? err.statusCode ?? err.response?.status;
40
+ if (Number.isFinite(Number(status))) return HEADLESS_EXIT_CODES.MODEL_ERROR;
41
+ const code = err.code || err.cause?.code;
42
+ if (
43
+ typeof code === "string" &&
44
+ /^(ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|UND_ERR)/.test(
45
+ code,
46
+ )
47
+ ) {
48
+ return HEADLESS_EXIT_CODES.MODEL_ERROR;
49
+ }
50
+ if (typeof err.name === "string" && /LLM|Provider|Api/i.test(err.name)) {
51
+ return HEADLESS_EXIT_CODES.MODEL_ERROR;
52
+ }
53
+ return HEADLESS_EXIT_CODES.ERROR;
54
+ }
55
+
56
+ /**
57
+ * Map a finished run's endReason (+ error flag) to an exit code.
58
+ * @param {string} endReason "complete" | "max_turns" | "budget-exhausted" |
59
+ * "cost-budget-exhausted" | "no-response" | ...
60
+ * @param {boolean} isError
61
+ */
62
+ function exitCodeForEndReason(endReason, isError) {
63
+ if (!isError) return HEADLESS_EXIT_CODES.OK;
64
+ if (endReason === "max_turns" || endReason === "budget-exhausted") {
65
+ return HEADLESS_EXIT_CODES.MAX_TURNS;
66
+ }
67
+ if (endReason === "cost-budget-exhausted") {
68
+ return HEADLESS_EXIT_CODES.MAX_BUDGET;
69
+ }
70
+ return HEADLESS_EXIT_CODES.ERROR;
71
+ }
72
+
73
+ module.exports = {
74
+ HEADLESS_EXIT_CODES,
75
+ classifyLoopError,
76
+ exitCodeForEndReason,
77
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Deterministic-headless manifest helpers (gap-analysis 2026-07-11,
3
+ * docs/CLAUDE_CODE_CLI_GAP_ANALYSIS.md P0 "确定性 Headless").
4
+ *
5
+ * The stream-json init event carries a protocol version, whether this run
6
+ * persists its session, which ambient customization sources are live, and
7
+ * short digests of the resolved permission policy + tool surface — so a CI
8
+ * caller can assert "same run shape" across invocations without diffing the
9
+ * whole event stream.
10
+ *
11
+ * STREAM_PROTOCOL_VERSION mirrors packages/agent-sdk/src/protocol.ts
12
+ * PROTOCOL_VERSION — a BREAKING stream-json change must bump both in the same
13
+ * commit (see agent-sdk docs/PROTOCOL.md). Additive optional fields stay on
14
+ * the current version.
15
+ */
16
+
17
+ import crypto from "node:crypto";
18
+ import { createRequire } from "node:module";
19
+
20
+ export const STREAM_PROTOCOL_VERSION = 1;
21
+
22
+ const _require = createRequire(import.meta.url);
23
+
24
+ function shortHash(value) {
25
+ return crypto
26
+ .createHash("sha256")
27
+ .update(JSON.stringify(value))
28
+ .digest("hex")
29
+ .slice(0, 16);
30
+ }
31
+
32
+ const sortedList = (v) =>
33
+ [...new Set((Array.isArray(v) ? v : []).filter(Boolean))].sort();
34
+
35
+ // null = "no explicit list" (the default full tool set) — kept distinct from
36
+ // an explicit empty list so the digest can tell the two apart.
37
+ const sortedListOrNull = (v) => (Array.isArray(v) ? sortedList(v) : null);
38
+
39
+ /**
40
+ * Digest of the resolved permission policy: permission mode + allow/deny tool
41
+ * lists + settings permission rules. Stable across key order / duplicates so
42
+ * two runs with the same effective policy produce the same digest.
43
+ *
44
+ * @param {object} p
45
+ * @param {string} [p.permissionMode]
46
+ * @param {string[]} [p.allowedTools]
47
+ * @param {string[]} [p.disallowedTools]
48
+ * @param {{allow?:string[],ask?:string[],deny?:string[]}|null} [p.permissionRules]
49
+ * @returns {string} 16-hex-char sha256 prefix
50
+ */
51
+ export function computePolicyDigest({
52
+ permissionMode,
53
+ allowedTools,
54
+ disallowedTools,
55
+ permissionRules,
56
+ } = {}) {
57
+ const rules = permissionRules
58
+ ? {
59
+ allow: sortedList(permissionRules.allow),
60
+ ask: sortedList(permissionRules.ask),
61
+ deny: sortedList(permissionRules.deny),
62
+ }
63
+ : null;
64
+ return shortHash({
65
+ v: 1,
66
+ permissionMode: permissionMode || "default",
67
+ allowedTools: sortedListOrNull(allowedTools),
68
+ disallowedTools: sortedList(disallowedTools),
69
+ rules,
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Digest of the tool surface exposed to the model (name set, order-independent).
75
+ * @param {string[]} toolNames
76
+ * @returns {string} 16-hex-char sha256 prefix
77
+ */
78
+ export function computeToolsHash(toolNames) {
79
+ return shortHash({ v: 1, tools: sortedListOrNull(toolNames) });
80
+ }
81
+
82
+ /**
83
+ * Which ambient customization sources are LIVE for this run. Reflects the
84
+ * kill-switch env vars (--safe-mode / --bare flip these) plus what the runner
85
+ * actually wired. Under `--bare` this collapses to at most
86
+ * ["settings-permissions"] (+ explicit MCP), which is the reproducibility
87
+ * signal CI wants.
88
+ *
89
+ * @param {object} p
90
+ * @param {object|null} [p.permissionRules] resolved settings permission rules
91
+ * @param {object|null} [p.settingsHooks] resolved settings hooks
92
+ * @param {boolean} [p.mcp] MCP surface enabled/connected
93
+ * @param {string[]} [p.enabledToolNames]
94
+ * @param {object} [p.env=process.env]
95
+ * @returns {string[]}
96
+ */
97
+ export function buildLoadedSources({
98
+ permissionRules = null,
99
+ settingsHooks = null,
100
+ mcp = false,
101
+ enabledToolNames = null,
102
+ env = process.env,
103
+ } = {}) {
104
+ const on = (k) => env[k] !== "0";
105
+ // null tool list = "default full set" → run_skill is available.
106
+ const skillToolAvailable =
107
+ !Array.isArray(enabledToolNames) || enabledToolNames.includes("run_skill");
108
+ const sources = [];
109
+ if (permissionRules) sources.push("settings-permissions");
110
+ if (settingsHooks) sources.push("settings-hooks");
111
+ if (on("CC_PROJECT_MEMORY")) sources.push("project-memory");
112
+ if (on("CC_MEMORY_INJECT")) sources.push("memory-recall");
113
+ if (on("CC_SKILLS") && skillToolAvailable) {
114
+ sources.push("skills");
115
+ }
116
+ if (on("CC_PLUGINS")) sources.push("plugins");
117
+ if (mcp) sources.push("mcp");
118
+ return sources;
119
+ }
120
+
121
+ /**
122
+ * Machine-readable capability manifest for `cc agent --capabilities`:
123
+ * everything a programmatic caller needs to know up front to drive this CLI
124
+ * (protocol version, tool names, permission modes, IO formats, exit codes,
125
+ * feature flags) without parsing --help text.
126
+ */
127
+ export function buildAgentCapabilities() {
128
+ let version = null;
129
+ try {
130
+ version = _require("../../package.json").version || null;
131
+ } catch {
132
+ /* manifest stays usable without a version */
133
+ }
134
+ const contract = _require("../runtime/coding-agent-contract-shared.cjs");
135
+ const { HEADLESS_EXIT_CODES } = _require("./exit-codes.cjs");
136
+ return {
137
+ name: "chainlesschain-cli",
138
+ version,
139
+ protocol_version: STREAM_PROTOCOL_VERSION,
140
+ agent_tools: contract.listCodingAgentToolNames(),
141
+ permission_modes: [
142
+ "default",
143
+ "manual",
144
+ "auto",
145
+ "dontAsk",
146
+ "plan",
147
+ "acceptEdits",
148
+ "bypassPermissions",
149
+ ],
150
+ output_formats: ["text", "json", "stream-json"],
151
+ input_formats: ["text", "stream-json"],
152
+ exit_codes: HEADLESS_EXIT_CODES,
153
+ features: {
154
+ bare: true,
155
+ safe_mode: true,
156
+ ephemeral: true,
157
+ background: true,
158
+ worktree: true,
159
+ checkpoint: true,
160
+ json_schema_output: true,
161
+ partial_messages: true,
162
+ interactive_approvals: true,
163
+ permission_prompt_tool: true,
164
+ remote_control: true,
165
+ mcp: {
166
+ config_file: true,
167
+ registry: true,
168
+ tool_search: true,
169
+ oauth: true,
170
+ managed_allowlist: true,
171
+ },
172
+ sandbox_engines: ["docker", "bubblewrap"],
173
+ },
174
+ };
175
+ }
@@ -117,6 +117,69 @@ function tryParseDecision(stdout) {
117
117
  * nonBlockingError?:boolean, error?:string }}
118
118
  */
119
119
  function runCommandHook(command, input = {}, opts = {}) {
120
+ if (!command) {
121
+ return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode: 0 };
122
+ }
123
+ // Circuit breaker (gap 2026-07-11 P2 "Hooks 硬化"): N consecutive
124
+ // non-blocking failures (spawn error / timeout / non-zero non-2 exit) of
125
+ // the SAME command trip an open state — further runs are skipped for a
126
+ // cooldown, then one half-open trial runs; success (or a real block/ask
127
+ // decision) closes it. CC_HOOK_BREAKER_THRESHOLD=0 disables.
128
+ const breaker = hookBreakerConfig();
129
+ if (breaker.threshold > 0) {
130
+ const st = _breaker.get(command);
131
+ if (st && st.fails >= breaker.threshold) {
132
+ const now = Date.now();
133
+ if (now - st.openedAt < breaker.cooldownMs) {
134
+ return {
135
+ decision: HOOK_DECISIONS.CONTINUE,
136
+ reason: `hook circuit breaker open (${st.fails} consecutive failures) — skipped for cooldown`,
137
+ exitCode: null,
138
+ skipped: true,
139
+ breakerOpen: true,
140
+ };
141
+ }
142
+ // half-open: let this one trial through; the outcome below decides.
143
+ }
144
+ }
145
+ const result = _runCommandHookInner(command, input, opts);
146
+ if (breaker.threshold > 0) {
147
+ if (result.nonBlockingError) {
148
+ const st = _breaker.get(command) || { fails: 0, openedAt: 0 };
149
+ st.fails += 1;
150
+ if (st.fails >= breaker.threshold) st.openedAt = Date.now();
151
+ _breaker.set(command, st);
152
+ } else {
153
+ _breaker.delete(command); // success / block / ask closes the breaker
154
+ }
155
+ }
156
+ return result;
157
+ }
158
+
159
+ // Payload schema version (gap 2026-07-11 P2): every hook's stdin JSON carries
160
+ // schema_version so hook scripts can branch on future payload changes.
161
+ // Additive-only changes keep the version; a breaking reshape must bump it.
162
+ const HOOK_PAYLOAD_SCHEMA_VERSION = 1;
163
+
164
+ // command → { fails, openedAt } (per process; hooks are per-run anyway)
165
+ const _breaker = new Map();
166
+
167
+ function hookBreakerConfig(env = process.env) {
168
+ const threshold = Number(env.CC_HOOK_BREAKER_THRESHOLD);
169
+ const cooldown = Number(env.CC_HOOK_BREAKER_COOLDOWN_MS);
170
+ return {
171
+ threshold:
172
+ Number.isFinite(threshold) && threshold >= 0 ? Math.floor(threshold) : 3,
173
+ cooldownMs: Number.isFinite(cooldown) && cooldown > 0 ? cooldown : 60000,
174
+ };
175
+ }
176
+
177
+ /** Test seam: clear breaker state between cases. */
178
+ function _resetHookBreaker() {
179
+ _breaker.clear();
180
+ }
181
+
182
+ function _runCommandHookInner(command, input = {}, opts = {}) {
120
183
  const { cwd, event } = opts;
121
184
  // spawnSync is SYNCHRONOUS — it blocks the whole agent until the hook returns
122
185
  // or its timeout fires. Node treats a 0/undefined timeout as UNLIMITED, so a
@@ -128,15 +191,15 @@ function runCommandHook(command, input = {}, opts = {}) {
128
191
  Number.isFinite(_rawTimeout) && _rawTimeout > 0
129
192
  ? Math.min(_rawTimeout, 600000)
130
193
  : 60000;
131
- if (!command) {
132
- return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode: 0 };
133
- }
134
194
  const shellKind = normalizeShellKind(opts.shell);
135
195
  const usePowershell = shellKind === "powershell" || shellKind === "pwsh";
136
196
  let res;
137
197
  try {
138
198
  const common = {
139
- input: JSON.stringify(input),
199
+ input: JSON.stringify({
200
+ ...input,
201
+ schema_version: input.schema_version ?? HOOK_PAYLOAD_SCHEMA_VERSION,
202
+ }),
140
203
  cwd: cwd || process.cwd(),
141
204
  encoding: "utf-8",
142
205
  timeout,
@@ -258,5 +321,8 @@ module.exports = {
258
321
  runHooks,
259
322
  tryParseDecision,
260
323
  HOOK_DECISIONS,
324
+ HOOK_PAYLOAD_SCHEMA_VERSION,
325
+ hookBreakerConfig,
326
+ _resetHookBreaker,
261
327
  _deps,
262
328
  };
@@ -0,0 +1,130 @@
1
+ /**
2
+ * IDE-context redaction — the implicit-context counterpart of the credential
3
+ * READ guard ([[credential-guard.js]]).
4
+ *
5
+ * The IDE bridge shares editor state (selection text, open-editor tabs,
6
+ * terminal output, diagnostics) with every prompt WITHOUT any tool-permission
7
+ * gate: nothing was "read" through read_file, so denyRead rules and the
8
+ * credential guard never fire. An open `.env` tab or an `export API_KEY=…`
9
+ * still visible in the integrated terminal would ride straight into model
10
+ * context. This module closes that side door:
11
+ *
12
+ * 1. `filterIdeFileEntry` — file-path entries (open editors, selection file,
13
+ * diagnostics files) that name a credential file are dropped, reusing the
14
+ * exact same `credentialFileReason` patterns the read guard enforces. The
15
+ * non-resolved variant is deliberate: these paths come from the IDE, may
16
+ * not exist locally, and a pure string check never touches the fs.
17
+ * 2. `redactSecretsInText` — free text (selection, terminal output,
18
+ * diagnostic messages) is scrubbed with a conservative set of secret
19
+ * shapes: AWS access-key ids, Bearer tokens, PEM private-key blocks,
20
+ * well-known token prefixes, and `SECRET_NAME = value` assignments (only
21
+ * the VALUE is swallowed — key names and ordinary code survive).
22
+ *
23
+ * Precision over recall, same posture as the guards: a redactor that mangles
24
+ * everyday code teaches users to turn it off. `CC_IDE_CONTEXT_REDACTION=0`
25
+ * disables (default ON).
26
+ */
27
+
28
+ import {
29
+ credentialFileReason,
30
+ _internals as credentialGuardInternals,
31
+ } from "./credential-guard.js";
32
+
33
+ const REDACTED = "[REDACTED]";
34
+ const REDACTED_FILE = "[redacted credential file]";
35
+
36
+ // Boundary-delimited secret words (KEY / TOKEN / SECRET / PASSWORD / …) —
37
+ // shared with the credential guard so both surfaces agree on what "looks
38
+ // secret".
39
+ const SECRET_VAR_RE = credentialGuardInternals.SECRET_VAR_RE;
40
+
41
+ // AWS access-key ids are globally unique, fixed-shape strings.
42
+ const AWS_ACCESS_KEY_RE = /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g;
43
+
44
+ // `Bearer <token>` in headers / curl lines / pasted requests.
45
+ const BEARER_RE = /\b(Bearer)\s+[A-Za-z0-9\-._~+/=]{8,}/g;
46
+
47
+ // PEM private-key blocks — the bytes ARE the secret. A block whose END marker
48
+ // was truncated off still redacts to the end of the text (fail toward safety).
49
+ const PEM_PRIVATE_KEY_RE =
50
+ /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----|$)/g;
51
+
52
+ // High-precision vendor token prefixes (OpenAI/Anthropic sk-, GitHub PATs,
53
+ // GitLab PATs, Slack xox*) — unambiguous even outside an assignment.
54
+ const TOKEN_PREFIX_RE =
55
+ /\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{16,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g;
56
+
57
+ // `API_KEY = "value"` / `token: value` / JSON `"secret": "value"` — the key
58
+ // must contain a boundary-delimited secret word, the value must be a bare
59
+ // token-like run (≥8 chars, no spaces). The trailing lookahead rejects values
60
+ // that continue into a call — `token = getToken()` is code, not a secret.
61
+ // Only the value is replaced; the key and separator are kept so the agent
62
+ // still sees WHAT was configured, just not the secret itself.
63
+ const ASSIGNMENT_RE =
64
+ /\b([A-Za-z_][A-Za-z0-9_-]*)(["']?\s*[=:]\s*)(["']?)([A-Za-z0-9+/=_.\-]{8,})\3(?![A-Za-z0-9+/=_.\-(])/g;
65
+
66
+ /**
67
+ * Env kill-switch: CC_IDE_CONTEXT_REDACTION=0|false|no|off disables all IDE
68
+ * context redaction. Default ON — only an explicit opt-out turns it off.
69
+ */
70
+ export function ideRedactionEnabled(env = process.env) {
71
+ const raw = env && env.CC_IDE_CONTEXT_REDACTION;
72
+ return !(raw != null && /^(0|false|no|off)$/i.test(String(raw).trim()));
73
+ }
74
+
75
+ /**
76
+ * Gate one IDE-supplied file-path entry (open-editor tab, selection file,
77
+ * diagnostic file). Returns the path unchanged when it is fine to share, or
78
+ * null when it names a credential/secret file and the whole entry must be
79
+ * dropped (or replaced with the `REDACTED_FILE_PLACEHOLDER`).
80
+ *
81
+ * Pure string check by design (non-resolved `credentialFileReason`): IDE paths
82
+ * may be remote/virtual, and a context filter must never do fs I/O. `cwd` is
83
+ * accepted for signature parity with the resolved guards but unused here.
84
+ *
85
+ * @param {string} filePath
86
+ * @param {object} [opts] { cwd?, env? }
87
+ * @returns {string|null}
88
+ */
89
+ export function filterIdeFileEntry(filePath, opts = {}) {
90
+ const p = String(filePath || "");
91
+ if (!p) return filePath ?? null;
92
+ if (!ideRedactionEnabled(opts.env || process.env)) return filePath;
93
+ return credentialFileReason(p) ? null : filePath;
94
+ }
95
+
96
+ /**
97
+ * Scrub secret-shaped substrings out of IDE-supplied free text (selection
98
+ * text, terminal output, diagnostic messages). Non-strings pass through
99
+ * untouched; with the kill-switch off the text is returned as-is.
100
+ *
101
+ * @param {string} text
102
+ * @param {object} [opts] { env? }
103
+ * @returns {string}
104
+ */
105
+ export function redactSecretsInText(text, opts = {}) {
106
+ if (typeof text !== "string" || text.length === 0) return text;
107
+ if (!ideRedactionEnabled(opts.env || process.env)) return text;
108
+ let out = text;
109
+ out = out.replace(PEM_PRIVATE_KEY_RE, REDACTED);
110
+ out = out.replace(BEARER_RE, (_m, word) => `${word} ${REDACTED}`);
111
+ out = out.replace(AWS_ACCESS_KEY_RE, REDACTED);
112
+ out = out.replace(TOKEN_PREFIX_RE, REDACTED);
113
+ out = out.replace(ASSIGNMENT_RE, (m, key, sep, quote, _value) => {
114
+ // Hyphens count as segment boundaries too (api-key ≙ api_key).
115
+ if (!SECRET_VAR_RE.test(key.replace(/-/g, "_"))) return m;
116
+ return `${key}${sep}${quote}${REDACTED}${quote}`;
117
+ });
118
+ return out;
119
+ }
120
+
121
+ export const REDACTED_MARKER = REDACTED;
122
+ export const REDACTED_FILE_PLACEHOLDER = REDACTED_FILE;
123
+
124
+ export const _internals = {
125
+ AWS_ACCESS_KEY_RE,
126
+ BEARER_RE,
127
+ PEM_PRIVATE_KEY_RE,
128
+ TOKEN_PREFIX_RE,
129
+ ASSIGNMENT_RE,
130
+ };
@@ -18,6 +18,10 @@
18
18
  */
19
19
 
20
20
  import { simpleDiff } from "./note-versioning.js";
21
+ import {
22
+ filterIdeFileEntry,
23
+ redactSecretsInText,
24
+ } from "./ide-context-redaction.js";
21
25
 
22
26
  /** Hard cap on the selected text we inline into the prompt. */
23
27
  const SELECTION_TEXT_CAP = 2000;
@@ -86,6 +90,61 @@ export function parseToolResultJson(result) {
86
90
  }
87
91
  }
88
92
 
93
+ // ─── Redaction seams (see [[ide-context-redaction.js]]) ─────────────────────
94
+ // IDE context bypasses the tool-permission layer entirely (nothing goes
95
+ // through read_file), so credential files and secret-shaped text are filtered
96
+ // HERE, at the collection boundary, before anything reaches the prompt.
97
+ // CC_IDE_CONTEXT_REDACTION=0 disables (the filters below become pass-through).
98
+
99
+ /** Drop the selection when it lives in a credential file; scrub its text. */
100
+ function sanitizeIdeSelection(sel, env) {
101
+ if (!sel) return sel;
102
+ if (sel.file && filterIdeFileEntry(sel.file, { env }) === null) return null;
103
+ if (typeof sel.text === "string" && sel.text.length > 0) {
104
+ const scrubbed = redactSecretsInText(sel.text, { env });
105
+ if (scrubbed !== sel.text) return { ...sel, text: scrubbed };
106
+ }
107
+ return sel;
108
+ }
109
+
110
+ /** Drop open-editor entries whose file is a credential file. */
111
+ function sanitizeIdeEditors(editors, env) {
112
+ if (!Array.isArray(editors)) return editors;
113
+ return editors.filter(
114
+ (e) => !(e && e.file && filterIdeFileEntry(e.file, { env }) === null),
115
+ );
116
+ }
117
+
118
+ /** Scrub secret-shaped text out of terminal commands and their output. */
119
+ function sanitizeIdeTerminals(terms, env) {
120
+ if (!Array.isArray(terms)) return terms;
121
+ return terms.map((t) => {
122
+ if (!t || typeof t !== "object") return t;
123
+ const out = { ...t };
124
+ if (typeof out.command === "string") {
125
+ out.command = redactSecretsInText(out.command, { env });
126
+ }
127
+ if (typeof out.output === "string") {
128
+ out.output = redactSecretsInText(out.output, { env });
129
+ }
130
+ return out;
131
+ });
132
+ }
133
+
134
+ /** Drop diagnostics for credential files; scrub their message text. */
135
+ function sanitizeIdeDiagnostics(diags, env) {
136
+ if (!Array.isArray(diags)) return diags;
137
+ return diags
138
+ .filter(
139
+ (d) => !(d && d.file && filterIdeFileEntry(d.file, { env }) === null),
140
+ )
141
+ .map((d) =>
142
+ d && typeof d.message === "string"
143
+ ? { ...d, message: redactSecretsInText(d.message, { env }) }
144
+ : d,
145
+ );
146
+ }
147
+
89
148
  /** Resolve to the promise's value, or null after `ms` / on rejection. */
90
149
  function withTimeout(promise, ms) {
91
150
  return new Promise((resolve) => {
@@ -137,18 +196,22 @@ export async function collectIdeContext(mcp, opts = {}) {
137
196
  ? call("mcp__ide__getTerminalOutput", { limit: TERMINAL_LIMIT })
138
197
  : Promise.resolve(null),
139
198
  ]);
140
- const openEditors = Array.isArray(editors?.editors) ? editors.editors : null;
141
- const terminals = Array.isArray(terminalData?.terminals)
199
+ const env = opts.env || process.env;
200
+ const sel = sanitizeIdeSelection(selection || null, env);
201
+ let openEditors = Array.isArray(editors?.editors) ? editors.editors : null;
202
+ if (openEditors) openEditors = sanitizeIdeEditors(openEditors, env);
203
+ let terminals = Array.isArray(terminalData?.terminals)
142
204
  ? terminalData.terminals
143
205
  : null;
206
+ if (terminals) terminals = sanitizeIdeTerminals(terminals, env);
144
207
  if (
145
- !selection &&
208
+ !sel &&
146
209
  !(openEditors && openEditors.length > 0) &&
147
210
  !(terminals && terminals.length > 0)
148
211
  ) {
149
212
  return null;
150
213
  }
151
- return { selection: selection || null, openEditors, terminals };
214
+ return { selection: sel, openEditors, terminals };
152
215
  }
153
216
 
154
217
  /**
@@ -304,8 +367,11 @@ export async function collectIdeDiagnostics(mcp, filePath, opts = {}) {
304
367
  );
305
368
  const all = Array.isArray(data?.diagnostics) ? data.diagnostics : null;
306
369
  if (!all) return null;
307
- const relevant = all.filter(
308
- (d) => d && DIAG_SEVERITIES.has(String(d.severity).toLowerCase()),
370
+ const relevant = sanitizeIdeDiagnostics(
371
+ all.filter(
372
+ (d) => d && DIAG_SEVERITIES.has(String(d.severity).toLowerCase()),
373
+ ),
374
+ env,
309
375
  );
310
376
  return relevant.length > 0 ? relevant : null;
311
377
  }
@@ -653,6 +719,7 @@ export async function expandIdeMentions(text, mcp, opts = {}) {
653
719
  const mentions = findIdeMentions(src);
654
720
  if (mentions.length === 0) return out;
655
721
  const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
722
+ const env = opts.env || process.env;
656
723
  const blocks = [];
657
724
  for (const kind of mentions) {
658
725
  try {
@@ -663,11 +730,9 @@ export async function expandIdeMentions(text, mcp, opts = {}) {
663
730
  );
664
731
  continue;
665
732
  }
666
- const sel = await callIdeToolJson(
667
- mcp,
668
- "mcp__ide__getSelection",
669
- {},
670
- timeoutMs,
733
+ const sel = sanitizeIdeSelection(
734
+ await callIdeToolJson(mcp, "mcp__ide__getSelection", {}, timeoutMs),
735
+ env,
671
736
  );
672
737
  const b = formatSelectionMention(sel);
673
738
  if (b) {
@@ -689,7 +754,14 @@ export async function expandIdeMentions(text, mcp, opts = {}) {
689
754
  {},
690
755
  timeoutMs,
691
756
  );
692
- const b = formatDiagnosticsMention(data);
757
+ const safe =
758
+ data && Array.isArray(data.diagnostics)
759
+ ? {
760
+ ...data,
761
+ diagnostics: sanitizeIdeDiagnostics(data.diagnostics, env),
762
+ }
763
+ : data;
764
+ const b = formatDiagnosticsMention(safe);
693
765
  if (b) {
694
766
  blocks.push(b);
695
767
  out.expanded.push("diagnostics");
@@ -709,7 +781,11 @@ export async function expandIdeMentions(text, mcp, opts = {}) {
709
781
  { limit: TERMINAL_MENTION_LIMIT },
710
782
  timeoutMs,
711
783
  );
712
- const b = formatTerminalMention(data);
784
+ const safe =
785
+ data && Array.isArray(data.terminals)
786
+ ? { ...data, terminals: sanitizeIdeTerminals(data.terminals, env) }
787
+ : data;
788
+ const b = formatTerminalMention(safe);
713
789
  if (b) {
714
790
  blocks.push(b);
715
791
  out.expanded.push("terminal");
@@ -15,11 +15,14 @@ export {
15
15
  appendUserMessage,
16
16
  appendAssistantMessage,
17
17
  appendToolCall,
18
+ appendToolCallCompact,
18
19
  appendToolResult,
19
20
  appendCompactEvent,
20
21
  readEvents,
21
22
  rebuildMessages,
22
23
  listJsonlSessions,
24
+ renameSession,
25
+ pruneJsonlSessions,
23
26
  forkSession,
24
27
  sessionExists,
25
28
  getLastSessionId,
@@ -33,4 +36,7 @@ export {
33
36
  isUnsafeSessionId,
34
37
  appendTokenUsage,
35
38
  toIsoSafe,
39
+ verifySession,
40
+ verifyAllSessions,
41
+ TRANSCRIPT_CHAIN_STATUS,
36
42
  } from "../harness/jsonl-session-store.js";