Files
openclaw/extensions/device-pair/doctor-contract-api.ts
Peter Steinberger eafe2a8d0b refactor: consolidate duplicated plugin state and doctor migration plumbing onto SDK seams (#99850)
* refactor(plugin-sdk): add createPersistentDedupeCache and migrate channel presence caches

* refactor(matrix): adopt SDK approval reaction target store

* refactor(plugin-sdk): share doctor legacy-state migration fs helpers

* refactor(memory-core): dedupe qmd cache entry envelope validation

* chore(plugin-sdk): pin surface budgets for shared dedupe and doctor helpers

* test(matrix): use future approval expiry fixtures for reaction targets

* test(matrix): use future approval expiry fixtures for reaction targets
2026-07-04 01:51:03 -07:00

86 lines
2.7 KiB
TypeScript

// Device Pair doctor contract migrates shipped plugin-owned state.
import fs from "node:fs/promises";
import path from "node:path";
import {
archiveLegacyStateSource,
type PluginDoctorStateMigration,
} from "openclaw/plugin-sdk/runtime-doctor";
import {
DEVICE_PAIR_NOTIFY_LEGACY_STATE_FILE,
DEVICE_PAIR_NOTIFY_SUBSCRIBER_MAX_ENTRIES,
DEVICE_PAIR_NOTIFY_SUBSCRIBER_NAMESPACE,
normalizeLegacyNotifyState,
notifySubscriberStoreKey,
type LegacyNotifyStateFile,
type NotifySubscription,
} from "./notify-state.js";
function resolveLegacyNotifyStatePath(stateDir: string): string {
return path.join(stateDir, DEVICE_PAIR_NOTIFY_LEGACY_STATE_FILE);
}
async function readLegacyNotifyState(filePath: string): Promise<LegacyNotifyStateFile | null> {
try {
return normalizeLegacyNotifyState(JSON.parse(await fs.readFile(filePath, "utf8")) as unknown);
} catch {
return null;
}
}
export const stateMigrations: PluginDoctorStateMigration[] = [
{
id: "device-pair-notify-json-to-plugin-state",
label: "Device Pair notify subscribers",
async detectLegacyState(params) {
const filePath = resolveLegacyNotifyStatePath(params.stateDir);
const state = await readLegacyNotifyState(filePath);
if (!state || state.subscribers.length === 0) {
return null;
}
return {
preview: [
`- Device Pair notify subscribers: ${filePath} -> plugin state (${DEVICE_PAIR_NOTIFY_SUBSCRIBER_NAMESPACE}, ${state.subscribers.length} subscriber(s))`,
],
};
},
async migrateLegacyState(params) {
const changes: string[] = [];
const warnings: string[] = [];
const filePath = resolveLegacyNotifyStatePath(params.stateDir);
const state = await readLegacyNotifyState(filePath);
if (!state || state.subscribers.length === 0) {
return { changes, warnings };
}
const store = params.context.openPluginStateKeyedStore<NotifySubscription>({
namespace: DEVICE_PAIR_NOTIFY_SUBSCRIBER_NAMESPACE,
maxEntries: DEVICE_PAIR_NOTIFY_SUBSCRIBER_MAX_ENTRIES,
});
let imported = 0;
let alreadyPresent = 0;
for (const subscriber of state.subscribers) {
const inserted = await store.registerIfAbsent(
notifySubscriberStoreKey(subscriber),
subscriber,
);
if (inserted) {
imported++;
} else {
alreadyPresent++;
}
}
changes.push(
`Migrated Device Pair notify subscribers -> plugin state (${imported} imported, ${alreadyPresent} already present)`,
);
await archiveLegacyStateSource({
filePath,
label: "Device Pair notify-state",
changes,
warnings,
});
return { changes, warnings };
},
},
];