chainlesschain 0.162.162 → 0.162.164
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.
- package/package.json +3 -1
- package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BqRkaHGb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-DWO7OZLC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-C5oaVezM.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-CsUESDoZ.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-Dragqu5k.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-CQsaHctY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-DdG1jNSM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-DN6sjK4z.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-Cxm_ZTCl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-DVXGmA56.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-HwJR-C6B.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DEc4vD_A.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-CKiTcv36.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-Br9UUNN0.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-NeUH_70T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-BqR-z2Df.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DcWB_SgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BfhgcudF.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-LaU29LgD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-B2ZB9uwW.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BuHl38A2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-m627yqvZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-C7bxF8F5.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DPLPQzLp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-Bvkzg88z.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-BTsiA7h_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-DS_j6r25.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CKRG3Rsl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-DW8INoNQ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-wqi0uxuR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-CMlTuPLT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-CSx4m5JJ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-vnQCUy-_.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-BV2NA742.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-CzK_FPB1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-Cb7G9oL3.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-qJxlUyzr.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-DqaBJ7Te.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-CwhTJkRi.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-YDs4Fal4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-CEmQm9n8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-zby_P_LA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-DsmDNFNT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-DuzQ6me5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-DZbeN613.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CoNSVAUS.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-OYJEAaMC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-CWIapV--.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-CX8xMxZn.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-BLCN8YyY.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-BV_sxp75.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-BTnRrHRp.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-D1KZg018.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-COm5cuNg.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-0saWEkYI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-Dr7iKLS2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-C1vpPBcr.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-CxlWrtNj.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-BT2V8dPN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-bm9V3c5k.js} +4 -4
- package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CocH1yyX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DHItlMdd.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-C59sLc4w.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Crj4BMc8.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-DX4KFceY.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-BD_iwQLr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-BmCTzYsH.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-BGUaxZYl.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-BS0tJx2O.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal--tHSAsld.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D9-MFxS4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-CiCB485f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-SmCFuW_l.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-DkK3Rqe0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-CDM4LGdB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-IDB7DJXt.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-Bikj5cmt.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-C9abWlAw.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-DrbRKVky.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-CccCSDQf.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-CXObXLhb.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-B5kfYAxC.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-DmiPBfip.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-Q6lYteAc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C229ABfy.js → index-25Wlqps4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-33U29UNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-4oXBDczR.js} +1 -1
- package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-BGYXvMc5.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-BJCCa0s_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-BJJ5DhKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-BLOzViJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-BRrbUMoG.js} +1 -1
- package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BUFpT17Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BkRDiqGc.js} +1 -1
- package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-BsfvW6my.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-By-kfPED.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-BzFTcQsc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-C2FXdboq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-C5XOlTHN.js} +1 -1
- package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-CLhBDS2w.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-CMq7UeoT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-CVMTWNKl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-Caghi-yL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CdB6FuSH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-CoK8-kWQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-CsO3GUAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-CwZBa20Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-D-kASucw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbepULSc.js → index-D8eBw7vW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D9wgifk4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-DAYCTc07.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-DF6Q8l2h.js} +1 -1
- package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
- package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-DgTLApS9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-DgxVJ8pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-DlJvIqoR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-Dw4eHfsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-Rh-ytwow.js} +3 -3
- package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-StAEt2pq.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-bzfw951k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-fSUMbMvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-tFHy5Wol.js} +1 -1
- package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-BNq7X72N.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-DOf9viTc.js} +1 -1
- package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CRHTcW4x.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-Cyoozgm2.js} +1 -1
- package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-6-TjFNru.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-DlUmO1zi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-CFOL8mIR.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BtaFK5zd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BIbc2bur.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-BXX9d6-w.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-BGv10Bpn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-FY4IL4jn.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-BM3OHxAT.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-D1fbFiHB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +320 -32
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/doctor.js +85 -1
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +456 -1
- package/src/commands/review.js +363 -2
- package/src/commands/session.js +191 -0
- package/src/commands/team.js +19 -1
- package/src/data/changelog.json +10 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +55 -2
- package/src/gateways/ws/ws-agent-handler.js +50 -0
- package/src/harness/jsonl-session-store.js +60 -0
- package/src/harness/worktree-isolator.js +140 -5
- package/src/lib/agent-schedule-store.js +318 -10
- package/src/lib/agent-team/team-worktree.js +37 -2
- package/src/lib/agent-tool-admission.js +125 -0
- package/src/lib/app-preview.js +360 -0
- package/src/lib/async-hook-supervisor.cjs +54 -0
- package/src/lib/capability-manifest.js +319 -0
- package/src/lib/capability-negotiation.js +23 -12
- package/src/lib/channels/channel-manager.js +54 -2
- package/src/lib/cli-context-engineering.js +9 -2
- package/src/lib/context-breakdown.js +116 -0
- package/src/lib/diagnostic-bundle.js +230 -0
- package/src/lib/doctor-checkup.js +275 -4
- package/src/lib/execution-location.js +286 -0
- package/src/lib/headless-manifest.js +13 -40
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/hook-stats-store.cjs +197 -0
- package/src/lib/idempotency.js +109 -0
- package/src/lib/interaction-adapter.js +34 -4
- package/src/lib/json-schema-output.js +46 -12
- package/src/lib/json-schema-validate.js +143 -3
- package/src/lib/jsonl-session-store.js +1 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
- package/src/lib/lsp/lsp-manager.js +56 -0
- package/src/lib/monitor-event.js +290 -0
- package/src/lib/operation-fingerprint.js +217 -0
- package/src/lib/plugin-runtime/bin.js +37 -0
- package/src/lib/plugin-runtime/capabilities.js +19 -0
- package/src/lib/plugin-runtime/capability-consent.js +203 -0
- package/src/lib/plugin-runtime/hooks.js +37 -0
- package/src/lib/plugin-runtime/lsp.js +38 -0
- package/src/lib/plugin-runtime/mcp.js +40 -0
- package/src/lib/plugin-runtime/monitors.js +37 -0
- package/src/lib/plugin-runtime/plugin-options.js +151 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/pr-automation-policy.js +224 -0
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/remote-approval-bridge.js +66 -13
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +153 -0
- package/src/lib/review-comment-anchor.js +241 -0
- package/src/lib/review-pipeline.js +46 -7
- package/src/lib/runtime-checkup.js +221 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/secret-scan.js +409 -0
- package/src/lib/session-branch.js +0 -0
- package/src/lib/settings-hook-events.cjs +13 -0
- package/src/lib/settings-loader.cjs +42 -0
- package/src/lib/side-effect-ledger-store.js +88 -0
- package/src/lib/side-effect-ledger.js +401 -0
- package/src/lib/skill-loader.js +32 -0
- package/src/lib/skill-path-scope.js +63 -0
- package/src/lib/sub-agent-context.js +110 -9
- package/src/lib/sub-agent-registry.js +19 -0
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/telemetry/span-recorder.js +17 -4
- package/src/lib/terminal-context.js +195 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/lib/unattended-action-policy.js +182 -0
- package/src/lib/worktree-cleanup-safety.js +138 -0
- package/src/lib/worktree-isolator.js +8 -0
- package/src/lib/worktree-sparse.js +131 -0
- package/src/repl/agent-repl.js +280 -30
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +565 -21
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/diagnostics.js +60 -0
- package/src/runtime/headless-runner.js +393 -1
- package/src/runtime/headless-stream.js +60 -1
- package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
- package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
- package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall-first secret scanner + a labeled corpus and recall / false-positive /
|
|
3
|
+
* latency benchmark (IDE gap §8.1 "凭据脱敏真实语料基准" of
|
|
4
|
+
* CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-07-13.md).
|
|
5
|
+
*
|
|
6
|
+
* [[ide-context-redaction.js]] `redactSecretsInText` is deliberately PRECISION-
|
|
7
|
+
* first: it guards editor context that rides into the model, where mangling
|
|
8
|
+
* everyday code teaches users to disable it. That posture is right there, but it
|
|
9
|
+
* is the wrong one for the EXPORT gate — a diagnostic bundle / artifact / trace
|
|
10
|
+
* that leaves the machine must fail toward NOT leaking, i.e. recall-first. §8.1
|
|
11
|
+
* asks to prove a redactor with a real corpus (provider tokens, JWT, Bearer,
|
|
12
|
+
* PEM, cloud creds, connection strings, cookies, `.env` variants) plus counter-
|
|
13
|
+
* examples (sample values, hashes, UUIDs, plain base64) and to report recall,
|
|
14
|
+
* false-positive rate, P95 latency and max input size — per surface.
|
|
15
|
+
*
|
|
16
|
+
* This module is that recall-first scanner (`scanSecrets` / `redactSecrets`),
|
|
17
|
+
* the labeled `SECRET_CORPUS`, and `runRedactionBenchmark` which measures ANY
|
|
18
|
+
* redactor against it. It does NOT touch the precision-first IDE redactor, so
|
|
19
|
+
* default IDE-context behavior is unchanged; this is the scanner the future
|
|
20
|
+
* "Secret Scan before export" gate (P1-9 诊断包) runs.
|
|
21
|
+
*
|
|
22
|
+
* Pure: no fs / RNG / clock. Latency is measured only when a `clock` is
|
|
23
|
+
* injected, keeping the module deterministic in tests.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { isSecretEnvName } from "./credential-guard.js";
|
|
27
|
+
|
|
28
|
+
export const REDACTED = "[REDACTED]";
|
|
29
|
+
|
|
30
|
+
/** The export surfaces §8.1 wants measured independently. */
|
|
31
|
+
export const SURFACES = Object.freeze([
|
|
32
|
+
"source_context",
|
|
33
|
+
"transcript",
|
|
34
|
+
"trace",
|
|
35
|
+
"artifact",
|
|
36
|
+
"diagnostic_bundle",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/** The secret categories the scanner covers. */
|
|
40
|
+
export const SECRET_CATEGORIES = Object.freeze([
|
|
41
|
+
"provider_token",
|
|
42
|
+
"jwt",
|
|
43
|
+
"bearer",
|
|
44
|
+
"pem_private_key",
|
|
45
|
+
"connection_string",
|
|
46
|
+
"cookie",
|
|
47
|
+
"env_assignment",
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
// Each pattern is stored as a SOURCE string so `scanSecrets` compiles a fresh
|
|
51
|
+
// RegExp per call — a shared /g/ regex carries `lastIndex` state across calls
|
|
52
|
+
// and would skip matches. `replace` resets state itself, but scan/match do not.
|
|
53
|
+
const PATTERNS = [
|
|
54
|
+
{
|
|
55
|
+
category: "provider_token",
|
|
56
|
+
// OpenAI/Anthropic sk-, GitHub PAT/OAuth, GitLab PAT, Slack xox*, AWS keys.
|
|
57
|
+
source:
|
|
58
|
+
"\\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,}|(?:AKIA|ASIA)[0-9A-Z]{16})\\b",
|
|
59
|
+
flags: "g",
|
|
60
|
+
replace: () => REDACTED,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
category: "jwt",
|
|
64
|
+
// Three base64url segments; the first two begin `eyJ` (base64 of `{"`).
|
|
65
|
+
source:
|
|
66
|
+
"\\beyJ[A-Za-z0-9_-]{6,}\\.eyJ[A-Za-z0-9_-]{6,}\\.[A-Za-z0-9_-]{6,}\\b",
|
|
67
|
+
flags: "g",
|
|
68
|
+
replace: () => REDACTED,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
category: "bearer",
|
|
72
|
+
source: "\\bBearer\\s+[A-Za-z0-9\\-._~+/=]{8,}",
|
|
73
|
+
flags: "g",
|
|
74
|
+
replace: () => `Bearer ${REDACTED}`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
category: "pem_private_key",
|
|
78
|
+
// The bytes ARE the secret; a truncated END marker still redacts to the end.
|
|
79
|
+
source:
|
|
80
|
+
"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\\s\\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----|$)",
|
|
81
|
+
flags: "g",
|
|
82
|
+
replace: () => REDACTED,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
category: "connection_string",
|
|
86
|
+
// scheme://user:PASSWORD@host — the embedded password is the secret. Only
|
|
87
|
+
// the credential form (with `:pass@`) matches, so `https://example.com` is
|
|
88
|
+
// untouched. Keep the scheme + user, swallow the password + host tail.
|
|
89
|
+
source:
|
|
90
|
+
"\\b((?:postgres|postgresql|mysql|mongodb(?:\\+srv)?|redis|rediss|amqp|amqps|https?|ftp|ssh):\\/\\/[^:\\s/@]+:)[^@\\s/]+@\\S+",
|
|
91
|
+
flags: "gi",
|
|
92
|
+
replace: (_m, prefix) => `${prefix}${REDACTED}`,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
category: "cookie",
|
|
96
|
+
// A Cookie / Set-Cookie header value. Keep the header name, swallow the value.
|
|
97
|
+
source: "\\b(Set-Cookie|Cookie)(\\s*:\\s*)\\S[^\\n\\r]*",
|
|
98
|
+
flags: "gi",
|
|
99
|
+
replace: (_m, name, sep) => `${name}${sep}${REDACTED}`,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
category: "env_assignment",
|
|
103
|
+
// `API_KEY = "value"` / `token: value`. The key must contain a boundary-
|
|
104
|
+
// delimited secret word (isSecretEnvName), the value a bare token-like run.
|
|
105
|
+
// The lookahead rejects `token = getToken()` (code, not a secret).
|
|
106
|
+
source:
|
|
107
|
+
"\\b([A-Za-z_][A-Za-z0-9_-]*)([\"']?\\s*[=:]\\s*)([\"']?)([A-Za-z0-9+/=_.\\-]{8,})\\3(?![A-Za-z0-9+/=_.\\-(])",
|
|
108
|
+
flags: "g",
|
|
109
|
+
replace: (m, key, sep, quote) =>
|
|
110
|
+
isSecretEnvName(key.replace(/-/g, "_"))
|
|
111
|
+
? `${key}${sep}${quote}${REDACTED}${quote}`
|
|
112
|
+
: m, // key isn't secret-shaped → leave the assignment alone
|
|
113
|
+
// env_assignment only counts as a detection when the key gates true; a
|
|
114
|
+
// guard so scanSecrets doesn't report `count = 12345678` as a secret.
|
|
115
|
+
guard: (m, groups) => isSecretEnvName(String(groups[0]).replace(/-/g, "_")),
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Find every secret-shaped span in `text`. Recall-first: overlapping categories
|
|
121
|
+
* all report, so an assignment holding a JWT is caught by both.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} text
|
|
124
|
+
* @returns {Array<{category:string, index:number, length:number, match:string}>}
|
|
125
|
+
*/
|
|
126
|
+
export function scanSecrets(text) {
|
|
127
|
+
if (typeof text !== "string" || text.length === 0) return [];
|
|
128
|
+
const findings = [];
|
|
129
|
+
for (const p of PATTERNS) {
|
|
130
|
+
const re = new RegExp(p.source, p.flags);
|
|
131
|
+
for (const m of text.matchAll(re)) {
|
|
132
|
+
if (p.guard && !p.guard(m[0], m.slice(1))) continue;
|
|
133
|
+
findings.push({
|
|
134
|
+
category: p.category,
|
|
135
|
+
index: m.index,
|
|
136
|
+
length: m[0].length,
|
|
137
|
+
match: m[0],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return findings.sort((a, b) => a.index - b.index || a.length - b.length);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Redact every secret-shaped span, keeping the surviving structure a human /
|
|
146
|
+
* agent still needs (the `Bearer` word, a header/key name, a connection scheme)
|
|
147
|
+
* while swallowing the secret value.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} text
|
|
150
|
+
* @returns {string}
|
|
151
|
+
*/
|
|
152
|
+
export function redactSecrets(text) {
|
|
153
|
+
if (typeof text !== "string" || text.length === 0) return text;
|
|
154
|
+
let out = text;
|
|
155
|
+
for (const p of PATTERNS) {
|
|
156
|
+
const re = new RegExp(p.source, p.flags);
|
|
157
|
+
out = out.replace(re, p.replace);
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Whether `text` contains any secret (the export gate's fail-closed check). */
|
|
163
|
+
export function containsSecret(text) {
|
|
164
|
+
return scanSecrets(text).length > 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Labeled corpus ───────────────────────────────────────────────────────────
|
|
168
|
+
//
|
|
169
|
+
// Positives carry the exact `secret` substring that MUST disappear after
|
|
170
|
+
// redaction; negatives (sample/placeholder values, hashes, UUIDs, plain base64,
|
|
171
|
+
// ordinary code) MUST pass through byte-for-byte. Items are spread across the
|
|
172
|
+
// export surfaces so the benchmark can report per-surface.
|
|
173
|
+
|
|
174
|
+
const POSITIVES = [
|
|
175
|
+
{
|
|
176
|
+
id: "openai",
|
|
177
|
+
category: "provider_token",
|
|
178
|
+
surface: "transcript",
|
|
179
|
+
secret: "sk-abcd1234efgh5678ijkl",
|
|
180
|
+
text: 'client = OpenAI(api_key="sk-abcd1234efgh5678ijkl")',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: "github_pat",
|
|
184
|
+
category: "provider_token",
|
|
185
|
+
surface: "artifact",
|
|
186
|
+
secret: "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
|
|
187
|
+
text: "git remote set-url origin https://ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@github.com/x/y",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "aws",
|
|
191
|
+
category: "provider_token",
|
|
192
|
+
surface: "diagnostic_bundle",
|
|
193
|
+
secret: "AKIAIOSFODNN7EXAMPLE",
|
|
194
|
+
text: "aws_access_key_id AKIAIOSFODNN7EXAMPLE",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "jwt",
|
|
198
|
+
category: "jwt",
|
|
199
|
+
surface: "trace",
|
|
200
|
+
secret:
|
|
201
|
+
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U",
|
|
202
|
+
text: "Authorization header carried eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "bearer",
|
|
206
|
+
category: "bearer",
|
|
207
|
+
surface: "source_context",
|
|
208
|
+
secret: "aG9sZDpteS1zZWNyZXQtdG9rZW4",
|
|
209
|
+
text: "curl -H 'Authorization: Bearer aG9sZDpteS1zZWNyZXQtdG9rZW4' https://api",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: "pem",
|
|
213
|
+
category: "pem_private_key",
|
|
214
|
+
surface: "artifact",
|
|
215
|
+
secret:
|
|
216
|
+
"-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAKj34Gkx...\n-----END RSA PRIVATE KEY-----",
|
|
217
|
+
text: "key file:\n-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAKj34Gkx...\n-----END RSA PRIVATE KEY-----\n",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "postgres",
|
|
221
|
+
category: "connection_string",
|
|
222
|
+
surface: "diagnostic_bundle",
|
|
223
|
+
secret: "s3cr3tP@ss",
|
|
224
|
+
text: "DATABASE_URL=postgres://appuser:s3cr3tP@ss@db.internal:5432/prod",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: "mongodb",
|
|
228
|
+
category: "connection_string",
|
|
229
|
+
surface: "trace",
|
|
230
|
+
secret: "hunter2hunter2",
|
|
231
|
+
text: "mongodb+srv://svc:hunter2hunter2@cluster0.mongodb.net/app",
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: "cookie",
|
|
235
|
+
category: "cookie",
|
|
236
|
+
surface: "transcript",
|
|
237
|
+
secret: "sessionid=9f8b7c6d5e4f3a2b1c0d",
|
|
238
|
+
text: "Set-Cookie: sessionid=9f8b7c6d5e4f3a2b1c0d; HttpOnly",
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "env_password",
|
|
242
|
+
category: "env_assignment",
|
|
243
|
+
surface: "diagnostic_bundle",
|
|
244
|
+
secret: "Sup3rSecret_Value-123",
|
|
245
|
+
text: 'DB_PASSWORD="Sup3rSecret_Value-123"',
|
|
246
|
+
},
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
const NEGATIVES = [
|
|
250
|
+
{
|
|
251
|
+
id: "uuid",
|
|
252
|
+
surface: "trace",
|
|
253
|
+
text: "requestId = 550e8400-e29b-41d4-a716-446655440000",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: "git_sha",
|
|
257
|
+
surface: "transcript",
|
|
258
|
+
text: "commit beaa3f9ec7069258aad5e8587d32f779da5e5d20 landed",
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: "plain_base64",
|
|
262
|
+
surface: "artifact",
|
|
263
|
+
text: "payload blob aGVsbG8gd29ybGQgdGhpcyBpcyBub3QgYSBzZWNyZXQ=",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: "prose",
|
|
267
|
+
surface: "source_context",
|
|
268
|
+
text: "The function returns the number of open editor tabs.",
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "code_call",
|
|
272
|
+
surface: "source_context",
|
|
273
|
+
text: "const token = getToken(config)",
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: "nonsecret_assignment",
|
|
277
|
+
surface: "diagnostic_bundle",
|
|
278
|
+
text: "retryCount = 12345678",
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: "plain_url",
|
|
282
|
+
surface: "artifact",
|
|
283
|
+
text: "docs at https://example.com/guide/index.html",
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: "hex_digest",
|
|
287
|
+
surface: "diagnostic_bundle",
|
|
288
|
+
text: "sha256 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
|
|
289
|
+
},
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
export const SECRET_CORPUS = Object.freeze({
|
|
293
|
+
positives: Object.freeze(POSITIVES.map((p) => Object.freeze(p))),
|
|
294
|
+
negatives: Object.freeze(NEGATIVES.map((n) => Object.freeze(n))),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
function percentile(sortedAsc, p) {
|
|
298
|
+
if (sortedAsc.length === 0) return null;
|
|
299
|
+
const idx = Math.min(
|
|
300
|
+
sortedAsc.length - 1,
|
|
301
|
+
Math.max(0, Math.ceil((p / 100) * sortedAsc.length) - 1),
|
|
302
|
+
);
|
|
303
|
+
return sortedAsc[idx];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Measure a redactor against a labeled corpus: recall (positives whose secret
|
|
308
|
+
* disappeared), false-positive rate (negatives that were altered), max input
|
|
309
|
+
* size, and — only when a `clock` is injected — P95 redaction latency. Reports
|
|
310
|
+
* overall, per category (recall) and per surface (recall + FPR).
|
|
311
|
+
*
|
|
312
|
+
* @param {object} [corpus=SECRET_CORPUS]
|
|
313
|
+
* @param {object} [opts]
|
|
314
|
+
* @param {(text:string)=>string} [opts.redactor=redactSecrets]
|
|
315
|
+
* @param {()=>number} [opts.clock] ms clock; enables latency metrics
|
|
316
|
+
* @returns {object} the benchmark report
|
|
317
|
+
*/
|
|
318
|
+
export function runRedactionBenchmark(corpus = SECRET_CORPUS, opts = {}) {
|
|
319
|
+
const redactor =
|
|
320
|
+
typeof opts.redactor === "function" ? opts.redactor : redactSecrets;
|
|
321
|
+
const clock = typeof opts.clock === "function" ? opts.clock : null;
|
|
322
|
+
const positives = Array.isArray(corpus?.positives) ? corpus.positives : [];
|
|
323
|
+
const negatives = Array.isArray(corpus?.negatives) ? corpus.negatives : [];
|
|
324
|
+
|
|
325
|
+
const latencies = [];
|
|
326
|
+
let maxInputBytes = 0;
|
|
327
|
+
const measure = (text) => {
|
|
328
|
+
maxInputBytes = Math.max(maxInputBytes, Buffer.byteLength(text, "utf8"));
|
|
329
|
+
if (!clock) return redactor(text);
|
|
330
|
+
const t0 = Number(clock());
|
|
331
|
+
const out = redactor(text);
|
|
332
|
+
const t1 = Number(clock());
|
|
333
|
+
if (Number.isFinite(t0) && Number.isFinite(t1)) latencies.push(t1 - t0);
|
|
334
|
+
return out;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const catRecall = {}; // category -> {detected, total}
|
|
338
|
+
const surface = {}; // surface -> {detected, posTotal, falsePos, negTotal}
|
|
339
|
+
const bumpSurface = (s) =>
|
|
340
|
+
(surface[s] = surface[s] || {
|
|
341
|
+
detected: 0,
|
|
342
|
+
posTotal: 0,
|
|
343
|
+
falsePos: 0,
|
|
344
|
+
negTotal: 0,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const missed = [];
|
|
348
|
+
let detected = 0;
|
|
349
|
+
for (const item of positives) {
|
|
350
|
+
const out = measure(item.text);
|
|
351
|
+
const ok = typeof item.secret === "string" && !out.includes(item.secret);
|
|
352
|
+
const cat = (catRecall[item.category] = catRecall[item.category] || {
|
|
353
|
+
detected: 0,
|
|
354
|
+
total: 0,
|
|
355
|
+
});
|
|
356
|
+
cat.total += 1;
|
|
357
|
+
const s = bumpSurface(item.surface);
|
|
358
|
+
s.posTotal += 1;
|
|
359
|
+
if (ok) {
|
|
360
|
+
detected += 1;
|
|
361
|
+
cat.detected += 1;
|
|
362
|
+
s.detected += 1;
|
|
363
|
+
} else {
|
|
364
|
+
missed.push(item.id);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const falsePositives = [];
|
|
369
|
+
let fp = 0;
|
|
370
|
+
for (const item of negatives) {
|
|
371
|
+
const out = measure(item.text);
|
|
372
|
+
const s = bumpSurface(item.surface);
|
|
373
|
+
s.negTotal += 1;
|
|
374
|
+
if (out !== item.text) {
|
|
375
|
+
fp += 1;
|
|
376
|
+
s.falsePos += 1;
|
|
377
|
+
falsePositives.push(item.id);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const byCategory = {};
|
|
382
|
+
for (const [c, v] of Object.entries(catRecall)) {
|
|
383
|
+
byCategory[c] = { recall: v.total ? v.detected / v.total : 1, ...v };
|
|
384
|
+
}
|
|
385
|
+
const bySurface = {};
|
|
386
|
+
for (const [sName, v] of Object.entries(surface)) {
|
|
387
|
+
bySurface[sName] = {
|
|
388
|
+
recall: v.posTotal ? v.detected / v.posTotal : 1,
|
|
389
|
+
falsePositiveRate: v.negTotal ? v.falsePos / v.negTotal : 0,
|
|
390
|
+
...v,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const sorted = latencies.slice().sort((a, b) => a - b);
|
|
395
|
+
return {
|
|
396
|
+
overall: {
|
|
397
|
+
recall: positives.length ? detected / positives.length : 1,
|
|
398
|
+
falsePositiveRate: negatives.length ? fp / negatives.length : 0,
|
|
399
|
+
positives: positives.length,
|
|
400
|
+
negatives: negatives.length,
|
|
401
|
+
maxInputBytes,
|
|
402
|
+
p95LatencyMs: clock ? percentile(sorted, 95) : null,
|
|
403
|
+
},
|
|
404
|
+
missed,
|
|
405
|
+
falsePositives,
|
|
406
|
+
byCategory,
|
|
407
|
+
bySurface,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
Binary file
|
|
@@ -19,12 +19,18 @@
|
|
|
19
19
|
const { collectHooks } = require("./settings-hooks.cjs");
|
|
20
20
|
const { runHooks } = require("./hook-runner.cjs");
|
|
21
21
|
const { buildHookEnvelope } = require("./hook-event-bus.cjs");
|
|
22
|
+
const hookEventLog = require("./hook-event-log.cjs");
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Stamp a unified-bus delivery id (event_id) onto a hook payload (P2 event bus).
|
|
25
26
|
* Additive — a hook that ignores it is unaffected; a hook that reads it gets a
|
|
26
27
|
* stable per-delivery id for correlation / `cc hook replay`. traceId/parentId
|
|
27
28
|
* are threaded when the caller provides them (else null).
|
|
29
|
+
*
|
|
30
|
+
* When `CC_HOOK_EVENT_LOG` is enabled, the FULL envelope is best-effort recorded
|
|
31
|
+
* to the hash-chained hook event log so `cc hook replay <event-id>` has a real
|
|
32
|
+
* source. Recording is opt-in and never throws, so the default firing path is
|
|
33
|
+
* byte-for-byte unchanged.
|
|
28
34
|
*/
|
|
29
35
|
function withDeliveryId(event, payload, { sessionId, traceId, parentId } = {}) {
|
|
30
36
|
const env = buildHookEnvelope({
|
|
@@ -34,6 +40,13 @@ function withDeliveryId(event, payload, { sessionId, traceId, parentId } = {}) {
|
|
|
34
40
|
traceId: traceId || null,
|
|
35
41
|
parentId: parentId || null,
|
|
36
42
|
});
|
|
43
|
+
if (hookEventLog.isHookEventLogEnabled()) {
|
|
44
|
+
try {
|
|
45
|
+
hookEventLog.appendHookEvent(env);
|
|
46
|
+
} catch {
|
|
47
|
+
/* best-effort — recording must not break the turn */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
37
50
|
return { ...payload, event_id: env.event_id };
|
|
38
51
|
}
|
|
39
52
|
|
|
@@ -415,11 +415,53 @@ function readBooleanSetting(key, opts = {}) {
|
|
|
415
415
|
return value;
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Read a string-array setting across the layered `.claude/settings.json` files
|
|
420
|
+
* plus managed settings. Unlike {@link readBooleanSetting} (last-layer wins),
|
|
421
|
+
* arrays UNION across layers (mirroring how `env`/`sandbox` accumulate in
|
|
422
|
+
* {@link loadSettingsConfig}) — a user-scope and a project-scope layer can each
|
|
423
|
+
* contribute entries. Non-array values and non-string members are ignored.
|
|
424
|
+
* Returns a de-duplicated array, or `undefined` when no layer sets it.
|
|
425
|
+
*
|
|
426
|
+
* @param {string} key settings key or dotted path (e.g. "instructionExcludes")
|
|
427
|
+
* @param {object} [opts]
|
|
428
|
+
* @param {string} [opts.cwd=process.cwd()]
|
|
429
|
+
* @param {string} [opts.settingsFile]
|
|
430
|
+
* @param {(msg:string)=>void} [opts.onWarn]
|
|
431
|
+
* @returns {string[]|undefined}
|
|
432
|
+
*/
|
|
433
|
+
function readStringArraySetting(key, opts = {}) {
|
|
434
|
+
const cwd = opts.cwd || process.cwd();
|
|
435
|
+
const parts = String(key).split(".");
|
|
436
|
+
const collected = [];
|
|
437
|
+
let sawAny = false;
|
|
438
|
+
const absorb = (data) => {
|
|
439
|
+
let node = data;
|
|
440
|
+
for (const p of parts) {
|
|
441
|
+
node = node && typeof node === "object" ? node[p] : undefined;
|
|
442
|
+
}
|
|
443
|
+
if (Array.isArray(node)) {
|
|
444
|
+
sawAny = true;
|
|
445
|
+
for (const item of node) {
|
|
446
|
+
if (typeof item === "string" && item.trim()) collected.push(item);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
for (const file of settingsPaths(cwd, opts.settingsFile)) {
|
|
451
|
+
absorb(readSettingsFile(file, { onWarn: opts.onWarn }));
|
|
452
|
+
}
|
|
453
|
+
const managed = loadManagedSettings(opts);
|
|
454
|
+
if (managed.settings) absorb(managed.settings);
|
|
455
|
+
if (!sawAny) return undefined;
|
|
456
|
+
return [...new Set(collected)];
|
|
457
|
+
}
|
|
458
|
+
|
|
418
459
|
module.exports = {
|
|
419
460
|
loadSettings,
|
|
420
461
|
loadSettingsConfig,
|
|
421
462
|
readSettingsFile,
|
|
422
463
|
readBooleanSetting,
|
|
464
|
+
readStringArraySetting,
|
|
423
465
|
settingsPaths,
|
|
424
466
|
managedSettingsPath,
|
|
425
467
|
loadManagedSettings,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persist the side-effect ledger ([[side-effect-ledger.js]]) as a tamper-evident
|
|
3
|
+
* chained session event so a worker that crashed mid `git push` / `file-write` /
|
|
4
|
+
* `package-install` can rebuild its ledger on recovery and reconcile WITHOUT
|
|
5
|
+
* repeating a side-effect it already issued.
|
|
6
|
+
*
|
|
7
|
+
* Same shape as [[turn-binding-store.js]]: each persist writes the FULL ledger
|
|
8
|
+
* snapshot (newest wins), all I/O is delegated to the canonical session store
|
|
9
|
+
* via `_deps` so the module stays pure-testable.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
appendEvent as storeAppendEvent,
|
|
14
|
+
readEvents as storeReadEvents,
|
|
15
|
+
} from "../harness/jsonl-session-store.js";
|
|
16
|
+
import {
|
|
17
|
+
SideEffectLedger,
|
|
18
|
+
reconcileSideEffects,
|
|
19
|
+
} from "./side-effect-ledger.js";
|
|
20
|
+
|
|
21
|
+
/** Event type carrying a full SideEffectLedger snapshot. */
|
|
22
|
+
export const SIDE_EFFECT_LEDGER_EVENT = "side_effect_ledger";
|
|
23
|
+
|
|
24
|
+
export const _deps = {
|
|
25
|
+
appendEvent: storeAppendEvent,
|
|
26
|
+
readEvents: storeReadEvents,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Append the current ledger as a chained event. Best-effort — a persistence
|
|
31
|
+
* failure must not crash the operation it is guarding.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} sessionId
|
|
34
|
+
* @param {SideEffectLedger|object} ledger
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
export function persistSideEffectLedger(sessionId, ledger) {
|
|
38
|
+
if (!sessionId || !ledger) return false;
|
|
39
|
+
const snapshot =
|
|
40
|
+
ledger instanceof SideEffectLedger ? ledger.toJSON() : ledger;
|
|
41
|
+
if (!snapshot || !Array.isArray(snapshot.ops)) return false;
|
|
42
|
+
try {
|
|
43
|
+
_deps.appendEvent(sessionId, SIDE_EFFECT_LEDGER_EVENT, snapshot);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Rebuild the ledger from the LATEST persisted snapshot, or an empty ledger when
|
|
52
|
+
* the session never persisted one (or on read error).
|
|
53
|
+
*
|
|
54
|
+
* @param {string} sessionId
|
|
55
|
+
* @param {{clock?:Function}} [opts]
|
|
56
|
+
* @returns {SideEffectLedger}
|
|
57
|
+
*/
|
|
58
|
+
export function loadSideEffectLedger(sessionId, opts = {}) {
|
|
59
|
+
let events = [];
|
|
60
|
+
try {
|
|
61
|
+
events = _deps.readEvents(sessionId) || [];
|
|
62
|
+
} catch {
|
|
63
|
+
return new SideEffectLedger(opts);
|
|
64
|
+
}
|
|
65
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
66
|
+
const e = events[i];
|
|
67
|
+
if (
|
|
68
|
+
e &&
|
|
69
|
+
e.type === SIDE_EFFECT_LEDGER_EVENT &&
|
|
70
|
+
e.data &&
|
|
71
|
+
Array.isArray(e.data.ops)
|
|
72
|
+
) {
|
|
73
|
+
return SideEffectLedger.fromJSON(e.data, opts);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return new SideEffectLedger(opts);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Load + reconcile in one step: the recovery entry point. Returns the plan a
|
|
81
|
+
* restarting worker uses to decide, per op, redo / inspect / skip.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} sessionId
|
|
84
|
+
* @returns {{plans:Array, redo:string[], inspect:string[], skip:string[]}}
|
|
85
|
+
*/
|
|
86
|
+
export function reconcileSessionSideEffects(sessionId) {
|
|
87
|
+
return reconcileSideEffects(loadSideEffectLedger(sessionId));
|
|
88
|
+
}
|