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
|
@@ -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
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin OPTIONS resolution (Phase 3 gap: typed optionsSchema + sensitive
|
|
3
|
+
* project-scope gate).
|
|
4
|
+
*
|
|
5
|
+
* A plugin declares `optionsSchema` (typed config with `scope` and `sensitive`
|
|
6
|
+
* in [[capabilities.js]]). Users supply option VALUES at two scopes:
|
|
7
|
+
* - USER scope → user data dir `plugin-options.json` (may hold secrets)
|
|
8
|
+
* - PROJECT scope → repo-local `.chainlesschain/plugin-options.json`
|
|
9
|
+
* (checked-in-able → MUST NOT hold secrets)
|
|
10
|
+
*
|
|
11
|
+
* The security-critical rule (already enforced by `validateOptions`): a
|
|
12
|
+
* SENSITIVE option can never come from project config. `resolvePluginOptions`
|
|
13
|
+
* layers defaults < project (non-sensitive only) < user, DROPS + WARNS on any
|
|
14
|
+
* sensitive/user-only option that appears in project config, and returns the
|
|
15
|
+
* merged values with a redacted view for logging.
|
|
16
|
+
*
|
|
17
|
+
* Store IO is injected (`_deps`) so unit tests never touch the real dirs; the
|
|
18
|
+
* resolver itself is pure.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
import { getElectronUserDataDir } from "../paths.js";
|
|
24
|
+
import {
|
|
25
|
+
optionDefaults,
|
|
26
|
+
validateOptions,
|
|
27
|
+
redactSensitiveOptions,
|
|
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
|
+
userStorePath: () =>
|
|
36
|
+
path.join(getElectronUserDataDir(), "plugin-options.json"),
|
|
37
|
+
projectStorePath: (cwd) =>
|
|
38
|
+
path.join(cwd || process.cwd(), ".chainlesschain", "plugin-options.json"),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function readJsonObject(p) {
|
|
42
|
+
try {
|
|
43
|
+
if (!_deps.existsSync(p)) return {};
|
|
44
|
+
const data = JSON.parse(_deps.readFileSync(p, "utf8"));
|
|
45
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
46
|
+
} catch {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeJsonObject(p, obj) {
|
|
52
|
+
_deps.mkdirSync(path.dirname(p), { recursive: true });
|
|
53
|
+
_deps.writeFileSync(p, JSON.stringify(obj, null, 2), "utf8");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Load a plugin's option VALUES at the given scope (`user` | `project`). */
|
|
57
|
+
export function loadPluginOptionValues(name, scope, { cwd } = {}) {
|
|
58
|
+
const p =
|
|
59
|
+
scope === "project" ? _deps.projectStorePath(cwd) : _deps.userStorePath();
|
|
60
|
+
const store = readJsonObject(p);
|
|
61
|
+
const v = store[name];
|
|
62
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Persist a plugin's option VALUES at a scope (replaces the plugin's entry). */
|
|
66
|
+
export function setPluginOptionValues(name, values, scope, { cwd } = {}) {
|
|
67
|
+
const p =
|
|
68
|
+
scope === "project" ? _deps.projectStorePath(cwd) : _deps.userStorePath();
|
|
69
|
+
const store = readJsonObject(p);
|
|
70
|
+
store[name] = values && typeof values === "object" ? values : {};
|
|
71
|
+
writeJsonObject(p, store);
|
|
72
|
+
return { name, scope, path: p };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Provided keys of `values` (that exist in schema) accepted at `scope`. */
|
|
76
|
+
function acceptedKeys(schema, values, scope) {
|
|
77
|
+
const res = validateOptions(schema, values, { scope });
|
|
78
|
+
const keys = new Set();
|
|
79
|
+
for (const key of Object.keys(values || {})) {
|
|
80
|
+
if (!Object.prototype.hasOwnProperty.call(schema, key)) continue; // unknown → ignored
|
|
81
|
+
// A provided key was accepted iff no error names it. validateOptions always
|
|
82
|
+
// quotes the offending key as `"<key>"`, so this is an exact match.
|
|
83
|
+
if (!res.errors.some((e) => e.includes(`"${key}"`))) keys.add(key);
|
|
84
|
+
}
|
|
85
|
+
return { res, keys };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* PURE: resolve effective option values from defaults + project + user config.
|
|
90
|
+
*
|
|
91
|
+
* Precedence: defaults < project (non-sensitive, non-user-only) < user.
|
|
92
|
+
* A sensitive or user-only option present in project config is DROPPED and a
|
|
93
|
+
* warning is recorded — a checked-in project file can never inject a secret.
|
|
94
|
+
*
|
|
95
|
+
* @returns {{ options:object, redacted:object, warnings:string[],
|
|
96
|
+
* sources:Record<string,'default'|'project'|'user'>,
|
|
97
|
+
* droppedFromProject:string[] }}
|
|
98
|
+
*/
|
|
99
|
+
export function resolvePluginOptions(
|
|
100
|
+
schema,
|
|
101
|
+
{ userValues, projectValues } = {},
|
|
102
|
+
) {
|
|
103
|
+
const s = schema || {};
|
|
104
|
+
const options = optionDefaults(s);
|
|
105
|
+
const sources = {};
|
|
106
|
+
for (const key of Object.keys(options)) sources[key] = "default";
|
|
107
|
+
|
|
108
|
+
const warnings = [];
|
|
109
|
+
const droppedFromProject = [];
|
|
110
|
+
|
|
111
|
+
// Project layer first (lower precedence). validateOptions errors on sensitive/
|
|
112
|
+
// user-only keys at project scope — those are the drops we surface + refuse.
|
|
113
|
+
const proj = acceptedKeys(s, projectValues, "project");
|
|
114
|
+
for (const err of proj.res.errors) {
|
|
115
|
+
if (/sensitive|user-scoped/.test(err)) {
|
|
116
|
+
const m = err.match(/"([^"]+)"/);
|
|
117
|
+
if (m) droppedFromProject.push(m[1]);
|
|
118
|
+
}
|
|
119
|
+
warnings.push(`project: ${err}`);
|
|
120
|
+
}
|
|
121
|
+
for (const w of proj.res.warnings) warnings.push(`project: ${w}`);
|
|
122
|
+
for (const key of proj.keys) {
|
|
123
|
+
options[key] = proj.res.normalized[key];
|
|
124
|
+
sources[key] = "project";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// User layer on top (highest precedence; may carry secrets).
|
|
128
|
+
const user = acceptedKeys(s, userValues, "user");
|
|
129
|
+
for (const err of user.res.errors) warnings.push(`user: ${err}`);
|
|
130
|
+
for (const w of user.res.warnings) warnings.push(`user: ${w}`);
|
|
131
|
+
for (const key of user.keys) {
|
|
132
|
+
options[key] = user.res.normalized[key];
|
|
133
|
+
sources[key] = "user";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
options,
|
|
138
|
+
redacted: redactSensitiveOptions(s, options),
|
|
139
|
+
warnings,
|
|
140
|
+
sources,
|
|
141
|
+
droppedFromProject: [...new Set(droppedFromProject)],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Load both scopes' values from disk and resolve against a schema. */
|
|
146
|
+
export function getResolvedPluginOptions(name, schema, { cwd } = {}) {
|
|
147
|
+
return resolvePluginOptions(schema, {
|
|
148
|
+
userValues: loadPluginOptionValues(name, "user", { cwd }),
|
|
149
|
+
projectValues: loadPluginOptionValues(name, "project", { cwd }),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR/CI automation policy — the fail-closed decision engine behind "P1-4 PR/CI
|
|
3
|
+
* Monitor、Auto-fix 与受控合并" of CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-
|
|
4
|
+
* 07-13.md.
|
|
5
|
+
*
|
|
6
|
+
* Letting an agent iterate on CI failures and merge PRs is useful and dangerous.
|
|
7
|
+
* The gap doc pins the guardrails precisely: each failing check may be auto-
|
|
8
|
+
* fixed at most N times within a token/time budget; a fix must read only the
|
|
9
|
+
* CURRENT commit's logs (never chase a stale failure); auto-merge is OFF by
|
|
10
|
+
* default and, when on, requires branch protection, all required checks, an
|
|
11
|
+
* approving review and zero pending approvals — and never pushes a protected
|
|
12
|
+
* branch directly or bypasses protection.
|
|
13
|
+
*
|
|
14
|
+
* This module is those decisions as pure predicates. It holds no state and does
|
|
15
|
+
* no I/O — a caller feeds the observed PR/CI signals and gets an allow/deny with
|
|
16
|
+
* a named reason. Everything defaults to DENY: an unknown or missing signal
|
|
17
|
+
* blocks the automation rather than assuming it is safe.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Canonical CI check states. */
|
|
21
|
+
export const CHECK_STATE = Object.freeze({
|
|
22
|
+
PASSED: "passed",
|
|
23
|
+
FAILED: "failed",
|
|
24
|
+
PENDING: "pending",
|
|
25
|
+
NEUTRAL: "neutral",
|
|
26
|
+
ERROR: "error",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const CHECK_ALIASES = new Map([
|
|
30
|
+
["passed", CHECK_STATE.PASSED],
|
|
31
|
+
["pass", CHECK_STATE.PASSED],
|
|
32
|
+
["success", CHECK_STATE.PASSED],
|
|
33
|
+
["succeeded", CHECK_STATE.PASSED],
|
|
34
|
+
["green", CHECK_STATE.PASSED],
|
|
35
|
+
["failed", CHECK_STATE.FAILED],
|
|
36
|
+
["fail", CHECK_STATE.FAILED],
|
|
37
|
+
["failure", CHECK_STATE.FAILED],
|
|
38
|
+
["red", CHECK_STATE.FAILED],
|
|
39
|
+
["pending", CHECK_STATE.PENDING],
|
|
40
|
+
["queued", CHECK_STATE.PENDING],
|
|
41
|
+
["in_progress", CHECK_STATE.PENDING],
|
|
42
|
+
["running", CHECK_STATE.PENDING],
|
|
43
|
+
["neutral", CHECK_STATE.NEUTRAL],
|
|
44
|
+
["skipped", CHECK_STATE.NEUTRAL],
|
|
45
|
+
["cancelled", CHECK_STATE.NEUTRAL],
|
|
46
|
+
["canceled", CHECK_STATE.NEUTRAL],
|
|
47
|
+
["error", CHECK_STATE.ERROR],
|
|
48
|
+
["timed_out", CHECK_STATE.ERROR],
|
|
49
|
+
["action_required", CHECK_STATE.ERROR],
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
/** Normalize any provider's check conclusion/status to a canonical state. */
|
|
53
|
+
export function normalizeCheckState(value) {
|
|
54
|
+
if (typeof value !== "string") return null;
|
|
55
|
+
return CHECK_ALIASES.get(value.trim().toLowerCase()) || null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A failing state that auto-fix could act on. */
|
|
59
|
+
function isFailing(state) {
|
|
60
|
+
return state === CHECK_STATE.FAILED || state === CHECK_STATE.ERROR;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Summarize a set of checks. `allPassed` is true only when there is at least one
|
|
65
|
+
* check and every one is passed or neutral (a bare PR with no checks is NOT
|
|
66
|
+
* "all passed" — required checks must actually exist and run).
|
|
67
|
+
*
|
|
68
|
+
* @param {Array<{name?:string, state?:string, conclusion?:string, status?:string}>} checks
|
|
69
|
+
*/
|
|
70
|
+
export function summarizeChecks(checks = []) {
|
|
71
|
+
const list = Array.isArray(checks) ? checks : [];
|
|
72
|
+
let passed = 0;
|
|
73
|
+
let failed = 0;
|
|
74
|
+
let pending = 0;
|
|
75
|
+
const byName = new Map();
|
|
76
|
+
for (const c of list) {
|
|
77
|
+
const state =
|
|
78
|
+
normalizeCheckState(c?.state) ??
|
|
79
|
+
normalizeCheckState(c?.conclusion) ??
|
|
80
|
+
normalizeCheckState(c?.status);
|
|
81
|
+
if (c?.name != null) byName.set(String(c.name), state);
|
|
82
|
+
if (state === CHECK_STATE.PASSED || state === CHECK_STATE.NEUTRAL) passed++;
|
|
83
|
+
else if (isFailing(state)) failed++;
|
|
84
|
+
else pending++; // pending / unknown → not yet decided
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
total: list.length,
|
|
88
|
+
passed,
|
|
89
|
+
failed,
|
|
90
|
+
pending,
|
|
91
|
+
anyFailed: failed > 0,
|
|
92
|
+
allPassed: list.length > 0 && failed === 0 && pending === 0,
|
|
93
|
+
byName,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function budgetExhausted(budget) {
|
|
98
|
+
if (!budget || typeof budget !== "object") return false;
|
|
99
|
+
const over = (spent, cap) =>
|
|
100
|
+
cap != null && Number.isFinite(Number(cap)) && Number(spent) >= Number(cap);
|
|
101
|
+
return (
|
|
102
|
+
over(budget.tokensSpent, budget.tokenBudget) ||
|
|
103
|
+
over(budget.timeSpentMs, budget.timeBudgetMs)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Decide whether an agent may auto-fix ONE failing check right now. Fail-closed:
|
|
109
|
+
* anything unproven denies.
|
|
110
|
+
*
|
|
111
|
+
* @param {object} params
|
|
112
|
+
* @param {object} params.check {name, state|conclusion, logCommitSha}
|
|
113
|
+
* @param {string} params.headCommitSha the PR head the fix would target
|
|
114
|
+
* @param {number} params.iteration fixes already attempted for this check
|
|
115
|
+
* @param {number} params.maxIterations cap (default 3)
|
|
116
|
+
* @param {object} [params.budget] {tokensSpent, tokenBudget, timeSpentMs, timeBudgetMs}
|
|
117
|
+
* @returns {{allow:boolean, reason:string}}
|
|
118
|
+
*/
|
|
119
|
+
export function autoFixDecision(params = {}) {
|
|
120
|
+
const check = params.check || {};
|
|
121
|
+
const state =
|
|
122
|
+
normalizeCheckState(check.state) ?? normalizeCheckState(check.conclusion);
|
|
123
|
+
if (!isFailing(state)) {
|
|
124
|
+
return { allow: false, reason: "check-not-failing" };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const maxIterations = Number.isFinite(Number(params.maxIterations))
|
|
128
|
+
? Number(params.maxIterations)
|
|
129
|
+
: 3;
|
|
130
|
+
const iteration = Number(params.iteration) || 0;
|
|
131
|
+
if (iteration >= maxIterations) {
|
|
132
|
+
return { allow: false, reason: "max-iterations-reached" };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Only fix the CURRENT failure: the log we would read must belong to the head
|
|
136
|
+
// commit. A missing/older log sha means we cannot prove freshness → deny.
|
|
137
|
+
const logSha = check.logCommitSha == null ? "" : String(check.logCommitSha);
|
|
138
|
+
const headSha =
|
|
139
|
+
params.headCommitSha == null ? "" : String(params.headCommitSha);
|
|
140
|
+
if (!logSha || !headSha) {
|
|
141
|
+
return { allow: false, reason: "commit-sha-unverifiable" };
|
|
142
|
+
}
|
|
143
|
+
if (logSha !== headSha) {
|
|
144
|
+
return { allow: false, reason: "stale-failure-log" };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (budgetExhausted(params.budget)) {
|
|
148
|
+
return { allow: false, reason: "budget-exhausted" };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { allow: true, reason: "ok" };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Decide whether a PR may be auto-merged. Fail-closed and exhaustive: every
|
|
156
|
+
* unmet requirement is collected, and merge is allowed ONLY when the list is
|
|
157
|
+
* empty. Auto-merge is off unless `enabled === true`.
|
|
158
|
+
*
|
|
159
|
+
* @param {object} params
|
|
160
|
+
* @param {boolean} params.enabled auto-merge explicitly turned on
|
|
161
|
+
* @param {boolean} params.hasOpenPr a real PR exists (no direct-to-branch)
|
|
162
|
+
* @param {boolean} params.branchProtectionSatisfied
|
|
163
|
+
* @param {boolean} params.reviewApproved
|
|
164
|
+
* @param {number} params.pendingApprovals permission approvals still waiting
|
|
165
|
+
* @param {string[]} params.requiredChecks names that must be present AND passed
|
|
166
|
+
* @param {Array} params.checks observed checks (summarizeChecks input)
|
|
167
|
+
* @param {string} [params.targetBranch]
|
|
168
|
+
* @returns {{allow:boolean, reason:string, unmet:string[]}}
|
|
169
|
+
*/
|
|
170
|
+
export function autoMergeDecision(params = {}) {
|
|
171
|
+
const unmet = [];
|
|
172
|
+
|
|
173
|
+
if (params.enabled !== true) unmet.push("auto-merge-disabled");
|
|
174
|
+
if (params.hasOpenPr !== true) unmet.push("no-open-pr");
|
|
175
|
+
if (params.branchProtectionSatisfied !== true) {
|
|
176
|
+
unmet.push("branch-protection-unsatisfied");
|
|
177
|
+
}
|
|
178
|
+
if (params.reviewApproved !== true) unmet.push("review-not-approved");
|
|
179
|
+
if (Number(params.pendingApprovals) > 0) unmet.push("pending-approvals");
|
|
180
|
+
|
|
181
|
+
const summary = summarizeChecks(params.checks);
|
|
182
|
+
if (summary.anyFailed) unmet.push("checks-failing");
|
|
183
|
+
if (summary.pending > 0) unmet.push("checks-pending");
|
|
184
|
+
|
|
185
|
+
// Every REQUIRED check must be present and passed — a required check that
|
|
186
|
+
// never ran is a block, not a pass.
|
|
187
|
+
const required = Array.isArray(params.requiredChecks)
|
|
188
|
+
? params.requiredChecks
|
|
189
|
+
: [];
|
|
190
|
+
for (const name of required) {
|
|
191
|
+
const state = summary.byName.get(String(name));
|
|
192
|
+
if (state !== CHECK_STATE.PASSED && state !== CHECK_STATE.NEUTRAL) {
|
|
193
|
+
unmet.push(`required-check-missing:${name}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
allow: unmet.length === 0,
|
|
199
|
+
reason: unmet.length === 0 ? "ok" : unmet[0],
|
|
200
|
+
unmet,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* A compact, log-safe status line for the PR status bar (P1-4 "Session 顶部提供
|
|
206
|
+
* PR 状态条"). Carries only coordinates + a checks tally, never a token.
|
|
207
|
+
*
|
|
208
|
+
* @param {object} params {branch, prNumber, checks, reviewApproved, mergeable}
|
|
209
|
+
* @returns {string}
|
|
210
|
+
*/
|
|
211
|
+
export function describePrStatusBar(params = {}) {
|
|
212
|
+
const parts = [];
|
|
213
|
+
if (params.branch) parts.push(`branch:${params.branch}`);
|
|
214
|
+
if (params.prNumber != null) parts.push(`PR#${params.prNumber}`);
|
|
215
|
+
const s = summarizeChecks(params.checks);
|
|
216
|
+
parts.push(`checks:${s.passed}/${s.total}${s.anyFailed ? " ✗" : ""}`);
|
|
217
|
+
parts.push(
|
|
218
|
+
`review:${params.reviewApproved === true ? "approved" : "pending"}`,
|
|
219
|
+
);
|
|
220
|
+
if (params.mergeable != null) {
|
|
221
|
+
parts.push(`merge:${params.mergeable ? "ready" : "blocked"}`);
|
|
222
|
+
}
|
|
223
|
+
return parts.join(" · ");
|
|
224
|
+
}
|