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
|
@@ -19,6 +19,33 @@
|
|
|
19
19
|
import path from "path";
|
|
20
20
|
import { discoverPlugins } from "./scopes.js";
|
|
21
21
|
import { partitionByTrust, warnUntrustedOnce } from "./trust.js";
|
|
22
|
+
import { componentCapabilityDenial } from "./capabilities.js";
|
|
23
|
+
|
|
24
|
+
// One-time stderr notice when a plugin's bin dir is refused at the COMPONENT
|
|
25
|
+
// level because the plugin opted into the capability model but did not declare
|
|
26
|
+
// the `process` capability its executables need. Distinct from the trust gate:
|
|
27
|
+
// these plugins ARE trusted, but their bin component is denied.
|
|
28
|
+
const _capabilityDenied = new Set();
|
|
29
|
+
function warnBinCapabilityDeniedOnce(entries) {
|
|
30
|
+
if (!entries || entries.length === 0) return;
|
|
31
|
+
if (_capabilityDenied.has("bin-capability")) return;
|
|
32
|
+
_capabilityDenied.add("bin-capability");
|
|
33
|
+
const list = entries.map((e) => `${e.name} (${e.reason})`).join("; ");
|
|
34
|
+
try {
|
|
35
|
+
process.stderr.write(
|
|
36
|
+
`[plugins] refused bin dir(s) from plugin(s) that declared a permissions ` +
|
|
37
|
+
`block but did not declare the 'process' capability: ${list}\n` +
|
|
38
|
+
` add 'process' to the plugin's permissions block to enable them.\n`,
|
|
39
|
+
);
|
|
40
|
+
} catch {
|
|
41
|
+
/* stderr notice is best-effort */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Test hook: reset the one-time capability-denied warning guard. */
|
|
46
|
+
export function _resetBinWarnings() {
|
|
47
|
+
_capabilityDenied.clear();
|
|
48
|
+
}
|
|
22
49
|
|
|
23
50
|
/**
|
|
24
51
|
* Collect trusted, installed plugins' `bin/` directories.
|
|
@@ -40,10 +67,19 @@ export function collectPluginBinDirs(opts = {}) {
|
|
|
40
67
|
"bin",
|
|
41
68
|
);
|
|
42
69
|
const out = [];
|
|
70
|
+
const denied = [];
|
|
43
71
|
for (const p of trusted) {
|
|
44
72
|
if (!p.manifest || p.manifest.ok !== true) continue;
|
|
45
73
|
const bins = p.manifest.components?.bin;
|
|
46
74
|
if (!Array.isArray(bins) || bins.length === 0) continue;
|
|
75
|
+
// Component-level capability gate: a plugin that declared a permissions
|
|
76
|
+
// block but did not declare the `process` capability its executables need
|
|
77
|
+
// gets its bin dir refused here (mirrors the MCP collector's denial).
|
|
78
|
+
const denial = componentCapabilityDenial(p.manifest, ["process"]);
|
|
79
|
+
if (denial) {
|
|
80
|
+
denied.push({ name: p.name, reason: denial.reason });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
47
83
|
// Every bin entry's absPath is a file inside the plugin's bin dir; collect
|
|
48
84
|
// the unique parent directories (usually just `<root>/bin`).
|
|
49
85
|
const dirs = new Set(bins.map((b) => path.dirname(b.absPath)));
|
|
@@ -56,6 +92,7 @@ export function collectPluginBinDirs(opts = {}) {
|
|
|
56
92
|
});
|
|
57
93
|
}
|
|
58
94
|
}
|
|
95
|
+
warnBinCapabilityDeniedOnce(denied);
|
|
59
96
|
return out;
|
|
60
97
|
}
|
|
61
98
|
|
|
@@ -220,6 +220,25 @@ export function auditDeclaredCapabilities(manifest) {
|
|
|
220
220
|
return findings;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Component-level capability denial. A plugin that opted into the capability
|
|
225
|
+
* model (declared a `permissions` block, so `capabilitiesDeclared === true`) but
|
|
226
|
+
* ships a component whose required capability it did NOT declare should have that
|
|
227
|
+
* single component refused — not just flagged by the whole-plugin consent notice.
|
|
228
|
+
* `capabilityNames` is the set of capability names whose absence denies this
|
|
229
|
+
* component (e.g. `["process"]` for a hook/bin/lsp, `["monitor"]` for a monitor,
|
|
230
|
+
* `["mcp","network"]` for an MCP server). Legacy plugins (no permissions block)
|
|
231
|
+
* are unrestricted by design and never denied. Returns `{reason}` or null.
|
|
232
|
+
*/
|
|
233
|
+
export function componentCapabilityDenial(manifest, capabilityNames) {
|
|
234
|
+
if (!manifest || manifest.capabilitiesDeclared !== true) return null;
|
|
235
|
+
const wanted = new Set(capabilityNames || []);
|
|
236
|
+
const finding = auditDeclaredCapabilities(manifest).find((f) =>
|
|
237
|
+
wanted.has(f.capability),
|
|
238
|
+
);
|
|
239
|
+
return finding ? { reason: finding.reason } : null;
|
|
240
|
+
}
|
|
241
|
+
|
|
223
242
|
// ─── options schema ─────────────────────────────────────────────────────────
|
|
224
243
|
|
|
225
244
|
export const OPTION_TYPES = Object.freeze([
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin capability CONSENT (Phase 3 gap: capability declaration + re-consent).
|
|
3
|
+
*
|
|
4
|
+
* Complements version trust ([[trust.js]]): trust pins the exact CODE a plugin
|
|
5
|
+
* ships (re-consent on version bump); this pins the CAPABILITY SET the plugin is
|
|
6
|
+
* allowed to request (process / network / filesystem / mcp / monitor /
|
|
7
|
+
* credential). The two are orthogonal — a plugin can be trusted-to-run yet still
|
|
8
|
+
* WIDEN what it wants (add `network:*`, a new credential), which must re-prompt.
|
|
9
|
+
*
|
|
10
|
+
* Consent is keyed by the SET, not the version: once the user consents, later
|
|
11
|
+
* versions that keep the same (or a narrower) capability set stay consented; any
|
|
12
|
+
* WIDENING (a new grant) revokes consent until re-granted. This mirrors
|
|
13
|
+
* `consentRequiredForUpgrade` in [[capabilities.js]] exactly.
|
|
14
|
+
*
|
|
15
|
+
* Recorded in the USER data dir (never in the repo — a checked-in project file
|
|
16
|
+
* must not be able to pre-grant itself capabilities), keyed `<scope>:<name>` →
|
|
17
|
+
* { version, capabilities, consentedAt }. Pure over injected IO so unit tests
|
|
18
|
+
* never touch the real user-data dir.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
import { getElectronUserDataDir } from "../paths.js";
|
|
24
|
+
import {
|
|
25
|
+
normalizeCapabilities,
|
|
26
|
+
diffCapabilities,
|
|
27
|
+
describeCapabilities,
|
|
28
|
+
} from "./capabilities.js";
|
|
29
|
+
|
|
30
|
+
export const _deps = {
|
|
31
|
+
existsSync: fs.existsSync,
|
|
32
|
+
readFileSync: fs.readFileSync,
|
|
33
|
+
writeFileSync: fs.writeFileSync,
|
|
34
|
+
mkdirSync: fs.mkdirSync,
|
|
35
|
+
now: () => new Date().toISOString(),
|
|
36
|
+
// Injectable so unit tests never touch the real user-data dir.
|
|
37
|
+
storePath: () =>
|
|
38
|
+
path.join(getElectronUserDataDir(), "plugin-capability-consent.json"),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function consentKey(scope, name) {
|
|
42
|
+
return `${scope}:${name}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function loadConsentStore() {
|
|
46
|
+
try {
|
|
47
|
+
const p = _deps.storePath();
|
|
48
|
+
if (!_deps.existsSync(p)) return {};
|
|
49
|
+
const data = JSON.parse(_deps.readFileSync(p, "utf8"));
|
|
50
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
51
|
+
} catch {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function saveConsentStore(store) {
|
|
57
|
+
const p = _deps.storePath();
|
|
58
|
+
_deps.mkdirSync(path.dirname(p), { recursive: true });
|
|
59
|
+
_deps.writeFileSync(p, JSON.stringify(store, null, 2), "utf8");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Coerce a capability set to the canonical shape diffCapabilities needs.
|
|
64
|
+
* `normalizeCapabilities` is NOT idempotent — it re-parses `filesystem`/
|
|
65
|
+
* `credential` through `toList`, so feeding it an already-normalized set (e.g.
|
|
66
|
+
* one read back from the store) corrupts those lists. Detect an already-
|
|
67
|
+
* canonical set and pass it through unchanged; only normalize raw input.
|
|
68
|
+
*/
|
|
69
|
+
function canonicalCaps(caps) {
|
|
70
|
+
if (
|
|
71
|
+
caps &&
|
|
72
|
+
typeof caps === "object" &&
|
|
73
|
+
caps.network &&
|
|
74
|
+
typeof caps.network === "object" &&
|
|
75
|
+
"domains" in caps.network &&
|
|
76
|
+
caps.filesystem &&
|
|
77
|
+
"roots" in caps.filesystem &&
|
|
78
|
+
caps.credential &&
|
|
79
|
+
"names" in caps.credential
|
|
80
|
+
) {
|
|
81
|
+
return caps;
|
|
82
|
+
}
|
|
83
|
+
return normalizeCapabilities(caps);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** True when a capability set grants nothing (all-deny) — nothing to consent. */
|
|
87
|
+
export function capabilitiesAreEmpty(caps) {
|
|
88
|
+
return !diffCapabilities(null, canonicalCaps(caps)).widened;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* PURE consent status: given a plugin's DECLARED capabilities and its stored
|
|
93
|
+
* consent entry, decide whether consent covers them. Re-consent is required on
|
|
94
|
+
* a first request or any WIDENING (new grant) — not on a version bump alone
|
|
95
|
+
* (trust handles versions).
|
|
96
|
+
*
|
|
97
|
+
* @param {object} declaredCaps raw or normalized capabilities
|
|
98
|
+
* @param {object|null} entry stored `{ version, capabilities }` (or null)
|
|
99
|
+
* @returns {{consented:boolean, reason:string, added:string[]}}
|
|
100
|
+
*/
|
|
101
|
+
export function capabilityConsentStatus(declaredCaps, entry) {
|
|
102
|
+
const declared = canonicalCaps(declaredCaps);
|
|
103
|
+
if (!diffCapabilities(null, declared).widened) {
|
|
104
|
+
return { consented: true, reason: "no capabilities declared", added: [] };
|
|
105
|
+
}
|
|
106
|
+
if (!entry) {
|
|
107
|
+
return {
|
|
108
|
+
consented: false,
|
|
109
|
+
reason: "capabilities never consented",
|
|
110
|
+
added: diffCapabilities(null, declared).added,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Coerce the stored set to canonical too (raw hand-written entry, or an
|
|
114
|
+
// already-normalized one read from the store) before diffing.
|
|
115
|
+
const diff = diffCapabilities(canonicalCaps(entry.capabilities), declared);
|
|
116
|
+
if (diff.widened) {
|
|
117
|
+
return {
|
|
118
|
+
consented: false,
|
|
119
|
+
reason: "capabilities widened since last consent",
|
|
120
|
+
added: diff.added,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
consented: true,
|
|
125
|
+
reason: "consent covers declared capabilities",
|
|
126
|
+
added: [],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Decide how an install/upgrade command should handle capability consent at the
|
|
132
|
+
* point of installation (pure — no I/O). `notice` is the shape returned by the
|
|
133
|
+
* command layer's capability resolver: `{ consented, added, ... }` or null when
|
|
134
|
+
* the plugin declares no capabilities.
|
|
135
|
+
*
|
|
136
|
+
* Returns one of:
|
|
137
|
+
* - "advisory" — nothing to consent (no caps, or already consented), OR the
|
|
138
|
+
* caller is non-interactive and did not pass an explicit grant flag: print
|
|
139
|
+
* the notice and require a later `cc plugin consent --grant`.
|
|
140
|
+
* - "grant" — an explicit grant flag was given: record consent immediately
|
|
141
|
+
* (scriptable / CI path).
|
|
142
|
+
* - "prompt" — consent is required and the session is interactive: ask the
|
|
143
|
+
* user, then grant on confirmation.
|
|
144
|
+
*
|
|
145
|
+
* Precedence: an explicit grant flag always wins over an interactive prompt, so
|
|
146
|
+
* `--grant-capabilities` is honored even under a TTY without a second question.
|
|
147
|
+
*/
|
|
148
|
+
export function resolveConsentAction(
|
|
149
|
+
notice,
|
|
150
|
+
{ grant = false, interactive = false } = {},
|
|
151
|
+
) {
|
|
152
|
+
if (!notice || notice.consented) return "advisory";
|
|
153
|
+
if (grant) return "grant";
|
|
154
|
+
if (interactive) return "prompt";
|
|
155
|
+
return "advisory";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Load the store and report whether one plugin's declared caps are consented. */
|
|
159
|
+
export function isPluginCapabilityConsented(plugin, declaredCaps) {
|
|
160
|
+
if (!plugin || !plugin.name) return false;
|
|
161
|
+
const entry = loadConsentStore()[consentKey(plugin.scope, plugin.name)];
|
|
162
|
+
return capabilityConsentStatus(declaredCaps, entry).consented;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Record consent for a plugin's currently-declared capability set. */
|
|
166
|
+
export function consentPluginCapabilities(
|
|
167
|
+
name,
|
|
168
|
+
{ scope = "project", version, capabilities } = {},
|
|
169
|
+
) {
|
|
170
|
+
if (!version) throw new Error("consentPluginCapabilities requires a version");
|
|
171
|
+
const store = loadConsentStore();
|
|
172
|
+
store[consentKey(scope, name)] = {
|
|
173
|
+
version,
|
|
174
|
+
capabilities: canonicalCaps(capabilities),
|
|
175
|
+
consentedAt: _deps.now(),
|
|
176
|
+
};
|
|
177
|
+
saveConsentStore(store);
|
|
178
|
+
return { name, scope, version };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Revoke capability consent for a plugin at a scope. */
|
|
182
|
+
export function revokeCapabilityConsent(name, { scope = "project" } = {}) {
|
|
183
|
+
const store = loadConsentStore();
|
|
184
|
+
const key = consentKey(scope, name);
|
|
185
|
+
const existed = Object.prototype.hasOwnProperty.call(store, key);
|
|
186
|
+
delete store[key];
|
|
187
|
+
saveConsentStore(store);
|
|
188
|
+
return { name, scope, removed: existed };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** All consent entries (for `cc plugin consent --list`). */
|
|
192
|
+
export function listCapabilityConsent() {
|
|
193
|
+
return Object.entries(loadConsentStore()).map(([key, v]) => {
|
|
194
|
+
const idx = key.indexOf(":");
|
|
195
|
+
return {
|
|
196
|
+
scope: key.slice(0, idx),
|
|
197
|
+
name: key.slice(idx + 1),
|
|
198
|
+
version: v?.version || null,
|
|
199
|
+
consentedAt: v?.consentedAt || null,
|
|
200
|
+
capabilities: describeCapabilities(v?.capabilities),
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
}
|
|
@@ -17,9 +17,36 @@
|
|
|
17
17
|
import fs from "fs";
|
|
18
18
|
import { discoverPlugins } from "./scopes.js";
|
|
19
19
|
import { partitionByTrust, warnUntrustedOnce } from "./trust.js";
|
|
20
|
+
import { componentCapabilityDenial } from "./capabilities.js";
|
|
20
21
|
|
|
21
22
|
export const _deps = { readFileSync: fs.readFileSync };
|
|
22
23
|
|
|
24
|
+
// One-time stderr notice when a plugin's hooks are refused at the COMPONENT
|
|
25
|
+
// level because the plugin opted into the capability model but did not declare
|
|
26
|
+
// the `process` capability its shell hooks need. Distinct from the trust gate:
|
|
27
|
+
// these plugins ARE trusted, but their hook component is denied.
|
|
28
|
+
const _capabilityDenied = new Set();
|
|
29
|
+
function warnHookCapabilityDeniedOnce(entries) {
|
|
30
|
+
if (!entries || entries.length === 0) return;
|
|
31
|
+
if (_capabilityDenied.has("hook-capability")) return;
|
|
32
|
+
_capabilityDenied.add("hook-capability");
|
|
33
|
+
const list = entries.map((e) => `${e.name} (${e.reason})`).join("; ");
|
|
34
|
+
try {
|
|
35
|
+
process.stderr.write(
|
|
36
|
+
`[plugins] refused hook(s) from plugin(s) that declared a permissions ` +
|
|
37
|
+
`block but did not declare the 'process' capability their hooks need: ${list}\n` +
|
|
38
|
+
` add 'process' to the plugin's permissions block to enable them.\n`,
|
|
39
|
+
);
|
|
40
|
+
} catch {
|
|
41
|
+
/* stderr notice is best-effort */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Test hook: reset the one-time capability-denied warning guard. */
|
|
46
|
+
export function _resetHookWarnings() {
|
|
47
|
+
_capabilityDenied.clear();
|
|
48
|
+
}
|
|
49
|
+
|
|
23
50
|
/** Accept either `{ hooks: {Event:[...]} }` (plugin wrap) or `{Event:[...]}`. */
|
|
24
51
|
function normalizeHookMap(parsed) {
|
|
25
52
|
if (
|
|
@@ -57,10 +84,19 @@ export function collectPluginHooks(opts = {}) {
|
|
|
57
84
|
"hooks",
|
|
58
85
|
);
|
|
59
86
|
const merged = {};
|
|
87
|
+
const denied = [];
|
|
60
88
|
for (const p of trusted) {
|
|
61
89
|
if (!p.manifest || p.manifest.ok !== true) continue;
|
|
62
90
|
const h = p.manifest.components?.hooks;
|
|
63
91
|
if (!h) continue;
|
|
92
|
+
// Component-level capability gate: a plugin that declared a permissions
|
|
93
|
+
// block but under-declared the `process` capability its shell hooks need
|
|
94
|
+
// gets its hooks refused here (mirrors the MCP collector's denial).
|
|
95
|
+
const denial = componentCapabilityDenial(p.manifest, ["process"]);
|
|
96
|
+
if (denial) {
|
|
97
|
+
denied.push({ name: p.name, reason: denial.reason });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
64
100
|
let parsed;
|
|
65
101
|
if (h.absPath) {
|
|
66
102
|
try {
|
|
@@ -89,6 +125,7 @@ export function collectPluginHooks(opts = {}) {
|
|
|
89
125
|
merged[event] = (merged[event] || []).concat(entries);
|
|
90
126
|
}
|
|
91
127
|
}
|
|
128
|
+
warnHookCapabilityDeniedOnce(denied);
|
|
92
129
|
return merged;
|
|
93
130
|
}
|
|
94
131
|
|
|
@@ -16,9 +16,31 @@ import path from "path";
|
|
|
16
16
|
import { discoverPlugins } from "./scopes.js";
|
|
17
17
|
import { registerLanguageServer } from "../lsp/lsp-server-registry.js";
|
|
18
18
|
import { partitionByTrust, warnUntrustedOnce } from "./trust.js";
|
|
19
|
+
import { componentCapabilityDenial } from "./capabilities.js";
|
|
19
20
|
|
|
20
21
|
const _loadedRoots = new Set();
|
|
21
22
|
|
|
23
|
+
// One-time stderr notice when a plugin's LSP servers are refused at the
|
|
24
|
+
// COMPONENT level because the plugin opted into the capability model but did not
|
|
25
|
+
// declare the `process` capability the servers need. Distinct from the trust
|
|
26
|
+
// gate: these plugins ARE trusted, but their LSP component is denied.
|
|
27
|
+
const _capabilityDenied = new Set();
|
|
28
|
+
function warnLspCapabilityDeniedOnce(entries) {
|
|
29
|
+
if (!entries || entries.length === 0) return;
|
|
30
|
+
if (_capabilityDenied.has("lsp-capability")) return;
|
|
31
|
+
_capabilityDenied.add("lsp-capability");
|
|
32
|
+
const list = entries.map((e) => `${e.name} (${e.reason})`).join("; ");
|
|
33
|
+
try {
|
|
34
|
+
process.stderr.write(
|
|
35
|
+
`[plugins] refused language server(s) from plugin(s) that declared a ` +
|
|
36
|
+
`permissions block but did not declare the 'process' capability: ${list}\n` +
|
|
37
|
+
` add 'process' to the plugin's permissions block to enable them.\n`,
|
|
38
|
+
);
|
|
39
|
+
} catch {
|
|
40
|
+
/* stderr notice is best-effort */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
/**
|
|
23
45
|
* Register every installed plugin's LSP servers for `cwd` (once per root).
|
|
24
46
|
* Never throws — a broken plugin manifest must not break code navigation.
|
|
@@ -49,8 +71,18 @@ export function ensurePluginLspServers(opts = {}) {
|
|
|
49
71
|
"language servers",
|
|
50
72
|
);
|
|
51
73
|
|
|
74
|
+
const denied = [];
|
|
52
75
|
for (const p of trusted) {
|
|
53
76
|
const servers = p.manifest?.components?.lsp || [];
|
|
77
|
+
if (servers.length === 0) continue;
|
|
78
|
+
// Component-level capability gate: a plugin that declared a permissions
|
|
79
|
+
// block but did not declare the `process` capability its language servers
|
|
80
|
+
// need gets them refused here (mirrors the MCP collector's denial).
|
|
81
|
+
const denial = componentCapabilityDenial(p.manifest, ["process"]);
|
|
82
|
+
if (denial) {
|
|
83
|
+
denied.push({ name: p.name, reason: denial.reason });
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
54
86
|
for (const s of servers) {
|
|
55
87
|
try {
|
|
56
88
|
registerLanguageServer({
|
|
@@ -66,6 +98,7 @@ export function ensurePluginLspServers(opts = {}) {
|
|
|
66
98
|
}
|
|
67
99
|
}
|
|
68
100
|
}
|
|
101
|
+
warnLspCapabilityDeniedOnce(denied);
|
|
69
102
|
return { registered };
|
|
70
103
|
}
|
|
71
104
|
|
|
@@ -73,3 +106,8 @@ export function ensurePluginLspServers(opts = {}) {
|
|
|
73
106
|
export function _resetPluginLspLoadState() {
|
|
74
107
|
_loadedRoots.clear();
|
|
75
108
|
}
|
|
109
|
+
|
|
110
|
+
/** Test hook: reset the one-time capability-denied warning guard. */
|
|
111
|
+
export function _resetLspWarnings() {
|
|
112
|
+
_capabilityDenied.clear();
|
|
113
|
+
}
|
|
@@ -17,9 +17,38 @@
|
|
|
17
17
|
import fs from "fs";
|
|
18
18
|
import { discoverPlugins } from "./scopes.js";
|
|
19
19
|
import { partitionByTrust, warnUntrustedOnce } from "./trust.js";
|
|
20
|
+
import { componentCapabilityDenial } from "./capabilities.js";
|
|
20
21
|
|
|
21
22
|
export const _deps = { readFileSync: fs.readFileSync };
|
|
22
23
|
|
|
24
|
+
// One-time stderr notice when a plugin's MCP server is refused at the COMPONENT
|
|
25
|
+
// level because the plugin opted into the capability model but did not declare
|
|
26
|
+
// the `mcp` capability its server needs. Distinct from the trust gate: these
|
|
27
|
+
// plugins ARE trusted, but their MCP component is denied. (Network is enforced
|
|
28
|
+
// separately at connection time by the egress/network policy, so a local stdio
|
|
29
|
+
// server is not denied for lacking a `network` declaration.)
|
|
30
|
+
const _capabilityDenied = new Set();
|
|
31
|
+
function warnMcpCapabilityDeniedOnce(entries) {
|
|
32
|
+
if (!entries || entries.length === 0) return;
|
|
33
|
+
if (_capabilityDenied.has("mcp-capability")) return;
|
|
34
|
+
_capabilityDenied.add("mcp-capability");
|
|
35
|
+
const list = entries.map((e) => `${e.name} (${e.reason})`).join("; ");
|
|
36
|
+
try {
|
|
37
|
+
process.stderr.write(
|
|
38
|
+
`[plugins] refused MCP server(s) from plugin(s) that declared a permissions ` +
|
|
39
|
+
`block but did not declare the 'mcp' capability their server needs: ${list}\n` +
|
|
40
|
+
` add 'mcp' to the plugin's permissions block to enable them.\n`,
|
|
41
|
+
);
|
|
42
|
+
} catch {
|
|
43
|
+
/* stderr notice is best-effort */
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Test hook: reset the one-time capability-denied warning guard. */
|
|
48
|
+
export function _resetMcpWarnings() {
|
|
49
|
+
_capabilityDenied.clear();
|
|
50
|
+
}
|
|
51
|
+
|
|
23
52
|
/** Accept `{ mcpServers: {...} }`, `{ servers: {...} }`, or a bare map. */
|
|
24
53
|
function normalizeServerMap(parsed) {
|
|
25
54
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -52,10 +81,20 @@ export function collectPluginMcpServers(opts = {}) {
|
|
|
52
81
|
);
|
|
53
82
|
const servers = {};
|
|
54
83
|
const sources = [];
|
|
84
|
+
const denied = [];
|
|
55
85
|
for (const p of trusted) {
|
|
56
86
|
if (!p.manifest || p.manifest.ok !== true) continue;
|
|
57
87
|
const m = p.manifest.components?.mcp;
|
|
58
88
|
if (!m) continue;
|
|
89
|
+
// Component-level capability gate: a plugin that declared a permissions
|
|
90
|
+
// block but under-declared the `mcp` capability its server needs gets its
|
|
91
|
+
// MCP component refused here (mirrors the hooks/monitors/bin/lsp denial).
|
|
92
|
+
// Legacy plugins (no permissions block) are unrestricted and never denied.
|
|
93
|
+
const denial = componentCapabilityDenial(p.manifest, ["mcp"]);
|
|
94
|
+
if (denial) {
|
|
95
|
+
denied.push({ name: p.name, reason: denial.reason });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
59
98
|
let parsed = null;
|
|
60
99
|
if (m.absPath) {
|
|
61
100
|
try {
|
|
@@ -85,5 +124,6 @@ export function collectPluginMcpServers(opts = {}) {
|
|
|
85
124
|
}
|
|
86
125
|
if (added > 0 && m.absPath) sources.push(m.absPath);
|
|
87
126
|
}
|
|
127
|
+
warnMcpCapabilityDeniedOnce(denied);
|
|
88
128
|
return { servers, sources };
|
|
89
129
|
}
|
|
@@ -21,11 +21,38 @@
|
|
|
21
21
|
import fs from "fs";
|
|
22
22
|
import { discoverPlugins } from "./scopes.js";
|
|
23
23
|
import { partitionByTrust, warnUntrustedOnce } from "./trust.js";
|
|
24
|
+
import { componentCapabilityDenial } from "./capabilities.js";
|
|
24
25
|
|
|
25
26
|
export const _deps = { readFileSync: fs.readFileSync };
|
|
26
27
|
|
|
27
28
|
const VALID_MODES = new Set(["interval", "longRunning"]);
|
|
28
29
|
|
|
30
|
+
// One-time stderr notice when a plugin's monitors are refused at the COMPONENT
|
|
31
|
+
// level because the plugin opted into the capability model but did not declare
|
|
32
|
+
// the `monitor` capability. Distinct from the trust gate: these plugins ARE
|
|
33
|
+
// trusted, but their monitor component is denied.
|
|
34
|
+
const _capabilityDenied = new Set();
|
|
35
|
+
function warnMonitorCapabilityDeniedOnce(entries) {
|
|
36
|
+
if (!entries || entries.length === 0) return;
|
|
37
|
+
if (_capabilityDenied.has("monitor-capability")) return;
|
|
38
|
+
_capabilityDenied.add("monitor-capability");
|
|
39
|
+
const list = entries.map((e) => `${e.name} (${e.reason})`).join("; ");
|
|
40
|
+
try {
|
|
41
|
+
process.stderr.write(
|
|
42
|
+
`[plugins] refused monitor(s) from plugin(s) that declared a permissions ` +
|
|
43
|
+
`block but did not declare the 'monitor' capability: ${list}\n` +
|
|
44
|
+
` add 'monitor' to the plugin's permissions block to enable them.\n`,
|
|
45
|
+
);
|
|
46
|
+
} catch {
|
|
47
|
+
/* stderr notice is best-effort */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Test hook: reset the one-time capability-denied warning guard. */
|
|
52
|
+
export function _resetMonitorWarnings() {
|
|
53
|
+
_capabilityDenied.clear();
|
|
54
|
+
}
|
|
55
|
+
|
|
29
56
|
/** Normalize one raw monitor entry into a validated descriptor, or null. */
|
|
30
57
|
function normalizeMonitor(plugin, raw) {
|
|
31
58
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -76,10 +103,19 @@ export function collectPluginMonitors(opts = {}) {
|
|
|
76
103
|
"monitors",
|
|
77
104
|
);
|
|
78
105
|
const out = [];
|
|
106
|
+
const denied = [];
|
|
79
107
|
for (const p of trusted) {
|
|
80
108
|
if (!p.manifest || p.manifest.ok !== true) continue;
|
|
81
109
|
const m = p.manifest.components?.monitors;
|
|
82
110
|
if (!m) continue;
|
|
111
|
+
// Component-level capability gate: a plugin that declared a permissions
|
|
112
|
+
// block but did not declare the `monitor` capability gets its monitors
|
|
113
|
+
// refused here (mirrors the MCP collector's denial).
|
|
114
|
+
const denial = componentCapabilityDenial(p.manifest, ["monitor"]);
|
|
115
|
+
if (denial) {
|
|
116
|
+
denied.push({ name: p.name, reason: denial.reason });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
83
119
|
let parsed;
|
|
84
120
|
if (m.absPath) {
|
|
85
121
|
try {
|
|
@@ -112,5 +148,6 @@ export function collectPluginMonitors(opts = {}) {
|
|
|
112
148
|
if (desc) out.push(desc);
|
|
113
149
|
}
|
|
114
150
|
}
|
|
151
|
+
warnMonitorCapabilityDeniedOnce(denied);
|
|
115
152
|
return out;
|
|
116
153
|
}
|