chainlesschain 0.162.163 → 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-BQ0kgfA1.js → AIOps-BqRkaHGb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Dr1MDANe.js → ActionButton-DWO7OZLC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CZ5vsaXW.js → Analytics-C5oaVezM.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-BIBga881.js → AppLayout-CsUESDoZ.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-vpXHRU3u.js → Artifacts-Dragqu5k.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-B8exuoub.js → Audit-CQsaHctY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-923evRRN.js → BackgroundAgents-DdG1jNSM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-sr_sswlQ.js → Backup-DN6sjK4z.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CnW-4XKT.js → BaseInput-Cxm_ZTCl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-FZCsTZIL.js → Chat-DVXGmA56.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DDPd9qT0.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DFrxB59H.js → Checkbox-DEc4vD_A.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-_b6WEel8.js → Codegen-CKiTcv36.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CkbnnZ-K.js → Col-Br9UUNN0.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BxM_82zW.js → Community-NeUH_70T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DYinP45R.js → Compact-BqR-z2Df.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DyXp3sSP.js → Compliance-DcWB_SgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BEr2DhW7.js → Cowork-BfhgcudF.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-B-Vwo1hN.js → Cron-LaU29LgD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DbLxKddd.js → Crosschain-B2ZB9uwW.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BsQ_LFw9.js → DID-BuHl38A2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-lVlXQHu5.js → Dashboard-m627yqvZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Beo_foO7.js → Dropdown-C7bxF8F5.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DszHIXED.js → EmailListRenderer-DPLPQzLp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DcAwoh81.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CuJLa2eb.js → Federation-Bvkzg88z.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cxd1lxhc.js → FormItemContext-BTsiA7h_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BBvq5Jsw.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
- package/src/assets/web-panel/assets/{Git-WqPKetx6.js → Git-DS_j6r25.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CWYtY3ke.js → Governance-CKRG3Rsl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BnX1iiNV.js → Inference-DW8INoNQ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BJuM8GN5.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DqLMNntm.js → Logs-wqi0uxuR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BqYwEYou.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-_vGfmhNl.js → Marketplace-CMlTuPLT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BeJDOkDj.js → McpTools-CSx4m5JJ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Da0EEZDO.js → Memory-vnQCUy-_.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DSuzcCCy.js → MobileBridge-BV2NA742.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-BE9YgdA0.js → Mtc-CzK_FPB1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-fC2BpO5S.js → MtcAudit-Cb7G9oL3.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-D24Xxqw3.js → Multisig-qJxlUyzr.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bs3oGvS8.js → NLProgramming-DqaBJ7Te.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BFjmAA-3.js → Notes-CwhTJkRi.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BX_9Bhv0.js → NotificationSettings-YDs4Fal4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-wjkZwJXI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DBcdsVEP.js → Organization-CEmQm9n8.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-DDyaF9vC.js → Overflow-zby_P_LA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BB78L4he.js → P2P-DsmDNFNT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DqTiZFum.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-INbnWv4j.js → Permissions-DuzQ6me5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BST-VmTq.js → PersonalDataHub-DZbeN613.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CE7_tS2k.js → Pipeline-CoNSVAUS.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-hK5L8dOB.js → Privacy-OYJEAaMC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BvkkUoDn.js → ProjectInit-CWIapV--.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-u7_EUtkm.js → ProjectSettings-CX8xMxZn.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-5Fmt_8Nw.js → Projects-BLCN8YyY.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B9eanarR.js → Providers-BV_sxp75.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C0dX5N2f.js → QrScannerModal-BTnRrHRp.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CKTpG-PZ.js → QuickAsk-D1KZg018.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Px_w_XQM.js → Recommend-COm5cuNg.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Di0fnYkM.js → RemoteSession-0saWEkYI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-vesnWjXt.js → Reputation-Dr7iKLS2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-exbY6LkT.js → Row-C1vpPBcr.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B3O3x70t.js → RssFeed-CxlWrtNj.js} +2 -2
- package/src/assets/web-panel/assets/{Search-uRA0DhNz.js → Search-BT2V8dPN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CViqdH6K.js → Security-bm9V3c5k.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CkXS97WN.js → Services-CocH1yyX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DxW5WYnE.js → Skeleton-DHItlMdd.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CLxmGFse.js → Skills-C59sLc4w.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Cr5fziXq.js → Sla-Crj4BMc8.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-H0JSb3cp.js → SpeechSettings-DX4KFceY.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DMYWMae7.js → SyncSettings-BD_iwQLr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CbYyDdF4.js → Tasks-BmCTzYsH.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Cpc8JjiK.js → Templates-BGUaxZYl.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CRh77Jda.js → Tenant-BS0tJx2O.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-4D8tNBO5.js → Terminal--tHSAsld.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D41jzzbG.js → TimelineRenderer-D9-MFxS4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C_jWEy6b.js → Tokens-CiCB485f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dk1ZUhsy.js → Trigger-SmCFuW_l.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CLVy4l7m.js → Trust-DkK3Rqe0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C-FeqFDj.js → UkeySign-CDM4LGdB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CqVZzyMm.js → VideoEditing-IDB7DJXt.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DX9BW9pu.js → Wallet-Bikj5cmt.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-7hQDpWw_.js → WebAuthn-C9abWlAw.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-6XqUAO0j.js → WorkflowEditor-DrbRKVky.js} +1 -1
- package/src/assets/web-panel/assets/{chat-5R_vkaJG.js → chat-CccCSDQf.js} +1 -1
- package/src/assets/web-panel/assets/{colors-huUPZdC-.js → colors-CXObXLhb.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CfJlgisE.js → compact-item-B5kfYAxC.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-jO8heofr.js → createContext-DmiPBfip.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DY8Vxqx7.js → hasIn-Q6lYteAc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtEXSoPL.js → index-25Wlqps4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsBtRWbA.js → index-33U29UNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQS-uPOK.js → index-4oXBDczR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXBcvUnf.js → index-BGYXvMc5.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2iVSFpy.js → index-BJCCa0s_.js} +1 -1
- package/src/assets/web-panel/assets/{index-A48PtXw9.js → index-BJJ5DhKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db1cLWir.js → index-BLOzViJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-vXs3B8GJ.js → index-BRrbUMoG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHE8OIls.js → index-BUFpT17Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmqGerpC.js → index-BkRDiqGc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzKmteNS.js → index-BsfvW6my.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAON7TzU.js → index-By-kfPED.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoI2sZm7.js → index-BzFTcQsc.js} +1 -1
- package/src/assets/web-panel/assets/{index-j3O2EEJe.js → index-C2FXdboq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRElRhyS.js → index-C5XOlTHN.js} +1 -1
- package/src/assets/web-panel/assets/{index-KRHroryI.js → index-CLhBDS2w.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxax9vQ6.js → index-CMq7UeoT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfCx4pFT.js → index-CVMTWNKl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CA6sO5VD.js → index-Caghi-yL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CA90TZxG.js → index-CdB6FuSH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cbe9G0V4.js → index-CoK8-kWQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0pet5Uy.js → index-CsO3GUAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChvHubX3.js → index-CwZBa20Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRXOP09X.js → index-D-kASucw.js} +1 -1
- package/src/assets/web-panel/assets/{index-B74OXFxC.js → index-D8eBw7vW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1VLuww0.js → index-D9wgifk4.js} +1 -1
- package/src/assets/web-panel/assets/{index-p85YaaKw.js → index-DAYCTc07.js} +1 -1
- package/src/assets/web-panel/assets/{index-BliaT02i.js → index-DF6Q8l2h.js} +1 -1
- package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
- package/src/assets/web-panel/assets/{index-Cjxbu68P.js → index-DgTLApS9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cis_Ip7H.js → index-DgxVJ8pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-v3pTaEQm.js → index-DlJvIqoR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYLBPpt8.js → index-Dw4eHfsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-B300RPnT.js → index-Rh-ytwow.js} +3 -3
- package/src/assets/web-panel/assets/{index-CLhBh-qE.js → index-StAEt2pq.js} +1 -1
- package/src/assets/web-panel/assets/{index-d1xkbtNz.js → index-bzfw951k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCqxyvqp.js → index-fSUMbMvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-dKx57io7.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-MCB7-raZ.js → initDefaultProps-BNq7X72N.js} +1 -1
- package/src/assets/web-panel/assets/{motion-UWDF9swV.js → motion-DOf9viTc.js} +1 -1
- package/src/assets/web-panel/assets/{move-CsTmgXZU.js → move-CRHTcW4x.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-fQy6KGLg.js → mtc-parser-Cyoozgm2.js} +1 -1
- package/src/assets/web-panel/assets/{omit-woPnEjd1.js → omit-6-TjFNru.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Bs2dZ1gf.js → pickAttrs-DlUmO1zi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-C7EVgPkg.js → placementArrow-CFOL8mIR.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BH2kYCTN.js → responsiveObserve-BtaFK5zd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BFBP2V_e.js → slide-BIbc2bur.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-xdDxDYse.js → statusUtils-BXX9d6-w.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-aAWgPPQf.js → styleChecker-BGv10Bpn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DkGSAWoO.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BrOih40N.js → useFs-FY4IL4jn.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-eIYrgMqO.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B8lutNSE.js → vnode-BM3OHxAT.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CffNre7V.js → zoom-D1fbFiHB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +71 -14
- package/src/commands/doctor.js +85 -1
- package/src/commands/plugin.js +226 -9
- package/src/commands/review.js +17 -1
- package/src/commands/session.js +191 -0
- package/src/commands/team.js +19 -1
- package/src/data/changelog.json +5 -4
- package/src/gateways/ws/session-protocol.js +50 -0
- 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 +30 -1
- 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/diagnostic-bundle.js +230 -0
- package/src/lib/doctor-checkup.js +223 -4
- package/src/lib/execution-location.js +286 -0
- package/src/lib/headless-manifest.js +13 -40
- package/src/lib/hook-stats-store.cjs +197 -0
- package/src/lib/idempotency.js +109 -0
- 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 +28 -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/pr-automation-policy.js +224 -0
- package/src/lib/remote-approval-bridge.js +66 -13
- package/src/lib/repl-rewind.js +101 -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/secret-scan.js +409 -0
- package/src/lib/session-branch.js +0 -0
- package/src/lib/side-effect-ledger.js +105 -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 +36 -4
- package/src/lib/sub-agent-registry.js +19 -0
- package/src/lib/telemetry/span-recorder.js +17 -4
- package/src/lib/terminal-context.js +195 -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 +152 -38
- package/src/runtime/agent-core.js +191 -13
- package/src/runtime/diagnostics.js +60 -0
- package/src/runtime/headless-runner.js +126 -1
- package/src/assets/web-panel/assets/MobileProjects-D0RAk4e8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +0 -1
- package/src/assets/web-panel/assets/index-BxUKMRpP.js +0 -1
- package/src/assets/web-panel/assets/index-DscU9iPI.js +0 -1
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical capability manifest — the single source the CLI capability output,
|
|
3
|
+
* the VS Code / JetBrains negotiation, the protocol docs, the compatibility
|
|
4
|
+
* fixture, the behavior matrix and the release-notes capability diff all project
|
|
5
|
+
* from (P1-9 "Capability Manifest 与脱敏诊断包" of
|
|
6
|
+
* CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-07-13.md).
|
|
7
|
+
*
|
|
8
|
+
* Today the wire-protocol capability surface is hand-authored in THREE places
|
|
9
|
+
* that can silently drift:
|
|
10
|
+
* - [[capability-negotiation.js]]: `PROTOCOL_FEATURES`, `FEATURE_MIN_VERSION`,
|
|
11
|
+
* and the private `FEATURE_TO_FIELD` map.
|
|
12
|
+
* - [[headless-manifest.js]]: `buildAgentCapabilities().features` (the
|
|
13
|
+
* `cc agent --capabilities` output).
|
|
14
|
+
* - the Java twin + the shared negotiation fixture.
|
|
15
|
+
* A new v2 field added to one list but not the others parses wrong on the peer
|
|
16
|
+
* that missed it. This module is the ONE definition; every other shape is a pure
|
|
17
|
+
* projection of it, and a drift-guard test asserts the live hand-authored
|
|
18
|
+
* constants still equal the projection (see capability-manifest.test.js).
|
|
19
|
+
*
|
|
20
|
+
* Pure: no fs / process / clock. Projections are deterministic so a caller can
|
|
21
|
+
* regenerate docs/fixtures and diff them against the checked-in copies in CI.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* THE canonical manifest. `wireFeatures` is the negotiable additive per-line
|
|
28
|
+
* field set (order mirrors capability-negotiation.js `PROTOCOL_FEATURES`);
|
|
29
|
+
* `runtimeFeatures` are the boolean capability flags that change what the CLI
|
|
30
|
+
* can DO (not the shape of a line the client must parse) and are NOT negotiated.
|
|
31
|
+
*/
|
|
32
|
+
export const CAPABILITY_MANIFEST = Object.freeze({
|
|
33
|
+
name: "chainlesschain-cli",
|
|
34
|
+
protocolVersion: 1,
|
|
35
|
+
minProtocolVersion: 1,
|
|
36
|
+
|
|
37
|
+
wireFeatures: Object.freeze([
|
|
38
|
+
Object.freeze({
|
|
39
|
+
key: "event_seq",
|
|
40
|
+
field: "seq",
|
|
41
|
+
minVersion: 1,
|
|
42
|
+
description:
|
|
43
|
+
"every stream-json stdout line carries a monotonic per-run `seq`",
|
|
44
|
+
}),
|
|
45
|
+
Object.freeze({
|
|
46
|
+
key: "tool_use_id",
|
|
47
|
+
field: "tool_use_id",
|
|
48
|
+
minVersion: 1,
|
|
49
|
+
description:
|
|
50
|
+
'tool_use / tool_result lines carry a pairing `id` ("tu-<n>")',
|
|
51
|
+
}),
|
|
52
|
+
Object.freeze({
|
|
53
|
+
key: "trace_id",
|
|
54
|
+
field: "trace_id",
|
|
55
|
+
minVersion: 1,
|
|
56
|
+
description:
|
|
57
|
+
"each line carries a run-scoped, caller-injectable `trace_id` for IDE ↔ CLI correlation",
|
|
58
|
+
}),
|
|
59
|
+
]),
|
|
60
|
+
|
|
61
|
+
// Boolean capability flags (buildAgentCapabilities().features truthy keys).
|
|
62
|
+
runtimeFeatures: Object.freeze([
|
|
63
|
+
"bare",
|
|
64
|
+
"safe_mode",
|
|
65
|
+
"ephemeral",
|
|
66
|
+
"background",
|
|
67
|
+
"worktree",
|
|
68
|
+
"checkpoint",
|
|
69
|
+
"json_schema_output",
|
|
70
|
+
"partial_messages",
|
|
71
|
+
"interactive_approvals",
|
|
72
|
+
"permission_prompt_tool",
|
|
73
|
+
"remote_control",
|
|
74
|
+
]),
|
|
75
|
+
|
|
76
|
+
mcpFeatures: Object.freeze([
|
|
77
|
+
"config_file",
|
|
78
|
+
"registry",
|
|
79
|
+
"tool_search",
|
|
80
|
+
"oauth",
|
|
81
|
+
"managed_allowlist",
|
|
82
|
+
]),
|
|
83
|
+
sandboxEngines: Object.freeze(["docker", "bubblewrap"]),
|
|
84
|
+
|
|
85
|
+
permissionModes: Object.freeze([
|
|
86
|
+
"default",
|
|
87
|
+
"manual",
|
|
88
|
+
"auto",
|
|
89
|
+
"dontAsk",
|
|
90
|
+
"plan",
|
|
91
|
+
"acceptEdits",
|
|
92
|
+
"bypassPermissions",
|
|
93
|
+
]),
|
|
94
|
+
outputFormats: Object.freeze(["text", "json", "stream-json"]),
|
|
95
|
+
inputFormats: Object.freeze(["text", "stream-json"]),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const manifestOr = (m) =>
|
|
99
|
+
m && typeof m === "object" ? m : CAPABILITY_MANIFEST;
|
|
100
|
+
const wireFeaturesOf = (m) =>
|
|
101
|
+
Array.isArray(manifestOr(m).wireFeatures) ? manifestOr(m).wireFeatures : [];
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The negotiable wire feature keys, in canonical declared order — the value
|
|
105
|
+
* capability-negotiation.js `PROTOCOL_FEATURES` must equal.
|
|
106
|
+
* @returns {string[]}
|
|
107
|
+
*/
|
|
108
|
+
export function toProtocolFeatures(m = CAPABILITY_MANIFEST) {
|
|
109
|
+
return wireFeaturesOf(m).map((f) => f.key);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Feature → minimum protocol version, ONLY for features gated above the base
|
|
114
|
+
* version (a v1-additive field is absent, matching an empty
|
|
115
|
+
* `FEATURE_MIN_VERSION` today). Adding a v2 feature here flows the gate to the
|
|
116
|
+
* negotiator automatically.
|
|
117
|
+
* @returns {Record<string, number>}
|
|
118
|
+
*/
|
|
119
|
+
export function toFeatureMinVersion(m = CAPABILITY_MANIFEST) {
|
|
120
|
+
const out = {};
|
|
121
|
+
for (const f of wireFeaturesOf(m)) {
|
|
122
|
+
if (Number(f.minVersion) > 1) out[f.key] = Number(f.minVersion);
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Feature → the stream line field it gates (the value of the private
|
|
129
|
+
* `FEATURE_TO_FIELD` map in capability-negotiation.js).
|
|
130
|
+
* @returns {Record<string, string>}
|
|
131
|
+
*/
|
|
132
|
+
export function toFieldGate(m = CAPABILITY_MANIFEST) {
|
|
133
|
+
const out = {};
|
|
134
|
+
for (const f of wireFeaturesOf(m)) out[f.key] = f.field;
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The server negotiation offer shape consumed by
|
|
140
|
+
* capability-negotiation.js `negotiateProtocol(server, …)`.
|
|
141
|
+
* @returns {{protocolVersion:number, minProtocolVersion:number, features:string[]}}
|
|
142
|
+
*/
|
|
143
|
+
export function toServerOffer(m = CAPABILITY_MANIFEST) {
|
|
144
|
+
const mm = manifestOr(m);
|
|
145
|
+
return {
|
|
146
|
+
protocolVersion: Number(mm.protocolVersion) || 1,
|
|
147
|
+
minProtocolVersion: Number(mm.minProtocolVersion) || 1,
|
|
148
|
+
features: toProtocolFeatures(mm),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The `features` object of `cc agent --capabilities`
|
|
154
|
+
* (buildAgentCapabilities().features): every runtime flag true, every wire
|
|
155
|
+
* feature true, plus the nested `mcp` object and `sandbox_engines` list. A test
|
|
156
|
+
* asserts the live buildAgentCapabilities().features deep-equals this.
|
|
157
|
+
* @returns {object}
|
|
158
|
+
*/
|
|
159
|
+
export function toAgentFeatureFlags(m = CAPABILITY_MANIFEST) {
|
|
160
|
+
const mm = manifestOr(m);
|
|
161
|
+
const flags = {};
|
|
162
|
+
for (const key of mm.runtimeFeatures || []) flags[key] = true;
|
|
163
|
+
for (const f of wireFeaturesOf(mm)) flags[f.key] = true;
|
|
164
|
+
flags.mcp = {};
|
|
165
|
+
for (const key of mm.mcpFeatures || []) flags.mcp[key] = true;
|
|
166
|
+
flags.sandbox_engines = [...(mm.sandboxEngines || [])];
|
|
167
|
+
return flags;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A deterministic capability fixture the VS Code / JetBrains twins can pin and
|
|
172
|
+
* assert against (the "Compatibility Fixture" the gap doc asks for). Order- and
|
|
173
|
+
* clock-independent; a `digest` lets a peer detect any change in one compare.
|
|
174
|
+
* @returns {object}
|
|
175
|
+
*/
|
|
176
|
+
export function buildCompatFixture(m = CAPABILITY_MANIFEST) {
|
|
177
|
+
const mm = manifestOr(m);
|
|
178
|
+
const body = {
|
|
179
|
+
protocolVersion: Number(mm.protocolVersion) || 1,
|
|
180
|
+
minProtocolVersion: Number(mm.minProtocolVersion) || 1,
|
|
181
|
+
features: toProtocolFeatures(mm),
|
|
182
|
+
featureFields: toFieldGate(mm),
|
|
183
|
+
featureMinVersion: toFeatureMinVersion(mm),
|
|
184
|
+
permissionModes: [...(mm.permissionModes || [])],
|
|
185
|
+
outputFormats: [...(mm.outputFormats || [])],
|
|
186
|
+
inputFormats: [...(mm.inputFormats || [])],
|
|
187
|
+
};
|
|
188
|
+
return { ...body, digest: capabilityDigest(mm) };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A stable digest of the whole capability surface (no RNG/clock) — one string
|
|
193
|
+
* that changes iff any projected capability changed. Handy as a CI drift check.
|
|
194
|
+
* @returns {string} 16-hex-char sha256 prefix
|
|
195
|
+
*/
|
|
196
|
+
export function capabilityDigest(m = CAPABILITY_MANIFEST) {
|
|
197
|
+
const mm = manifestOr(m);
|
|
198
|
+
const canonical = {
|
|
199
|
+
protocolVersion: Number(mm.protocolVersion) || 1,
|
|
200
|
+
minProtocolVersion: Number(mm.minProtocolVersion) || 1,
|
|
201
|
+
wire: wireFeaturesOf(mm)
|
|
202
|
+
.map((f) => [f.key, f.field, Number(f.minVersion) || 1])
|
|
203
|
+
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)),
|
|
204
|
+
runtime: [...(mm.runtimeFeatures || [])].sort(),
|
|
205
|
+
mcp: [...(mm.mcpFeatures || [])].sort(),
|
|
206
|
+
sandbox: [...(mm.sandboxEngines || [])].sort(),
|
|
207
|
+
permissionModes: [...(mm.permissionModes || [])].sort(),
|
|
208
|
+
outputFormats: [...(mm.outputFormats || [])].sort(),
|
|
209
|
+
inputFormats: [...(mm.inputFormats || [])].sort(),
|
|
210
|
+
};
|
|
211
|
+
return createHash("sha256")
|
|
212
|
+
.update(JSON.stringify(canonical))
|
|
213
|
+
.digest("hex")
|
|
214
|
+
.slice(0, 16);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* A behavior matrix row per wire feature — feature × min version × gated field ×
|
|
219
|
+
* negotiable × description. Consumed by renderProtocolDoc and by a test that a
|
|
220
|
+
* doc table matches the code.
|
|
221
|
+
* @returns {Array<{feature:string, minVersion:number, field:string, negotiable:boolean, description:string}>}
|
|
222
|
+
*/
|
|
223
|
+
export function renderBehaviorMatrix(m = CAPABILITY_MANIFEST) {
|
|
224
|
+
return wireFeaturesOf(m).map((f) => ({
|
|
225
|
+
feature: f.key,
|
|
226
|
+
minVersion: Number(f.minVersion) || 1,
|
|
227
|
+
field: f.field,
|
|
228
|
+
negotiable: true,
|
|
229
|
+
description: String(f.description || ""),
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Render the protocol capability section as Markdown (a generated doc a CI job
|
|
235
|
+
* diffs against the checked-in copy). Deterministic; ends with a trailing
|
|
236
|
+
* newline so a byte compare is stable.
|
|
237
|
+
* @returns {string}
|
|
238
|
+
*/
|
|
239
|
+
export function renderProtocolDoc(m = CAPABILITY_MANIFEST) {
|
|
240
|
+
const mm = manifestOr(m);
|
|
241
|
+
const lines = [];
|
|
242
|
+
lines.push("# Agent Protocol Capability Manifest (generated)");
|
|
243
|
+
lines.push("");
|
|
244
|
+
lines.push(
|
|
245
|
+
`Protocol version **${mm.protocolVersion}** (negotiates down to **${mm.minProtocolVersion}**).`,
|
|
246
|
+
);
|
|
247
|
+
lines.push("");
|
|
248
|
+
lines.push("## Negotiable wire features");
|
|
249
|
+
lines.push("");
|
|
250
|
+
lines.push("| Feature | Min version | Line field | Description |");
|
|
251
|
+
lines.push("| ------- | ----------- | ---------- | ----------- |");
|
|
252
|
+
for (const row of renderBehaviorMatrix(mm)) {
|
|
253
|
+
lines.push(
|
|
254
|
+
`| \`${row.feature}\` | ${row.minVersion} | \`${row.field}\` | ${row.description} |`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
lines.push("");
|
|
258
|
+
lines.push("## Permission modes");
|
|
259
|
+
lines.push("");
|
|
260
|
+
lines.push((mm.permissionModes || []).map((x) => `\`${x}\``).join(", "));
|
|
261
|
+
lines.push("");
|
|
262
|
+
return lines.join("\n") + "\n";
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Capability diff for release notes: what changed between a previous and a next
|
|
267
|
+
* manifest (or a manifest and the current canonical one). Additive vs removed vs
|
|
268
|
+
* min-version/field changes, plus any protocol-version bump.
|
|
269
|
+
*
|
|
270
|
+
* @param {object} prev a manifest (or its compat fixture)
|
|
271
|
+
* @param {object} [next=CAPABILITY_MANIFEST]
|
|
272
|
+
* @returns {{protocolChange:{from:number,to:number}|null,
|
|
273
|
+
* addedWireFeatures:string[], removedWireFeatures:string[],
|
|
274
|
+
* changedWireFeatures:Array<{feature:string, from:object, to:object}>,
|
|
275
|
+
* addedRuntimeFeatures:string[], removedRuntimeFeatures:string[]}}
|
|
276
|
+
*/
|
|
277
|
+
export function diffCapabilities(prev, next = CAPABILITY_MANIFEST) {
|
|
278
|
+
const wireMap = (m) => {
|
|
279
|
+
const out = new Map();
|
|
280
|
+
for (const f of wireFeaturesOf(m)) {
|
|
281
|
+
out.set(f.key, {
|
|
282
|
+
field: f.field,
|
|
283
|
+
minVersion: Number(f.minVersion) || 1,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
};
|
|
288
|
+
const runtimeSet = (m) => new Set(manifestOr(m).runtimeFeatures || []);
|
|
289
|
+
|
|
290
|
+
const p = wireMap(prev);
|
|
291
|
+
const n = wireMap(next);
|
|
292
|
+
const addedWireFeatures = [...n.keys()].filter((k) => !p.has(k)).sort();
|
|
293
|
+
const removedWireFeatures = [...p.keys()].filter((k) => !n.has(k)).sort();
|
|
294
|
+
const changedWireFeatures = [];
|
|
295
|
+
for (const [key, nv] of n) {
|
|
296
|
+
const pv = p.get(key);
|
|
297
|
+
if (pv && (pv.field !== nv.field || pv.minVersion !== nv.minVersion)) {
|
|
298
|
+
changedWireFeatures.push({ feature: key, from: pv, to: nv });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const pr = runtimeSet(prev);
|
|
303
|
+
const nr = runtimeSet(next);
|
|
304
|
+
const addedRuntimeFeatures = [...nr].filter((k) => !pr.has(k)).sort();
|
|
305
|
+
const removedRuntimeFeatures = [...pr].filter((k) => !nr.has(k)).sort();
|
|
306
|
+
|
|
307
|
+
const pVer = Number(manifestOr(prev).protocolVersion) || 1;
|
|
308
|
+
const nVer = Number(manifestOr(next).protocolVersion) || 1;
|
|
309
|
+
const protocolChange = pVer === nVer ? null : { from: pVer, to: nVer };
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
protocolChange,
|
|
313
|
+
addedWireFeatures,
|
|
314
|
+
removedWireFeatures,
|
|
315
|
+
changedWireFeatures,
|
|
316
|
+
addedRuntimeFeatures,
|
|
317
|
+
removedRuntimeFeatures,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
@@ -36,31 +36,42 @@
|
|
|
36
36
|
* fixture __tests__/fixtures/capability-negotiation-cases.json.
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
+
import {
|
|
40
|
+
CAPABILITY_MANIFEST,
|
|
41
|
+
toProtocolFeatures,
|
|
42
|
+
toFeatureMinVersion,
|
|
43
|
+
toFieldGate,
|
|
44
|
+
} from "./capability-manifest.js";
|
|
45
|
+
|
|
39
46
|
/** The current protocol version the CLI speaks (mirror of PROTOCOL_VERSION). */
|
|
40
|
-
export const PROTOCOL_VERSION =
|
|
47
|
+
export const PROTOCOL_VERSION = CAPABILITY_MANIFEST.protocolVersion;
|
|
41
48
|
|
|
42
49
|
/**
|
|
43
50
|
* The oldest protocol version the CLI can still speak end-to-end (the N-1 in
|
|
44
51
|
* "N / N-1"). At v1 there is no older line shape, so min === current; once v2
|
|
45
52
|
* ships this drops to 1 so a v1-only client negotiates a v1 session.
|
|
46
53
|
*/
|
|
47
|
-
export const PROTOCOL_MIN_VERSION =
|
|
54
|
+
export const PROTOCOL_MIN_VERSION = CAPABILITY_MANIFEST.minProtocolVersion;
|
|
48
55
|
|
|
49
56
|
/**
|
|
50
57
|
* The wire-protocol features subject to negotiation — the additive per-line
|
|
51
58
|
* fields a client may or may not understand. Runtime capabilities (bare,
|
|
52
59
|
* worktree, mcp, …) are NOT negotiated: they change what the CLI can do, not
|
|
53
60
|
* the shape of a line the client must parse.
|
|
61
|
+
*
|
|
62
|
+
* P1-9: projected from the single canonical [[capability-manifest.js]] so the
|
|
63
|
+
* negotiator, `cc agent --capabilities`, the protocol docs and the Java twin can
|
|
64
|
+
* no longer drift — add a v2 field to the manifest ONCE and it flows here.
|
|
54
65
|
*/
|
|
55
|
-
export const PROTOCOL_FEATURES =
|
|
66
|
+
export const PROTOCOL_FEATURES = toProtocolFeatures();
|
|
56
67
|
|
|
57
68
|
/**
|
|
58
69
|
* Minimum protocol version a feature requires. Empty today (every current
|
|
59
|
-
* feature is a v1 additive field). When a v2 field lands,
|
|
60
|
-
* N-1 (v1) session drops it automatically.
|
|
70
|
+
* feature is a v1 additive field). When a v2 field lands, bump its manifest
|
|
71
|
+
* `minVersion` and an N-1 (v1) session drops it automatically.
|
|
61
72
|
* @type {Record<string, number>}
|
|
62
73
|
*/
|
|
63
|
-
export const FEATURE_MIN_VERSION =
|
|
74
|
+
export const FEATURE_MIN_VERSION = toFeatureMinVersion();
|
|
64
75
|
|
|
65
76
|
function intOr(value, fallback) {
|
|
66
77
|
const n = Number(value);
|
|
@@ -185,12 +196,12 @@ export function negotiateProtocol(server = {}, clientOffer = null, opts = {}) {
|
|
|
185
196
|
};
|
|
186
197
|
}
|
|
187
198
|
|
|
188
|
-
/**
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Feature key → the stream line field it gates. P1-9: projected from the
|
|
201
|
+
* canonical [[capability-manifest.js]] so this map can't drift from the
|
|
202
|
+
* negotiated feature list.
|
|
203
|
+
*/
|
|
204
|
+
const FEATURE_TO_FIELD = toFieldGate();
|
|
194
205
|
|
|
195
206
|
/**
|
|
196
207
|
* Fold a negotiation result into a live field-gate the emitter reads per line:
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* De-identified diagnostic bundle assembler + mandatory secret-scan export gate
|
|
3
|
+
* (IDE gap P1-9 "脱敏诊断包" of CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-
|
|
4
|
+
* 07-13.md).
|
|
5
|
+
*
|
|
6
|
+
* When an IDE session misbehaves, a one-click diagnostic export is the fastest
|
|
7
|
+
* path to a fix — but a naive dump of "everything about the session" leaks
|
|
8
|
+
* source, keys and terminal output. The gap doc pins the contract exactly: a
|
|
9
|
+
* bundle carries version, platform, capability, connection state, redaction
|
|
10
|
+
* events, trace, reconnect history, and process/port/lockfile/worktree
|
|
11
|
+
* summaries; it does NOT carry source code, API keys, cookies, full environment
|
|
12
|
+
* variables, or unpermitted terminal output; and "导出前必须运行 Secret Scan".
|
|
13
|
+
*
|
|
14
|
+
* This module assembles that bundle from caller-supplied signals and runs the
|
|
15
|
+
* §8.1 recall-first scanner ([[secret-scan.js]]) as a fail-closed export gate.
|
|
16
|
+
* It reuses the canonical capability projection ([[capability-manifest.js]]) so
|
|
17
|
+
* the bundle never invents a second capability truth.
|
|
18
|
+
*
|
|
19
|
+
* Pure: no fs / process / clock / RNG — the caller collects the raw signals
|
|
20
|
+
* (which processes, which ports, the env object) and this module shapes, de-
|
|
21
|
+
* identifies and gates them. Timestamps, if any, are passed in.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { redactSecrets, scanSecrets, SURFACES } from "./secret-scan.js";
|
|
25
|
+
import { buildCompatFixture } from "./capability-manifest.js";
|
|
26
|
+
import { isSecretEnvName } from "./credential-guard.js";
|
|
27
|
+
|
|
28
|
+
/** The categories deliberately EXCLUDED from every bundle (never assembled). */
|
|
29
|
+
export const EXCLUDED_BY_DEFAULT = Object.freeze([
|
|
30
|
+
"source code body",
|
|
31
|
+
"API keys / tokens",
|
|
32
|
+
"cookies",
|
|
33
|
+
"full environment variable values",
|
|
34
|
+
"unpermitted terminal output",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Summarize an environment WITHOUT any values: variable NAMES only, with the
|
|
39
|
+
* secret-named ones flagged (via isSecretEnvName). Honors the contract "默认不
|
|
40
|
+
* 包含完整环境变量" — a name is metadata, a value may be a credential.
|
|
41
|
+
* @param {object} env
|
|
42
|
+
* @returns {{count:number, names:string[], secretNames:string[]}}
|
|
43
|
+
*/
|
|
44
|
+
export function summarizeEnv(env) {
|
|
45
|
+
if (!env || typeof env !== "object") {
|
|
46
|
+
return { count: 0, names: [], secretNames: [] };
|
|
47
|
+
}
|
|
48
|
+
const names = Object.keys(env).sort();
|
|
49
|
+
const secretNames = names.filter((n) => isSecretEnvName(n));
|
|
50
|
+
return { count: names.length, names, secretNames };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Recursively redact every string in a value (arrays/objects walked). */
|
|
54
|
+
function deepRedact(value, depth = 0) {
|
|
55
|
+
if (depth > 64) return value;
|
|
56
|
+
if (typeof value === "string") return redactSecrets(value);
|
|
57
|
+
if (Array.isArray(value)) return value.map((v) => deepRedact(v, depth + 1));
|
|
58
|
+
if (value && typeof value === "object") {
|
|
59
|
+
const out = {};
|
|
60
|
+
for (const k of Object.keys(value))
|
|
61
|
+
out[k] = deepRedact(value[k], depth + 1);
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Recursively scan a value for any residual secret, returning findings with a
|
|
69
|
+
* dotted path so the gate can name WHERE a leak survived. Read-only.
|
|
70
|
+
* @returns {Array<{path:string, category:string}>}
|
|
71
|
+
*/
|
|
72
|
+
export function scanBundleForSecrets(value, path = "", depth = 0) {
|
|
73
|
+
if (depth > 64) return [];
|
|
74
|
+
const findings = [];
|
|
75
|
+
if (typeof value === "string") {
|
|
76
|
+
for (const f of scanSecrets(value)) {
|
|
77
|
+
findings.push({ path: path || "(root)", category: f.category });
|
|
78
|
+
}
|
|
79
|
+
} else if (Array.isArray(value)) {
|
|
80
|
+
value.forEach((v, i) =>
|
|
81
|
+
findings.push(...scanBundleForSecrets(v, `${path}[${i}]`, depth + 1)),
|
|
82
|
+
);
|
|
83
|
+
} else if (value && typeof value === "object") {
|
|
84
|
+
for (const k of Object.keys(value)) {
|
|
85
|
+
findings.push(
|
|
86
|
+
...scanBundleForSecrets(value[k], path ? `${path}.${k}` : k, depth + 1),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return findings;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const asArray = (v) => (Array.isArray(v) ? v : []);
|
|
94
|
+
const asString = (v) =>
|
|
95
|
+
typeof v === "string" ? v : v == null ? "" : String(v);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Assemble the diagnostic bundle from caller-supplied signals, de-identifying
|
|
99
|
+
* as it goes. Free-text fields are secret-redacted; env is reduced to names;
|
|
100
|
+
* terminal output is included ONLY with explicit permission (and still
|
|
101
|
+
* redacted). Excluded categories are never assembled in the first place.
|
|
102
|
+
*
|
|
103
|
+
* @param {object} input
|
|
104
|
+
* @param {string} [input.version]
|
|
105
|
+
* @param {object|string} [input.platform]
|
|
106
|
+
* @param {object} [input.capability] defaults to the canonical compat fixture
|
|
107
|
+
* @param {string} [input.connectionState]
|
|
108
|
+
* @param {Array} [input.reconnectHistory] [{at?, reason?}] — reasons redacted
|
|
109
|
+
* @param {object} [input.trace] {traceId, spanCount} summary (no content)
|
|
110
|
+
* @param {Array} [input.redactionEvents] [{surface, category, count}] — what was scrubbed
|
|
111
|
+
* @param {Array} [input.processes] [{pid, name, cpu?}] metadata
|
|
112
|
+
* @param {Array<number|object>} [input.ports]
|
|
113
|
+
* @param {Array<string>} [input.lockfiles]
|
|
114
|
+
* @param {Array} [input.worktrees] [{path, branch}]
|
|
115
|
+
* @param {object} [input.env] reduced to names only
|
|
116
|
+
* @param {string} [input.terminalOutput] included only when opts.includeTerminalOutput
|
|
117
|
+
* @param {string} [input.notes]
|
|
118
|
+
* @param {object} [opts]
|
|
119
|
+
* @param {boolean} [opts.includeTerminalOutput=false]
|
|
120
|
+
* @returns {object} the assembled (already-redacted) bundle
|
|
121
|
+
*/
|
|
122
|
+
export function buildDiagnosticBundle(input = {}, opts = {}) {
|
|
123
|
+
const includeTerminal = opts.includeTerminalOutput === true;
|
|
124
|
+
|
|
125
|
+
const redactionEvents = asArray(input.redactionEvents)
|
|
126
|
+
.map((e) => ({
|
|
127
|
+
surface: SURFACES.includes(e?.surface) ? e.surface : "unknown",
|
|
128
|
+
category: asString(e?.category),
|
|
129
|
+
count: Number(e?.count) || 0,
|
|
130
|
+
}))
|
|
131
|
+
.filter((e) => e.category);
|
|
132
|
+
|
|
133
|
+
const bundle = {
|
|
134
|
+
schema: "cc-diagnostic-bundle/v1",
|
|
135
|
+
meta: {
|
|
136
|
+
version: asString(input.version) || null,
|
|
137
|
+
platform:
|
|
138
|
+
input.platform && typeof input.platform === "object"
|
|
139
|
+
? input.platform
|
|
140
|
+
: asString(input.platform) || null,
|
|
141
|
+
excluded: [...EXCLUDED_BY_DEFAULT],
|
|
142
|
+
},
|
|
143
|
+
capability:
|
|
144
|
+
input.capability && typeof input.capability === "object"
|
|
145
|
+
? input.capability
|
|
146
|
+
: buildCompatFixture(),
|
|
147
|
+
connection: {
|
|
148
|
+
state: asString(input.connectionState) || null,
|
|
149
|
+
reconnectHistory: asArray(input.reconnectHistory).map((r) => ({
|
|
150
|
+
at: r?.at ?? null,
|
|
151
|
+
reason: asString(r?.reason),
|
|
152
|
+
})),
|
|
153
|
+
},
|
|
154
|
+
trace: input.trace
|
|
155
|
+
? {
|
|
156
|
+
traceId: asString(input.trace.traceId) || null,
|
|
157
|
+
spanCount: Number(input.trace.spanCount) || 0,
|
|
158
|
+
}
|
|
159
|
+
: null,
|
|
160
|
+
redactionEvents,
|
|
161
|
+
runtime: {
|
|
162
|
+
processes: asArray(input.processes).map((p) => ({
|
|
163
|
+
pid: p?.pid ?? null,
|
|
164
|
+
name: asString(p?.name),
|
|
165
|
+
cpu: p?.cpu ?? null,
|
|
166
|
+
})),
|
|
167
|
+
ports: asArray(input.ports).map((p) =>
|
|
168
|
+
typeof p === "object" && p
|
|
169
|
+
? { port: Number(p.port) || null, proto: asString(p.proto) || null }
|
|
170
|
+
: { port: Number(p) || null, proto: null },
|
|
171
|
+
),
|
|
172
|
+
lockfiles: asArray(input.lockfiles).map((f) => asString(f)),
|
|
173
|
+
worktrees: asArray(input.worktrees).map((w) => ({
|
|
174
|
+
path: asString(w?.path),
|
|
175
|
+
branch: asString(w?.branch) || null,
|
|
176
|
+
})),
|
|
177
|
+
},
|
|
178
|
+
// env is reduced to NAMES only up front (values never enter the bundle).
|
|
179
|
+
env: summarizeEnv(input.env),
|
|
180
|
+
notes: input.notes != null ? asString(input.notes) : null,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Terminal output is unpermitted by default: dropped entirely, with a marker
|
|
184
|
+
// so the reader knows it was withheld (not merely absent).
|
|
185
|
+
if (includeTerminal && input.terminalOutput != null) {
|
|
186
|
+
bundle.terminalOutput = asString(input.terminalOutput);
|
|
187
|
+
} else {
|
|
188
|
+
bundle.terminalOutput = null;
|
|
189
|
+
bundle.terminalOutputWithheld = input.terminalOutput != null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Single de-identification pass over the WHOLE assembled bundle: every string
|
|
193
|
+
// field is secret-redacted regardless of which section it landed in, so the
|
|
194
|
+
// export gate is guaranteed clean no matter which signal carried a secret.
|
|
195
|
+
// env is already values-free, so its names (metadata) pass through untouched.
|
|
196
|
+
return deepRedact(bundle);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The mandatory pre-export gate ("导出前必须运行 Secret Scan"). Deep-scans the
|
|
201
|
+
* assembled bundle; fail-closed: if ANY residual secret survives, `ok` is false
|
|
202
|
+
* and `blocked` is true — the caller must NOT export it. On a clean bundle the
|
|
203
|
+
* gate returns it ready for export.
|
|
204
|
+
*
|
|
205
|
+
* @param {object} bundle a buildDiagnosticBundle() result
|
|
206
|
+
* @returns {{ok:boolean, blocked:boolean, findings:Array, bundle:object}}
|
|
207
|
+
*/
|
|
208
|
+
export function secretScanGate(bundle) {
|
|
209
|
+
const findings = scanBundleForSecrets(bundle);
|
|
210
|
+
const ok = findings.length === 0;
|
|
211
|
+
return { ok, blocked: !ok, findings, bundle };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* One-call assembly + gate. Builds the de-identified bundle and runs the secret
|
|
216
|
+
* scan. When the gate is clean, returns the exportable bundle; when it is not
|
|
217
|
+
* (should never happen given assembly redacts, but defense-in-depth), returns
|
|
218
|
+
* `ok:false` and the leak locations, and does NOT hand back an exportable body.
|
|
219
|
+
*
|
|
220
|
+
* @returns {{ok:boolean, bundle:(object|null), findings:Array}}
|
|
221
|
+
*/
|
|
222
|
+
export function exportDiagnosticBundle(input = {}, opts = {}) {
|
|
223
|
+
const bundle = buildDiagnosticBundle(input, opts);
|
|
224
|
+
const gate = secretScanGate(bundle);
|
|
225
|
+
return {
|
|
226
|
+
ok: gate.ok,
|
|
227
|
+
bundle: gate.ok ? bundle : null,
|
|
228
|
+
findings: gate.findings,
|
|
229
|
+
};
|
|
230
|
+
}
|