




























@@ -4,17 +4,19 @@ import type { OpenClawConfig } from "../../../config/types.openclaw.js";
44import { normalizePluginId } from "../../../plugins/config-state.js";
55import { loadInstalledPluginIndexInstallRecordsSync } from "../../../plugins/installed-plugin-index-records.js";
66import { loadManifestMetadataSnapshot } from "../../../plugins/manifest-contract-eligibility.js";
7+import { defaultSlotIdForKey, type PluginSlotKey } from "../../../plugins/slots.js";
78import { sanitizeForLog } from "../../../terminal/ansi.js";
89import { asObjectRecord } from "./object.js";
9101011const CHANNEL_CONFIG_META_KEYS = new Set(["defaults", "modelByChannel"]);
111212-type StalePluginSurface = "allow" | "entries" | "channel" | "heartbeat" | "modelByChannel";
13+type StalePluginSurface = "allow" | "entries" | "slot" | "channel" | "heartbeat" | "modelByChannel";
13141415type StalePluginConfigHit = {
1516pluginId: string;
1617pathLabel: string;
1718surface: StalePluginSurface;
19+slotKey?: PluginSlotKey;
1820};
19212022type StalePluginRegistryState = {
@@ -131,6 +133,32 @@ function scanStalePluginConfigWithState(
131133}
132134}
133135136+const slots = asObjectRecord(plugins?.slots);
137+if (slots) {
138+for (const slotKey of ["memory", "contextEngine"] as const satisfies readonly PluginSlotKey[]) {
139+const rawPluginId = slots[slotKey];
140+if (typeof rawPluginId !== "string") {
141+continue;
142+}
143+const pluginId = normalizePluginId(rawPluginId);
144+const defaultSlotId = defaultSlotIdForKey(slotKey);
145+if (
146+!pluginId ||
147+rawPluginId.trim().toLowerCase() === "none" ||
148+pluginId === normalizePluginId(defaultSlotId) ||
149+knownIds.has(pluginId)
150+) {
151+continue;
152+}
153+hits.push({
154+pluginId: rawPluginId,
155+pathLabel: `plugins.slots.${slotKey}`,
156+surface: "slot",
157+ slotKey,
158+});
159+}
160+}
161+134162const staleChannelIds = collectDanglingChannelIds({
135163 cfg,
136164 registryState,
@@ -236,6 +264,9 @@ function formatStalePluginHitWarning(hit: StalePluginConfigHit): string {
236264if (hit.surface === "allow" || hit.surface === "entries") {
237265return `- ${hit.pathLabel}: stale plugin reference "${hit.pluginId}" was found.`;
238266}
267+if (hit.surface === "slot") {
268+return `- ${hit.pathLabel}: slot references missing plugin "${hit.pluginId}".`;
269+}
239270if (hit.surface === "channel") {
240271return `- ${hit.pathLabel}: dangling channel config for missing plugin "${hit.pluginId}" was found.`;
241272}
@@ -310,6 +341,19 @@ export function maybeRepairStalePluginConfig(
310341}
311342}
312343344+const slotHits = hits.filter(
345+(hit): hit is StalePluginConfigHit & { slotKey: PluginSlotKey } =>
346+hit.surface === "slot" && hit.slotKey !== undefined,
347+);
348+if (slotHits.length > 0) {
349+const slots = asObjectRecord(nextPlugins?.slots);
350+if (slots) {
351+for (const hit of slotHits) {
352+slots[hit.slotKey] = defaultSlotIdForKey(hit.slotKey);
353+}
354+}
355+}
356+313357const channelIds = hits.filter((hit) => hit.surface === "channel").map((hit) => hit.pluginId);
314358if (channelIds.length > 0) {
315359removeDanglingChannelReferences(next, channelIds);
@@ -326,6 +370,11 @@ export function maybeRepairStalePluginConfig(
326370`- plugins.entries: removed ${entryIds.length} stale plugin entr${entryIds.length === 1 ? "y" : "ies"} (${entryIds.join(", ")})`,
327371);
328372}
373+if (slotHits.length > 0) {
374+changes.push(
375+`- plugins.slots: reset ${slotHits.length} stale plugin slot${slotHits.length === 1 ? "" : "s"} (${slotHits.map((hit) => `${hit.slotKey}: ${hit.pluginId} -> ${defaultSlotIdForKey(hit.slotKey)}`).join(", ")})`,
376+);
377+}
329378if (channelIds.length > 0) {
330379changes.push(
331380`- channels: removed ${channelIds.length} stale channel config${channelIds.length === 1 ? "" : "s"} (${channelIds.join(", ")})`,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。