@@ -46,6 +46,8 @@ import { checkMinHostVersion } from "./min-host-version.js";
4646import {
4747 getOfficialExternalPluginCatalogEntryForPackage ,
4848 getOfficialExternalPluginCatalogManifest ,
49+ resolveOfficialExternalPluginId ,
50+ resolveOfficialExternalPluginInstall ,
4951} from "./official-external-plugin-catalog.js" ;
5052import { isPathInside , safeRealpathSync } from "./path-safety.js" ;
5153import type { PluginKind } from "./plugin-kind.types.js" ;
@@ -140,6 +142,7 @@ export type PluginManifestRecord = {
140142 packageOptionalDependencies ?: PluginDependencySpecMap ;
141143 packageChannel ?: PluginPackageChannel ;
142144 packageInstall ?: PluginPackageInstall ;
145+ trustedOfficialInstall ?: boolean ;
143146 qaRunners ?: PluginManifestQaRunner [ ] ;
144147 skills : string [ ] ;
145148 settingsFiles ?: string [ ] ;
@@ -365,6 +368,7 @@ function buildRecord(params: {
365368 schemaCacheKey ?: string ;
366369 configSchema ?: Record < string , unknown > ;
367370 bundledChannelConfigCollector ?: BundledChannelConfigCollector ;
371+ trustedOfficialInstall ?: boolean ;
368372} ) : PluginManifestRecord {
369373 const manifestChannelConfigs =
370374 params . candidate . origin === "bundled" && params . bundledChannelConfigCollector
@@ -434,6 +438,7 @@ function buildRecord(params: {
434438 packageOptionalDependencies : params . candidate . packageOptionalDependencies ,
435439 packageChannel : params . candidate . packageManifest ?. channel ,
436440 packageInstall : params . candidate . packageManifest ?. install ,
441+ trustedOfficialInstall : params . trustedOfficialInstall === true ? true : undefined ,
437442 qaRunners : params . manifest . qaRunners ,
438443 skills : params . manifest . skills ?? [ ] ,
439444 settingsFiles : [ ] ,
@@ -634,7 +639,7 @@ function matchesInstalledPluginRecord(params: {
634639 env : NodeJS . ProcessEnv ;
635640 installRecords : Record < string , PluginInstallRecord > ;
636641} ) : boolean {
637- if ( params . candidate . origin !== "global" ) {
642+ if ( params . candidate . origin !== "global" && params . candidate . origin !== "config" ) {
638643 return false ;
639644 }
640645 const record = params . installRecords [ params . pluginId ] ;
@@ -653,6 +658,72 @@ function matchesInstalledPluginRecord(params: {
653658 } ) ;
654659}
655660
661+ function npmSpecMatchesPackage ( value : string | undefined , packageName : string ) : boolean {
662+ const normalized = value ?. trim ( ) ;
663+ if ( ! normalized ) {
664+ return false ;
665+ }
666+ if ( normalized === packageName ) {
667+ return true ;
668+ }
669+ return normalized . startsWith ( `${ packageName } @` ) ;
670+ }
671+
672+ function isTrustedOfficialPluginInstall ( params : {
673+ pluginId : string ;
674+ candidate : PluginCandidate ;
675+ env : NodeJS . ProcessEnv ;
676+ installRecords : Record < string , PluginInstallRecord > ;
677+ } ) : boolean {
678+ if (
679+ ( params . candidate . origin !== "global" && params . candidate . origin !== "config" ) ||
680+ ! matchesInstalledPluginRecord ( {
681+ pluginId : params . pluginId ,
682+ candidate : params . candidate ,
683+ env : params . env ,
684+ installRecords : params . installRecords ,
685+ } )
686+ ) {
687+ return false ;
688+ }
689+ const packageName = params . candidate . packageName ?. trim ( ) ;
690+ if ( ! packageName ) {
691+ return false ;
692+ }
693+ const catalogEntry = getOfficialExternalPluginCatalogEntryForPackage ( packageName ) ;
694+ if ( ! catalogEntry || resolveOfficialExternalPluginId ( catalogEntry ) !== params . pluginId ) {
695+ return false ;
696+ }
697+ const officialInstall = resolveOfficialExternalPluginInstall ( catalogEntry ) ;
698+ const installRecord = params . installRecords [ params . pluginId ] ;
699+ if ( ! installRecord ) {
700+ return false ;
701+ }
702+ if (
703+ installRecord . source === "npm" &&
704+ officialInstall ?. npmSpec === packageName &&
705+ [
706+ installRecord . resolvedName ,
707+ installRecord . spec ,
708+ installRecord . resolvedSpec ,
709+ params . candidate . packageName ,
710+ ] . some ( ( value ) => npmSpecMatchesPackage ( value , packageName ) )
711+ ) {
712+ return true ;
713+ }
714+ if (
715+ installRecord . source === "clawhub" &&
716+ officialInstall ?. clawhubSpec &&
717+ installRecord . clawhubChannel === "official" &&
718+ ( installRecord . clawhubPackage === packageName ||
719+ installRecord . spec === officialInstall . clawhubSpec ||
720+ installRecord . resolvedSpec === officialInstall . clawhubSpec )
721+ ) {
722+ return true ;
723+ }
724+ return false ;
725+ }
726+
656727function resolveDuplicatePrecedenceRank ( params : {
657728 pluginId : string ;
658729 candidate : PluginCandidate ;
@@ -858,6 +929,12 @@ export function loadPluginManifestRegistry(
858929 manifestPath : manifestRes . manifestPath ,
859930 schemaCacheKey,
860931 configSchema,
932+ trustedOfficialInstall : isTrustedOfficialPluginInstall ( {
933+ pluginId : manifest . id ,
934+ candidate,
935+ env,
936+ installRecords : getInstallRecords ( ) ,
937+ } ) ,
861938 ...( params . bundledChannelConfigCollector
862939 ? { bundledChannelConfigCollector : params . bundledChannelConfigCollector }
863940 : { } ) ,
0 commit comments