

























@@ -19,7 +19,11 @@ import { canonicalizeMainSessionAlias } from "../config/sessions/main-session.js
1919import type { SessionScope } from "../config/sessions/types.js";
2020import type { OpenClawConfig } from "../config/types.openclaw.js";
2121import { createSubsystemLogger } from "../logging/subsystem.js";
22-import { createPluginStateKeyedStore } from "../plugin-state/plugin-state-store.js";
22+import {
23+countPluginStateLiveEntries,
24+createPluginStateKeyedStore,
25+MAX_PLUGIN_STATE_ENTRIES_PER_PLUGIN,
26+} from "../plugin-state/plugin-state-store.js";
2327import {
2428buildAgentMainSessionKey,
2529DEFAULT_AGENT_ID,
@@ -127,6 +131,15 @@ function resolvePluginStateImportTargetKey(scopeKey: string, key: string): strin
127131return scopeKey ? `${scopeKey}:${key}` : key;
128132}
129133134+function findMissingKey(expected: Set<string>, actual: Set<string>): string | undefined {
135+for (const key of expected) {
136+if (!actual.has(key)) {
137+return key;
138+}
139+}
140+return undefined;
141+}
142+130143async function withPluginStateImportEnv<T>(
131144plan: Extract<ChannelLegacyStateMigrationPlan, { kind: "plugin-state-import" }>,
132145run: () => Promise<T>,
@@ -155,23 +168,41 @@ async function runLegacyMigrationPlans(
155168for (const plan of plans) {
156169if (plan.kind === "plugin-state-import") {
157170await withPluginStateImportEnv(plan, async () => {
158-let storeEntries: Array<{ key: string }> = [];
171+let storeEntries: Array<{ key: string; value: unknown }> = [];
172+let pluginEntryCount = 0;
159173const store = createPluginStateKeyedStore<unknown>(plan.pluginId, {
160174namespace: plan.namespace,
161175maxEntries: plan.maxEntries,
162176});
163177try {
164178storeEntries = await store.entries();
179+pluginEntryCount = countPluginStateLiveEntries(plan.pluginId);
165180} catch (err) {
166181warnings.push(
167182`Failed reading ${plan.label} plugin state before migration: ${String(err)}`,
168183);
169184return;
170185}
171186const existingKeys = new Set(storeEntries.map(({ key }) => key));
187+const existingValuesByKey = new Map(storeEntries.map(({ key, value }) => [key, value]));
188+const expectedKeys = new Set(existingKeys);
172189let remainingCapacity = Math.max(0, plan.maxEntries - storeEntries.length);
173190const entries = await plan.readEntries();
191+const missingEntries = entries.filter(
192+({ key }) => !existingKeys.has(resolvePluginStateImportTargetKey(plan.scopeKey, key)),
193+);
194+const pluginRemainingCapacity = Math.max(
195+0,
196+MAX_PLUGIN_STATE_ENTRIES_PER_PLUGIN - pluginEntryCount,
197+);
198+if (missingEntries.length > pluginRemainingCapacity) {
199+warnings.push(
200+`Skipped migrating ${plan.label} because plugin state has room for ${pluginRemainingCapacity} of ${missingEntries.length} missing entries; left legacy source in place`,
201+);
202+return;
203+}
174204let imported = 0;
205+const importedKeys: string[] = [];
175206for (const entry of entries) {
176207const targetKey = resolvePluginStateImportTargetKey(plan.scopeKey, entry.key);
177208if (existingKeys.has(targetKey)) {
@@ -182,7 +213,26 @@ async function runLegacyMigrationPlans(
182213}
183214try {
184215await store.register(targetKey, entry.value);
216+const nextExpectedKeys = new Set(expectedKeys);
217+nextExpectedKeys.add(targetKey);
218+const liveKeys = new Set((await store.entries()).map(({ key }) => key));
219+const missingKey = findMissingKey(nextExpectedKeys, liveKeys);
220+if (missingKey) {
221+for (const importedKey of importedKeys.toReversed()) {
222+await store.delete(importedKey);
223+}
224+await store.delete(targetKey);
225+if (existingValuesByKey.has(missingKey)) {
226+await store.register(missingKey, existingValuesByKey.get(missingKey));
227+}
228+warnings.push(
229+`Stopped migrating ${plan.label} because plugin state cap evicted ${missingKey}; left legacy source in place`,
230+);
231+return;
232+}
233+expectedKeys.add(targetKey);
185234existingKeys.add(targetKey);
235+importedKeys.push(targetKey);
186236remainingCapacity--;
187237imported++;
188238} catch (err) {
@@ -194,10 +244,14 @@ async function runLegacyMigrationPlans(
194244`Migrated ${imported} ${plan.label} ${imported === 1 ? "entry" : "entries"} → plugin state`,
195245);
196246}
247+let cleanupKeys = existingKeys;
248+if (plan.cleanupSource === "rename") {
249+cleanupKeys = expectedKeys;
250+}
197251const allEntriesCovered =
198252entries.length > 0 &&
199253entries.every(({ key }) =>
200-existingKeys.has(resolvePluginStateImportTargetKey(plan.scopeKey, key)),
254+cleanupKeys.has(resolvePluginStateImportTargetKey(plan.scopeKey, key)),
201255);
202256if (allEntriesCovered && plan.cleanupSource === "rename" && fileExists(plan.sourcePath)) {
203257const archivedPath = `${plan.sourcePath}.migrated`;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。