chainlesschain 0.162.160 → 0.162.162

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 (199) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-CkRHjW8e.js → AIOps-ClI2ALgv.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BP3MeNi2.js → ActionButton-BDjRcJaK.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-CjbjQsOr.js → Analytics-DikSRsaT.js} +2 -2
  5. package/src/assets/web-panel/assets/{AppLayout-i0adH1M8.js → AppLayout-9mLhMFmz.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-CCCG2GOU.js → Artifacts-BSr88BrH.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-DrsgvwHC.js → Audit-CdvyimOF.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DkXNkbZM.js → BackgroundAgents-D3_hCedh.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DarTWCex.js → Backup-Cyvv9RHf.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-DaDzhcto.js → BaseInput-BZM14kt8.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-C4EzGw6V.js → Chat-DrQSokVz.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-lkavIWMS.js → ChatBubbleRenderer-HwJR-C6B.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-gddNgaQi.js → Checkbox-bFLYeWMV.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BKldFWf9.js → Codegen-7mN7xJXT.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-ChR-brDs.js → Col-DAB5ZWm7.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DlHtZX86.js → Community-B_1TymaK.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BAhfcBpC.js → Compact-Bq1uSWLJ.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-gIrKkm6m.js → Compliance-CyiE1jEe.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BYnzLBO3.js → Cowork-Cpk0D7vm.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-ByUDIljI.js → Cron-d_Lv3rdB.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DOdNbHFp.js → Crosschain-DMqhHjMR.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BJHaNWD2.js → DID-Br-oPuBw.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Drm_tClz.js → Dashboard-CfLW_RaP.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-CA1WLzGN.js → Dropdown-DZnTOExV.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-Di1SKjNO.js → EmailListRenderer-DHIUHDv1.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-44Ve7ZFe.js → FamilyGuardDashboard-Bw3t3myu.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CubPwv_N.js → Federation-D9cpQPAY.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-CsUFe5bU.js → FormItemContext-Jdczr9Mr.js} +1 -1
  29. package/src/assets/web-panel/assets/GenericCardRenderer-BT8ZMpC5.js +1 -0
  30. package/src/assets/web-panel/assets/{Git-D-Qx9dhO.js → Git-DjARip2b.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-_YMYaU4n.js → Governance-BgWbSRcj.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-J7Yzh-K_.js → Inference-CfouJciM.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-D37iI_Mo.js → KnowledgeGraph-50l7mrAr.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-oMTSYfGY.js → Logs-rq50H84B.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BqaPmC0W.js → MarkdownRenderer-DUPIEdQH.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-DKUqYDaF.js → Marketplace-BYgPDmHU.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BEv3Gx61.js → McpTools-5DVgcFdx.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-CtA8XD2y.js → Memory-B70LPk7f.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-ug7c1mMj.js → MobileBridge-De8ssA5Y.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-WJaMs4i2.js → Mtc-RJsOTsIA.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-DXnK_-ru.js → MtcAudit-Crh-6BEK.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-C4Ev7726.js → Multisig-wuoR5l3x.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-BMFb9_zk.js → NLProgramming-U2Mji4fZ.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Bl93nvBX.js → Notes-CdTZAyDH.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BN2W5cl6.js → NotificationSettings-C-FucWE7.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-Dxc9oN4o.js → OrderTableRenderer-DCYFTeaI.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-CAs48637.js → Organization-an3OF85L.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Bhq27Zoc.js → Overflow-C-1vmYFe.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-D61-2542.js → P2P-Cwh47xKg.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-C1j4iUxz.js → PdhVaultBrowser-Dm38GT0k.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-rJ2-fScM.js → Permissions-DS2Y8awD.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DFIlTok6.js → PersonalDataHub-BQNziA0I.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-BjIgEWoZ.js → Pipeline-DF85L1ue.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BQoH5wcX.js → Privacy-BuLornF-.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-CYR_-_Ub.js → ProjectInit-C3Y31IwX.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-nkatCq-9.js → ProjectSettings-BCPiI8aD.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-BZ6s4k6g.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-BnKM9fsF.js → Providers-D3Wp022X.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-Cfe82dbz.js → QrScannerModal-CUJfQkH4.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-r1RbPufV.js → QuickAsk-UAc0EO8l.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-CjCbM2WE.js → Recommend-BaxKupcs.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-BuMGPH6V.js → RemoteSession-C75NNnXZ.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-Bmj3NY1F.js → Reputation-BVXynZAk.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-BIsNcE_1.js → Row-C5MRZ0bi.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-BObmojD2.js → RssFeed-DAKkEYka.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-BRmbkQNS.js → Search-DvWv9YOA.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-_rF3DRpY.js → Security-B5peafW1.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-D0If9ZV4.js → Services-UQmvPEaG.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-o7zIksUg.js → Skeleton-C3bhWpuF.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-oUR7zi9u.js → Skills-DfkKf9_H.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-fqKyGi03.js → Sla-DqMSjlBM.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-B1wZ_gxx.js → SpeechSettings-C-WbxWUX.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-B3qJWYXq.js → SyncSettings-CYuHZ4N2.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-BH5_dUfr.js → Tasks-CyeAyB9d.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-CCIVpjRP.js → Templates-BTCeQnpq.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-2GZtTGnB.js → Tenant-BifQuA9Y.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-BlMkfs_u.js → Terminal-Bx_9Bocf.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-U6hi5OMc.js → TimelineRenderer-BX0RFCOm.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C-_BerGJ.js → Tokens-CGHrCmUS.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DaKzBgN3.js → Trigger-DlqaY4SO.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-CLvcsco3.js → Trust-DfOmj-ay.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-BFn_Y1zZ.js → UkeySign-B72jH5fE.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-B0V18dLT.js → VideoEditing-BT-63fqv.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-BbmP7Imu.js → Wallet-Dsul3sHr.js} +3 -3
  86. package/src/assets/web-panel/assets/{WebAuthn-Dewj_0kt.js → WebAuthn-Izkwwdgy.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-DOxz3Lwa.js → WorkflowEditor-D3FHSiH1.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-B1OIpZnh.js → chat-CmVz5z3U.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CSGNVdri.js → colors-CuLgyJBp.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-8S0vcg0u.js → compact-item-Ml1YkXpw.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-vZyc27TN.js → createContext-BvjJ48bs.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DWw3-UhA.js → hasIn-DVC0157C.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DLAvUtOH.js → index-4SBta3d7.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BZnauCqN.js → index-4wcgvPoF.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-t-q3rkVm.js → index-B0S8NzO6.js} +1 -1
  97. package/src/assets/web-panel/assets/index-B7a1TrU6.js +1 -0
  98. package/src/assets/web-panel/assets/{index-BwKrTVV0.js → index-BUt8kTIB.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BQssf0EQ.js → index-B_EvG_GW.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-xx5M5Rbj.js → index-BbPtzB8M.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DKwl1AkQ.js → index-BbepULSc.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-B4pP2ycr.js → index-BfuKP4qe.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D1i_8oop.js → index-BiC6dQZG.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dc40pX3F.js → index-BjHBpRYZ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DeCS12Mh.js → index-BpFJHiTv.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BjC-TrOb.js → index-BydjdZnl.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DcfLXAW7.js → index-BzG-x9p4.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CfEvacz7.js → index-Bzr0rB5T.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BoIPHP4R.js → index-C229ABfy.js} +1 -1
  110. package/src/assets/web-panel/assets/index-C2B1KRja.js +1 -0
  111. package/src/assets/web-panel/assets/{index-BdyokEau.js → index-CFYVQMvu.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CLPr2vC_.js → index-CXkJH-lK.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DtHWHJnj.js → index-C_H_S-DY.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Q6oyLnKC.js → index-C_XuRgGn.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BeBv896m.js → index-C_vRgTl9.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DLgkAhEE.js → index-Ck_52dz_.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-tr1x96W9.js → index-CmFNvNLZ.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CB2MOS_8.js → index-CudmwNBV.js} +3 -3
  119. package/src/assets/web-panel/assets/{index-CFQWZvak.js → index-Cw0Rjy87.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BuB5P0TR.js → index-D3lD5qap.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-D8qXVfoV.js → index-DC_OI9H2.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C6r27A-o.js → index-DDGaOWBV.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CbZqw7pg.js → index-DJyN28Et.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CJgKP2cm.js → index-DYXHFr4r.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CI3slBKI.js → index-Dt5KMjwJ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CwXBblm-.js → index-DuGszaWY.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BKEmQQvR.js → index-DukDVXCv.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dg0Izipr.js → index-KMqhcF7l.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BkWd6YL7.js → index-KjgKdZe-.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DPgY5GLy.js → index-QXzlT9KD.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BDHyovnK.js → index-vG4XSJ1d.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-dxocDVD_.js → index-wvb_FtaV.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-Di5YFId0.js → initDefaultProps-BDgd2YZ8.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BVSN0aeL.js → motion-ErtrXdYe.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-DHNiaP2l.js → move-_GPPNCgs.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-xJPf3gQD.js → mtc-parser-DRLL-8V2.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-C7qzUdkN.js → omit-p86sl023.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-pSpjg3Le.js → pickAttrs-BTu7cMLH.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-cs_jL_UC.js → placementArrow-BEKz6Isr.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-DkS_Zs4r.js → responsiveObserve-Ck42c9MP.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-B5O1Kpxo.js → slide-1CEvuPsB.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BX02GnZp.js → statusUtils-BDfdx1GJ.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-EmBYRftd.js → styleChecker-B4K46m5w.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-ZGzN1cU8.js → useFlexGapSupport-DoIVpJNq.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-CfP7M1m7.js → useFs-DdAWi-ln.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-CbIPHgNh.js → usePersonalDataHub-iUu6k8dx.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-7xh0If_O.js → vnode-DZz4R6Tw.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DmitvTR2.js → zoom-DXlf9sA3.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +15 -1
  151. package/src/commands/agent.js +7 -0
  152. package/src/commands/background-session.js +16 -2
  153. package/src/commands/review.js +6 -1
  154. package/src/data/changelog.json +12 -2
  155. package/src/gateways/ws/background-agent-protocol.js +88 -14
  156. package/src/lib/agent-authority.js +251 -0
  157. package/src/lib/agents.js +19 -0
  158. package/src/lib/background-agent-phase.js +100 -0
  159. package/src/lib/background-agent-supervisor.js +322 -15
  160. package/src/lib/backpressure-policy.js +105 -0
  161. package/src/lib/capability-negotiation.js +212 -0
  162. package/src/lib/credential-guard.js +11 -0
  163. package/src/lib/credential-proxy.js +182 -0
  164. package/src/lib/docs-drift.js +166 -0
  165. package/src/lib/doctor-checkup.js +76 -0
  166. package/src/lib/event-seq-replay.js +167 -0
  167. package/src/lib/goal-condition-engine.js +369 -0
  168. package/src/lib/headless-manifest.js +13 -0
  169. package/src/lib/hook-event-bus.cjs +0 -0
  170. package/src/lib/ide-context.js +53 -14
  171. package/src/lib/json-schema-output.js +30 -1
  172. package/src/lib/json-schema-validate.js +691 -0
  173. package/src/lib/lsp/__tests__/diagnostics-scheduler.test.js +93 -0
  174. package/src/lib/lsp/diagnostics-scheduler.js +127 -0
  175. package/src/lib/operation-fingerprint.js +95 -0
  176. package/src/lib/plugin-runtime/capabilities.js +371 -0
  177. package/src/lib/plugin-runtime/manifest.js +30 -0
  178. package/src/lib/project-instructions.js +98 -1
  179. package/src/lib/remote-approval-bridge.js +16 -0
  180. package/src/lib/remote-path-mapping.js +229 -0
  181. package/src/lib/review-pipeline.js +227 -0
  182. package/src/lib/runtime-checkup.js +318 -0
  183. package/src/lib/schedule-planner.js +136 -0
  184. package/src/lib/session-lifecycle.js +305 -0
  185. package/src/lib/settings-hook-events.cjs +39 -13
  186. package/src/lib/subagent-contract.js +383 -0
  187. package/src/lib/telemetry-ids.js +190 -0
  188. package/src/lib/turn-binding.js +397 -0
  189. package/src/repl/bg-dashboard.js +13 -12
  190. package/src/runtime/agent-core.js +24 -10
  191. package/src/runtime/headless-stream.js +195 -11
  192. package/src/runtime/system-prompt.js +3 -0
  193. package/src/workers/background-agent-worker.js +37 -2
  194. package/src/assets/web-panel/assets/GenericCardRenderer-D7xRjPjZ.js +0 -1
  195. package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +0 -1
  196. package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +0 -1
  197. package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +0 -1
  198. package/src/assets/web-panel/assets/index-DmVFfcNR.js +0 -1
  199. package/src/assets/web-panel/assets/index-x7YyhMUa.js +0 -1
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Event-stream gap-detection + replay primitives — the server→client (event)
3
+ * counterpart to the client→server RemoteCommandLedger (harness/
4
+ * remote-command-ledger.js). Both give a reconnecting peer an at-most-once,
5
+ * totally-ordered view over a lossy transport; the ledger orders *commands*,
6
+ * this orders *events*.
7
+ *
8
+ * A producer keeps a bounded buffer of recently emitted, seq-stamped frames
9
+ * (EventReplayBuffer); a reconnecting consumer reports its last-seen seq and
10
+ * gets the tail re-sent, or is told to full-resync when the missed range has
11
+ * already been evicted. A consumer detects holes with SeqGapTracker.
12
+ *
13
+ * Pure + deterministic: no I/O, no timers, no globals. The `seq` it stamps is
14
+ * the same additive protocol-v1 notion used on the stream-json surface
15
+ * (agent-sdk docs/PROTOCOL.md §1.2.1) — 1-based, monotonic, per producer
16
+ * instance; consumers MUST tolerate its absence and MUST NOT require gap-free
17
+ * numbering across a producer restart (a fresh instance rewinds to 1).
18
+ */
19
+
20
+ const DEFAULT_MAX_EVENTS = 512;
21
+ const DEFAULT_MAX_BYTES = 1 << 20; // 1 MiB — bound memory on a chatty session
22
+
23
+ /** Best-effort byte size of a frame (overridable for deterministic tests). */
24
+ function approxBytes(frame) {
25
+ try {
26
+ return Buffer.byteLength(JSON.stringify(frame));
27
+ } catch {
28
+ return 0; // circular / unserializable — don't let sizing throw
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Producer-side bounded replay buffer. `record(frame)` stamps the next `seq`
34
+ * onto a shallow copy, retains it (evicting the oldest beyond count/byte
35
+ * bounds), and returns the stamped frame ready to send. `replaySince(cursor)`
36
+ * returns the frames a consumer with last-seen seq `cursor` still needs.
37
+ */
38
+ export class EventReplayBuffer {
39
+ constructor({
40
+ maxEvents = DEFAULT_MAX_EVENTS,
41
+ maxBytes = DEFAULT_MAX_BYTES,
42
+ sizeOf,
43
+ } = {}) {
44
+ this._max = Math.max(1, Math.floor(maxEvents) || DEFAULT_MAX_EVENTS);
45
+ this._maxBytes = Math.max(0, Math.floor(maxBytes) || 0);
46
+ this._sizeOf = typeof sizeOf === "function" ? sizeOf : approxBytes;
47
+ this._buf = []; // [{ seq, frame, bytes }] oldest → newest
48
+ this._seq = 0;
49
+ this._bytes = 0;
50
+ }
51
+
52
+ /** Highest seq stamped so far (0 before anything is recorded). */
53
+ get latestSeq() {
54
+ return this._seq;
55
+ }
56
+
57
+ /** Oldest seq still retained (== latestSeq when the buffer is empty). */
58
+ get oldestSeq() {
59
+ return this._buf.length ? this._buf[0].seq : this._seq;
60
+ }
61
+
62
+ /** Number of frames currently retained. */
63
+ get size() {
64
+ return this._buf.length;
65
+ }
66
+
67
+ /**
68
+ * Stamp the next seq onto `frame` (shallow copy), retain it, evict overflow.
69
+ * @returns the stamped frame (`{ ...frame, seq }`) to hand straight to send.
70
+ */
71
+ record(frame) {
72
+ const seq = ++this._seq;
73
+ const stamped = { ...frame, seq };
74
+ const bytes = this._sizeOf(stamped) || 0;
75
+ this._buf.push({ seq, frame: stamped, bytes });
76
+ this._bytes += bytes;
77
+ this._evict();
78
+ return stamped;
79
+ }
80
+
81
+ _evict() {
82
+ // Always keep at least the newest frame even if it alone exceeds maxBytes.
83
+ while (
84
+ this._buf.length > this._max ||
85
+ (this._maxBytes > 0 &&
86
+ this._bytes > this._maxBytes &&
87
+ this._buf.length > 1)
88
+ ) {
89
+ const dropped = this._buf.shift();
90
+ this._bytes -= dropped.bytes;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Frames a consumer whose last-seen seq is `cursor` still needs.
96
+ * - `cursor >= latestSeq` → nothing missed → `{ frames: [], truncated: false }`.
97
+ * - `cursor` still within the retained window → the exact tail after it.
98
+ * - `cursor` older than what's retained → the gap was evicted →
99
+ * `truncated: true` (consumer MUST full-resync; the returned frames are
100
+ * only the retained suffix, not the whole hole).
101
+ * @param {number} cursor last seq the consumer acknowledged (default 0 = all)
102
+ */
103
+ replaySince(cursor = 0) {
104
+ const c = Number.isFinite(cursor) ? cursor : 0;
105
+ if (c >= this._seq) {
106
+ return { frames: [], truncated: false, from: c, to: this._seq };
107
+ }
108
+ // The lowest cursor we can honor without a hole is (oldest retained − 1).
109
+ const floor = this._buf.length ? this._buf[0].seq - 1 : this._seq;
110
+ const truncated = c < floor;
111
+ const frames = this._buf.filter((e) => e.seq > c).map((e) => e.frame);
112
+ return { frames, truncated, from: c, to: this._seq };
113
+ }
114
+ }
115
+
116
+ const OK = Object.freeze({ status: "ok" });
117
+
118
+ /**
119
+ * Consumer-side sequence tracker. Feed it each received frame's `seq`; it
120
+ * reports contiguity so the consumer can request a replay on a hole and drop
121
+ * duplicates a reconnect re-sent. One tracker per producer epoch — reset it
122
+ * when the transport hands you a fresh producer instance (e.g. a non-reattach
123
+ * bg-attach reply), since a fresh producer rewinds seq to 1.
124
+ */
125
+ export class SeqGapTracker {
126
+ constructor() {
127
+ this._last = 0;
128
+ this._started = false;
129
+ }
130
+
131
+ /** Last contiguous seq accepted (0 before the first frame). */
132
+ get lastSeq() {
133
+ return this._last;
134
+ }
135
+
136
+ /**
137
+ * @param {number} seq the received frame's seq (absent/NaN on older
138
+ * producers that don't stamp — treated as always-ok, never a gap).
139
+ * @returns {{status:"ok"}|{status:"duplicate"}|{status:"gap",from:number,to:number}}
140
+ * - `ok`: contiguous (or the first frame, or an unstamped frame).
141
+ * - `duplicate`: `seq <= lastSeq` — already seen; drop it.
142
+ * - `gap`: `seq > lastSeq + 1` — frames `(from, to)` were missed; ask the
143
+ * producer for `replaySince(from)`. The tracker adopts `to` as current.
144
+ */
145
+ observe(seq) {
146
+ if (!Number.isFinite(seq)) return OK; // unstamped producer — tolerate
147
+ if (!this._started) {
148
+ this._started = true;
149
+ this._last = seq;
150
+ return OK;
151
+ }
152
+ if (seq === this._last + 1) {
153
+ this._last = seq;
154
+ return OK;
155
+ }
156
+ if (seq <= this._last) return { status: "duplicate" };
157
+ const from = this._last;
158
+ this._last = seq; // adopt new position; caller replays the (from, to) hole
159
+ return { status: "gap", from, to: seq };
160
+ }
161
+
162
+ /** Forget all history (call when attaching to a fresh producer instance). */
163
+ reset() {
164
+ this._last = 0;
165
+ this._started = false;
166
+ }
167
+ }
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Session-level completion-condition engine (P1 §"会话级完成条件引擎").
3
+ *
4
+ * Distinct from `cc goal` (long-term, cross-session OKR progress) and from
5
+ * `cc loop` (a timer that re-runs ONE command): this drives the agent's OUTER
6
+ * turns. After each outer turn an independent evaluator judges a completion
7
+ * CONDITION; if unmet, the runner starts the next outer turn — until the
8
+ * condition is met (`completed`) or a budget is exhausted (`exhausted`).
9
+ *
10
+ * The engine is a pure reducer over usage + per-turn evaluations, so it is
11
+ * deterministically testable without a model or the real agent loop. It:
12
+ * - supports model-judged conditions AND deterministic checkers
13
+ * (exit-zero / file-exists / contains / regex),
14
+ * - enforces max_outer_turns and token / cost / time budgets,
15
+ * - emits the SDK event taxonomy (goal_started / goal_evaluated /
16
+ * goal_completed / goal_exhausted) with structured reason, evidence and
17
+ * turn/token/cost/time, and
18
+ * - snapshots/restores so a condition survives `--resume`.
19
+ *
20
+ * The clock is injected (`now`) and NEVER read implicitly, so snapshots replay
21
+ * identically and tests are deterministic.
22
+ */
23
+
24
+ /** SDK event taxonomy for the completion-condition loop. */
25
+ export const GOAL_EVENTS = Object.freeze({
26
+ STARTED: "goal_started",
27
+ EVALUATED: "goal_evaluated",
28
+ COMPLETED: "goal_completed",
29
+ EXHAUSTED: "goal_exhausted",
30
+ });
31
+
32
+ /** Terminal decisions the reducer can return alongside `continue`. */
33
+ export const GOAL_DECISION = Object.freeze({
34
+ CONTINUE: "continue",
35
+ COMPLETE: "complete",
36
+ EXHAUSTED: "exhausted",
37
+ });
38
+
39
+ /** Default budget — a bounded outer-turn count; other axes unbounded (null). */
40
+ export const DEFAULT_GOAL_BUDGET = Object.freeze({
41
+ maxOuterTurns: 10,
42
+ maxTokens: null,
43
+ maxCostUsd: null,
44
+ maxTimeMs: null,
45
+ });
46
+
47
+ // A hard ceiling so a misconfigured condition can never spin forever.
48
+ const OUTER_TURN_HARD_CAP = 100;
49
+
50
+ function positiveIntOrNull(value, cap) {
51
+ const n = Number(value);
52
+ if (!Number.isFinite(n) || n <= 0) return null;
53
+ const floored = Math.floor(n);
54
+ return cap ? Math.min(floored, cap) : floored;
55
+ }
56
+
57
+ function positiveNumOrNull(value) {
58
+ const n = Number(value);
59
+ return Number.isFinite(n) && n > 0 ? n : null;
60
+ }
61
+
62
+ /** Normalize a budget: sane defaults, positive values, hard outer-turn cap. */
63
+ export function normalizeGoalBudget(input = {}) {
64
+ const maxOuterTurns =
65
+ positiveIntOrNull(input.maxOuterTurns, OUTER_TURN_HARD_CAP) ??
66
+ DEFAULT_GOAL_BUDGET.maxOuterTurns;
67
+ return {
68
+ maxOuterTurns,
69
+ maxTokens: positiveIntOrNull(input.maxTokens),
70
+ maxCostUsd: positiveNumOrNull(input.maxCostUsd),
71
+ maxTimeMs: positiveIntOrNull(input.maxTimeMs),
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Parse a `--goal-condition` spec into a condition descriptor. A `kind:` prefix
77
+ * selects a deterministic checker; anything else (or an explicit `model:`) is
78
+ * model-judged natural language.
79
+ *
80
+ * exit-zero:npm test → run the command, met iff exit 0
81
+ * file-exists:dist/app.js → met iff the path exists
82
+ * contains:ALL GREEN → met iff the last output contains the text
83
+ * regex:^BUILD OK$ → met iff the last output matches
84
+ * model:the tests pass → a model judges the transcript
85
+ * the tests pass → (bare) model-judged
86
+ */
87
+ export function parseGoalCondition(spec) {
88
+ const raw = String(spec ?? "").trim();
89
+ if (!raw) throw new Error("goal condition is required");
90
+ const idx = raw.indexOf(":");
91
+ const prefix = idx === -1 ? "" : raw.slice(0, idx).trim().toLowerCase();
92
+ const rest = idx === -1 ? "" : raw.slice(idx + 1).trim();
93
+ switch (prefix) {
94
+ case "exit-zero":
95
+ case "exit0":
96
+ if (!rest) throw new Error("exit-zero condition needs a command");
97
+ return { kind: "exit-zero", command: rest, source: raw };
98
+ case "file-exists":
99
+ case "file":
100
+ if (!rest) throw new Error("file-exists condition needs a path");
101
+ return { kind: "file-exists", path: rest, source: raw };
102
+ case "contains":
103
+ if (!rest) throw new Error("contains condition needs text");
104
+ return { kind: "contains", text: rest, source: raw };
105
+ case "regex":
106
+ if (!rest) throw new Error("regex condition needs a pattern");
107
+ return { kind: "regex", pattern: rest, source: raw };
108
+ case "model":
109
+ if (!rest) throw new Error("model condition needs a description");
110
+ return { kind: "model", text: rest, source: raw };
111
+ default:
112
+ return { kind: "model", text: raw, source: raw };
113
+ }
114
+ }
115
+
116
+ /** True when a condition is a deterministic checker (not model-judged). */
117
+ export function isDeterministicCondition(condition) {
118
+ return Boolean(condition) && condition.kind !== "model";
119
+ }
120
+
121
+ /**
122
+ * Evaluate a deterministic condition against gathered evidence — PURE.
123
+ * `evidence` supplies `output` (last turn text), `exitCode`, and a
124
+ * `fileExists(path) => boolean` predicate. Returns `{met, reason, evidence}`.
125
+ */
126
+ export function checkDeterministicCondition(condition, evidence = {}) {
127
+ const output = String(evidence.output ?? "");
128
+ switch (condition?.kind) {
129
+ case "contains": {
130
+ const met = output.includes(condition.text);
131
+ return {
132
+ met,
133
+ reason: met
134
+ ? `output contains "${condition.text}"`
135
+ : `output does not contain "${condition.text}"`,
136
+ evidence: { kind: "contains", matched: met },
137
+ };
138
+ }
139
+ case "regex": {
140
+ let re;
141
+ try {
142
+ re = new RegExp(condition.pattern, condition.flags || "m");
143
+ } catch (err) {
144
+ return {
145
+ met: false,
146
+ reason: `invalid regex: ${err.message}`,
147
+ evidence: { kind: "regex", error: true },
148
+ };
149
+ }
150
+ const met = re.test(output);
151
+ return {
152
+ met,
153
+ reason: met
154
+ ? `output matches /${condition.pattern}/`
155
+ : `output does not match /${condition.pattern}/`,
156
+ evidence: { kind: "regex", matched: met },
157
+ };
158
+ }
159
+ case "exit-zero": {
160
+ const met = Number(evidence.exitCode) === 0;
161
+ return {
162
+ met,
163
+ reason: `\`${condition.command}\` exited ${evidence.exitCode}`,
164
+ evidence: { kind: "exit-zero", exitCode: evidence.exitCode ?? null },
165
+ };
166
+ }
167
+ case "file-exists": {
168
+ const fx = typeof evidence.fileExists === "function";
169
+ const met = fx ? evidence.fileExists(condition.path) === true : false;
170
+ return {
171
+ met,
172
+ reason: met
173
+ ? `${condition.path} exists`
174
+ : `${condition.path} does not exist`,
175
+ evidence: { kind: "file-exists", path: condition.path, met },
176
+ };
177
+ }
178
+ default:
179
+ return {
180
+ met: false,
181
+ reason: `not a deterministic condition (${condition?.kind})`,
182
+ evidence: { kind: condition?.kind ?? "unknown" },
183
+ };
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Gather evidence and run a deterministic check. Deps (`spawnSync`,
189
+ * `existsSync`) are injected for tests; `lastOutput` feeds contains/regex.
190
+ */
191
+ export function runDeterministicCheck(condition, deps = {}) {
192
+ const evidence = { output: deps.lastOutput ?? "" };
193
+ if (condition.kind === "exit-zero") {
194
+ const spawnSync = deps.spawnSync;
195
+ if (typeof spawnSync !== "function") {
196
+ return {
197
+ met: false,
198
+ reason: "no spawnSync available for exit-zero",
199
+ evidence: { kind: "exit-zero", error: true },
200
+ };
201
+ }
202
+ const res = spawnSync(condition.command, {
203
+ cwd: deps.cwd,
204
+ shell: true,
205
+ encoding: "utf8",
206
+ windowsHide: true,
207
+ });
208
+ evidence.exitCode = res?.status ?? (res?.error ? 1 : null);
209
+ } else if (condition.kind === "file-exists") {
210
+ const existsSync = deps.existsSync;
211
+ evidence.fileExists = (p) =>
212
+ typeof existsSync === "function" ? existsSync(p) === true : false;
213
+ }
214
+ return checkDeterministicCondition(condition, evidence);
215
+ }
216
+
217
+ function usageSnapshot(state, nowMs) {
218
+ return {
219
+ outerTurns: state.outerTurns,
220
+ tokens: state.tokens,
221
+ costUsd: state.costUsd,
222
+ elapsedMs: Math.max(0, nowMs - state.startedAtMs),
223
+ };
224
+ }
225
+
226
+ function whichLimitHit(state, budget, nowMs) {
227
+ if (budget.maxOuterTurns != null && state.outerTurns >= budget.maxOuterTurns)
228
+ return "max_outer_turns";
229
+ if (budget.maxTokens != null && state.tokens >= budget.maxTokens)
230
+ return "max_tokens";
231
+ if (budget.maxCostUsd != null && state.costUsd >= budget.maxCostUsd)
232
+ return "max_cost";
233
+ if (
234
+ budget.maxTimeMs != null &&
235
+ Math.max(0, nowMs - state.startedAtMs) >= budget.maxTimeMs
236
+ )
237
+ return "max_time";
238
+ return null;
239
+ }
240
+
241
+ /**
242
+ * The pure decision step. Given the accumulated `state`, this turn's
243
+ * `evaluation` ({met, reason, evidence}), the `budget`, and the current time,
244
+ * decide `complete | continue | exhausted` and produce events. Always emits a
245
+ * `goal_evaluated`; adds a terminal `goal_completed` / `goal_exhausted` when
246
+ * the loop stops. Does NOT mutate `state` — returns the next one.
247
+ */
248
+ export function evaluateGoalStep(state, evaluation, budget, opts = {}) {
249
+ const nowMs = Number(opts.now ?? state.startedAtMs);
250
+ const next = {
251
+ ...state,
252
+ outerTurns: state.outerTurns + 1,
253
+ };
254
+ const usage = usageSnapshot(next, nowMs);
255
+ const met = evaluation?.met === true;
256
+ const evaluated = {
257
+ type: GOAL_EVENTS.EVALUATED,
258
+ met,
259
+ reason: evaluation?.reason ?? null,
260
+ evidence: evaluation?.evidence ?? null,
261
+ ...usage,
262
+ };
263
+ if (met) {
264
+ return {
265
+ state: { ...next, done: true, outcome: "completed" },
266
+ decision: GOAL_DECISION.COMPLETE,
267
+ events: [
268
+ evaluated,
269
+ {
270
+ type: GOAL_EVENTS.COMPLETED,
271
+ reason: evaluation?.reason ?? "condition met",
272
+ evidence: evaluation?.evidence ?? null,
273
+ ...usage,
274
+ },
275
+ ],
276
+ };
277
+ }
278
+ const limit = whichLimitHit(next, budget, nowMs);
279
+ if (limit) {
280
+ return {
281
+ state: { ...next, done: true, outcome: "exhausted", limit },
282
+ decision: GOAL_DECISION.EXHAUSTED,
283
+ events: [
284
+ evaluated,
285
+ {
286
+ type: GOAL_EVENTS.EXHAUSTED,
287
+ limit,
288
+ reason: `budget exhausted (${limit})`,
289
+ ...usage,
290
+ },
291
+ ],
292
+ };
293
+ }
294
+ return { state: next, decision: GOAL_DECISION.CONTINUE, events: [evaluated] };
295
+ }
296
+
297
+ /**
298
+ * Stateful wrapper over the reducer for the runner. Inject `now` (a `() => ms`
299
+ * clock) so behavior is deterministic and snapshot-replayable.
300
+ */
301
+ export class GoalConditionEngine {
302
+ constructor({ condition, budget, now } = {}) {
303
+ this.condition =
304
+ typeof condition === "string" ? parseGoalCondition(condition) : condition;
305
+ this.budget = normalizeGoalBudget(budget);
306
+ this._now = typeof now === "function" ? now : () => 0;
307
+ this.state = {
308
+ outerTurns: 0,
309
+ tokens: 0,
310
+ costUsd: 0,
311
+ startedAtMs: this._now(),
312
+ done: false,
313
+ outcome: null,
314
+ };
315
+ }
316
+
317
+ /** Emit the opening event; call once before the first outer turn. */
318
+ start() {
319
+ return {
320
+ type: GOAL_EVENTS.STARTED,
321
+ condition: this.condition,
322
+ budget: this.budget,
323
+ startedAtMs: this.state.startedAtMs,
324
+ };
325
+ }
326
+
327
+ /** Accumulate a turn's usage before evaluating it. */
328
+ recordTurnUsage({ tokens = 0, costUsd = 0 } = {}) {
329
+ this.state = {
330
+ ...this.state,
331
+ tokens: this.state.tokens + (Number(tokens) || 0),
332
+ costUsd: this.state.costUsd + (Number(costUsd) || 0),
333
+ };
334
+ return this.state;
335
+ }
336
+
337
+ /** Evaluate this outer turn; returns `{decision, events}`. */
338
+ evaluate(evaluation) {
339
+ const result = evaluateGoalStep(this.state, evaluation, this.budget, {
340
+ now: this._now(),
341
+ });
342
+ this.state = result.state;
343
+ return { decision: result.decision, events: result.events };
344
+ }
345
+
346
+ get done() {
347
+ return this.state.done === true;
348
+ }
349
+
350
+ /** Serialize for `--resume`. startedAtMs is preserved so time keeps counting. */
351
+ snapshot() {
352
+ return {
353
+ condition: this.condition,
354
+ budget: this.budget,
355
+ state: { ...this.state },
356
+ };
357
+ }
358
+
359
+ /** Restore a prior run (same clock injection contract). */
360
+ static fromSnapshot(snap, { now } = {}) {
361
+ const engine = new GoalConditionEngine({
362
+ condition: snap.condition,
363
+ budget: snap.budget,
364
+ now,
365
+ });
366
+ engine.state = { ...snap.state };
367
+ return engine;
368
+ }
369
+ }
@@ -16,6 +16,7 @@
16
16
 
17
17
  import crypto from "node:crypto";
18
18
  import { createRequire } from "node:module";
19
+ import { PROTOCOL_MIN_VERSION } from "./capability-negotiation.js";
19
20
 
20
21
  export const STREAM_PROTOCOL_VERSION = 1;
21
22
 
@@ -137,6 +138,10 @@ export function buildAgentCapabilities() {
137
138
  name: "chainlesschain-cli",
138
139
  version,
139
140
  protocol_version: STREAM_PROTOCOL_VERSION,
141
+ // Oldest wire version this CLI can still negotiate down to (the N-1 in the
142
+ // capability handshake, agent-sdk docs/PROTOCOL.md §1.3). At v1 there is no
143
+ // older line shape, so min === current.
144
+ min_protocol_version: PROTOCOL_MIN_VERSION,
140
145
  agent_tools: contract.listCodingAgentToolNames(),
141
146
  permission_modes: [
142
147
  "default",
@@ -162,6 +167,14 @@ export function buildAgentCapabilities() {
162
167
  interactive_approvals: true,
163
168
  permission_prompt_tool: true,
164
169
  remote_control: true,
170
+ // Additive protocol-v1 stream fields (agent-sdk docs/PROTOCOL.md §1.2.1):
171
+ // every stream-json stdout line carries a monotonic `seq` and a
172
+ // run-scoped `trace_id`, and tool_use/tool_result lines carry a pairing
173
+ // `id` ("tu-<n>"). `trace_id` is caller-injectable (--trace-id /
174
+ // CC_TRACE_ID) for end-to-end IDE ↔ CLI correlation.
175
+ tool_use_id: true,
176
+ event_seq: true,
177
+ trace_id: true,
165
178
  mcp: {
166
179
  config_file: true,
167
180
  registry: true,
Binary file
@@ -22,6 +22,7 @@ import {
22
22
  filterIdeFileEntry,
23
23
  redactSecretsInText,
24
24
  } from "./ide-context-redaction.js";
25
+ import { normalizeIdePathForCli, detectWsl } from "./remote-path-mapping.js";
25
26
 
26
27
  /** Hard cap on the selected text we inline into the prompt. */
27
28
  const SELECTION_TEXT_CAP = 2000;
@@ -95,24 +96,55 @@ export function parseToolResultJson(result) {
95
96
  // through read_file), so credential files and secret-shaped text are filtered
96
97
  // HERE, at the collection boundary, before anything reaches the prompt.
97
98
  // CC_IDE_CONTEXT_REDACTION=0 disables (the filters below become pass-through).
99
+ //
100
+ // The same boundary also folds a remote file representation (a vscode-remote://
101
+ // URI, a \\wsl.localhost\… UNC, or a Windows drive path while cc runs in WSL)
102
+ // into the path THIS host can open (see [[remote-path-mapping.js]]) — otherwise
103
+ // the agent, reading the inlined path, would target a file cc can't see. The
104
+ // credential filter runs on the ORIGINAL value (basename is preserved by the
105
+ // map, so the verdict is unchanged). CC_IDE_PATH_MAP=0 disables it.
106
+
107
+ /** Env gate: CC_IDE_PATH_MAP=0|false|off disables remote path normalization. */
108
+ export function idePathMapEnabled(env = process.env) {
109
+ const v = String(env?.CC_IDE_PATH_MAP ?? "").toLowerCase();
110
+ return !(v === "0" || v === "false" || v === "off");
111
+ }
112
+
113
+ /** Fold one IDE-reported file value into a path cc can open on this host. */
114
+ function mapIdeFile(file, env) {
115
+ if (typeof file !== "string" || !idePathMapEnabled(env)) return file;
116
+ return normalizeIdePathForCli(file, {
117
+ platform: process.platform,
118
+ wsl: detectWsl(env || {}),
119
+ });
120
+ }
98
121
 
99
122
  /** Drop the selection when it lives in a credential file; scrub its text. */
100
123
  function sanitizeIdeSelection(sel, env) {
101
124
  if (!sel) return sel;
102
125
  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 };
126
+ let out = sel;
127
+ const mappedFile = mapIdeFile(sel.file, env);
128
+ if (mappedFile !== sel.file) out = { ...out, file: mappedFile };
129
+ if (typeof out.text === "string" && out.text.length > 0) {
130
+ const scrubbed = redactSecretsInText(out.text, { env });
131
+ if (scrubbed !== out.text) out = { ...out, text: scrubbed };
106
132
  }
107
- return sel;
133
+ return out;
108
134
  }
109
135
 
110
- /** Drop open-editor entries whose file is a credential file. */
136
+ /** Drop open-editor entries whose file is a credential file; map the rest. */
111
137
  function sanitizeIdeEditors(editors, env) {
112
138
  if (!Array.isArray(editors)) return editors;
113
- return editors.filter(
114
- (e) => !(e && e.file && filterIdeFileEntry(e.file, { env }) === null),
115
- );
139
+ return editors
140
+ .filter(
141
+ (e) => !(e && e.file && filterIdeFileEntry(e.file, { env }) === null),
142
+ )
143
+ .map((e) => {
144
+ if (!e || typeof e.file !== "string") return e;
145
+ const mapped = mapIdeFile(e.file, env);
146
+ return mapped === e.file ? e : { ...e, file: mapped };
147
+ });
116
148
  }
117
149
 
118
150
  /** Scrub secret-shaped text out of terminal commands and their output. */
@@ -131,18 +163,25 @@ function sanitizeIdeTerminals(terms, env) {
131
163
  });
132
164
  }
133
165
 
134
- /** Drop diagnostics for credential files; scrub their message text. */
166
+ /** Drop diagnostics for credential files; map their file + scrub message text. */
135
167
  function sanitizeIdeDiagnostics(diags, env) {
136
168
  if (!Array.isArray(diags)) return diags;
137
169
  return diags
138
170
  .filter(
139
171
  (d) => !(d && d.file && filterIdeFileEntry(d.file, { env }) === null),
140
172
  )
141
- .map((d) =>
142
- d && typeof d.message === "string"
143
- ? { ...d, message: redactSecretsInText(d.message, { env }) }
144
- : d,
145
- );
173
+ .map((d) => {
174
+ if (!d) return d;
175
+ let out = d;
176
+ if (typeof d.file === "string") {
177
+ const mapped = mapIdeFile(d.file, env);
178
+ if (mapped !== d.file) out = { ...out, file: mapped };
179
+ }
180
+ if (typeof out.message === "string") {
181
+ out = { ...out, message: redactSecretsInText(out.message, { env }) };
182
+ }
183
+ return out;
184
+ });
146
185
  }
147
186
 
148
187
  /** Resolve to the promise's value, or null after `ms` / on rejection. */