

























@@ -514,6 +514,40 @@ describe("repairMissingConfiguredPluginInstalls", () => {
514514expect(result).toEqual({ changes: [], warnings: [] });
515515});
516516517+it("does not install channel plugins when the matching plugin entry is disabled", async () => {
518+mocks.listChannelPluginCatalogEntries.mockReturnValue([
519+{
520+id: "matrix",
521+pluginId: "matrix",
522+meta: { label: "Matrix" },
523+install: {
524+npmSpec: "@openclaw/plugin-matrix@1.2.3",
525+},
526+},
527+]);
528+529+const { repairMissingConfiguredPluginInstalls } =
530+await import("./missing-configured-plugin-install.js");
531+const result = await repairMissingConfiguredPluginInstalls({
532+cfg: {
533+plugins: {
534+entries: {
535+matrix: { enabled: false },
536+},
537+},
538+channels: {
539+matrix: { homeserver: "https://matrix.example.org" },
540+},
541+},
542+env: {},
543+});
544+545+expect(mocks.installPluginFromClawHub).not.toHaveBeenCalled();
546+expect(mocks.installPluginFromNpmSpec).not.toHaveBeenCalled();
547+expect(mocks.writePersistedInstalledPluginIndexInstallRecords).not.toHaveBeenCalled();
548+expect(result).toEqual({ changes: [], warnings: [] });
549+});
550+517551it("does not download configured channel plugins that are still bundled", async () => {
518552mocks.listChannelPluginCatalogEntries.mockReturnValue([
519553{
@@ -1126,6 +1160,218 @@ describe("repairMissingConfiguredPluginInstalls", () => {
11261160expect(result).toEqual({ changes: [], warnings: [] });
11271161});
112811621163+it("does not install a channel catalog plugin when a configured plugin already owns that channel", async () => {
1164+mocks.loadPluginMetadataSnapshot.mockReturnValue({
1165+plugins: [
1166+{
1167+id: "openclaw-lark",
1168+origin: "config",
1169+channels: ["feishu"],
1170+channelConfigs: {
1171+feishu: {
1172+schema: {
1173+type: "object",
1174+},
1175+},
1176+},
1177+},
1178+],
1179+diagnostics: [],
1180+});
1181+mocks.listChannelPluginCatalogEntries.mockReturnValue([
1182+{
1183+id: "feishu",
1184+pluginId: "feishu",
1185+meta: { label: "Feishu" },
1186+install: {
1187+npmSpec: "@openclaw/feishu",
1188+},
1189+trustedSourceLinkedOfficialInstall: true,
1190+},
1191+]);
1192+1193+const { repairMissingConfiguredPluginInstalls } =
1194+await import("./missing-configured-plugin-install.js");
1195+const result = await repairMissingConfiguredPluginInstalls({
1196+cfg: {
1197+plugins: {
1198+entries: {
1199+"openclaw-lark": {
1200+enabled: true,
1201+},
1202+},
1203+},
1204+channels: {
1205+feishu: {
1206+footer: {
1207+model: false,
1208+},
1209+},
1210+},
1211+},
1212+env: {},
1213+});
1214+1215+expect(mocks.installPluginFromClawHub).not.toHaveBeenCalled();
1216+expect(mocks.installPluginFromNpmSpec).not.toHaveBeenCalled();
1217+expect(mocks.writePersistedInstalledPluginIndexInstallRecords).not.toHaveBeenCalled();
1218+expect(result).toEqual({ changes: [], warnings: [] });
1219+});
1220+1221+it("still installs a channel catalog plugin when the configured owner is blocked by the allowlist", async () => {
1222+mocks.loadPluginMetadataSnapshot.mockReturnValue({
1223+plugins: [
1224+{
1225+id: "openclaw-lark",
1226+origin: "config",
1227+channels: ["feishu"],
1228+channelConfigs: {
1229+feishu: {
1230+schema: {
1231+type: "object",
1232+},
1233+},
1234+},
1235+},
1236+],
1237+diagnostics: [],
1238+});
1239+mocks.listChannelPluginCatalogEntries.mockReturnValue([
1240+{
1241+id: "feishu",
1242+pluginId: "feishu",
1243+meta: { label: "Feishu" },
1244+install: {
1245+npmSpec: "@openclaw/feishu",
1246+},
1247+trustedSourceLinkedOfficialInstall: true,
1248+},
1249+]);
1250+mocks.installPluginFromNpmSpec.mockResolvedValueOnce({
1251+ok: true,
1252+pluginId: "feishu",
1253+targetDir: "/tmp/openclaw-plugins/feishu",
1254+version: "2026.5.2",
1255+npmResolution: {
1256+name: "@openclaw/feishu",
1257+version: "2026.5.2",
1258+resolvedSpec: "@openclaw/feishu@2026.5.2",
1259+},
1260+});
1261+1262+const { repairMissingConfiguredPluginInstalls } =
1263+await import("./missing-configured-plugin-install.js");
1264+const result = await repairMissingConfiguredPluginInstalls({
1265+cfg: {
1266+plugins: {
1267+allow: ["some-other-plugin"],
1268+entries: {
1269+"openclaw-lark": {
1270+enabled: true,
1271+},
1272+},
1273+},
1274+channels: {
1275+feishu: {
1276+footer: {
1277+model: false,
1278+},
1279+},
1280+},
1281+},
1282+env: {},
1283+});
1284+1285+expect(mocks.installPluginFromNpmSpec).toHaveBeenCalledWith(
1286+expect.objectContaining({
1287+spec: "@openclaw/feishu",
1288+expectedPluginId: "feishu",
1289+trustedSourceLinkedOfficialInstall: true,
1290+}),
1291+);
1292+expect(result.changes).toEqual([
1293+'Installed missing configured plugin "feishu" from @openclaw/feishu.',
1294+]);
1295+});
1296+1297+it("still installs a channel catalog plugin when that plugin is explicitly configured", async () => {
1298+mocks.loadPluginMetadataSnapshot.mockReturnValue({
1299+plugins: [
1300+{
1301+id: "openclaw-lark",
1302+origin: "config",
1303+channels: ["feishu"],
1304+channelConfigs: {
1305+feishu: {
1306+schema: {
1307+type: "object",
1308+},
1309+},
1310+},
1311+},
1312+],
1313+diagnostics: [],
1314+});
1315+mocks.listChannelPluginCatalogEntries.mockReturnValue([
1316+{
1317+id: "feishu",
1318+pluginId: "feishu",
1319+meta: { label: "Feishu" },
1320+install: {
1321+npmSpec: "@openclaw/feishu",
1322+},
1323+trustedSourceLinkedOfficialInstall: true,
1324+},
1325+]);
1326+mocks.installPluginFromNpmSpec.mockResolvedValueOnce({
1327+ok: true,
1328+pluginId: "feishu",
1329+targetDir: "/tmp/openclaw-plugins/feishu",
1330+version: "2026.5.2",
1331+npmResolution: {
1332+name: "@openclaw/feishu",
1333+version: "2026.5.2",
1334+resolvedSpec: "@openclaw/feishu@2026.5.2",
1335+},
1336+});
1337+1338+const { repairMissingConfiguredPluginInstalls } =
1339+await import("./missing-configured-plugin-install.js");
1340+const result = await repairMissingConfiguredPluginInstalls({
1341+cfg: {
1342+plugins: {
1343+entries: {
1344+feishu: {
1345+enabled: true,
1346+},
1347+"openclaw-lark": {
1348+enabled: true,
1349+},
1350+},
1351+},
1352+channels: {
1353+feishu: {
1354+footer: {
1355+model: false,
1356+},
1357+},
1358+},
1359+},
1360+env: {},
1361+});
1362+1363+expect(mocks.installPluginFromNpmSpec).toHaveBeenCalledWith(
1364+expect.objectContaining({
1365+spec: "@openclaw/feishu",
1366+expectedPluginId: "feishu",
1367+trustedSourceLinkedOfficialInstall: true,
1368+}),
1369+);
1370+expect(result.changes).toEqual([
1371+'Installed missing configured plugin "feishu" from @openclaw/feishu.',
1372+]);
1373+});
1374+11291375it("reinstalls a missing configured plugin from its persisted install record", async () => {
11301376const records = {
11311377demo: {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。