feat(plugins): add SQLite plugin state store (#74190)

* feat(plugins): add experimental sqlite plugin state store
This commit is contained in:
Alex Knight
2026-04-29 23:02:14 +10:00
committed by GitHub
parent abaa4326d8
commit bbf985d50a
16 changed files with 1822 additions and 6 deletions

View File

@@ -24,6 +24,11 @@ import {
NODE_SYSTEM_NOTIFY_COMMAND,
NODE_SYSTEM_RUN_COMMANDS,
} from "../infra/node-commands.js";
import {
createPluginStateKeyedStore,
type OpenKeyedStoreOptions,
type PluginStateKeyedStore,
} from "../plugin-state/plugin-state-store.js";
import { normalizePluginGatewayMethodScope } from "../shared/gateway-method-policy.js";
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
import {
@@ -1944,6 +1949,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
});
const pluginRuntimeById = new Map<string, PluginRuntime>();
const pluginRuntimeRecordById = new Map<string, PluginRecord>();
const resolvePluginRuntime = (pluginId: string): PluginRuntime => {
const cached = pluginRuntimeById.get(pluginId);
@@ -1952,6 +1958,23 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
}
const runtime = new Proxy(registryParams.runtime, {
get(target, prop, receiver) {
if (prop === "state") {
const baseState = Reflect.get(target, prop, receiver);
return {
...baseState,
openKeyedStore: <T>(options: OpenKeyedStoreOptions): PluginStateKeyedStore<T> => {
const record =
pluginRuntimeRecordById.get(pluginId) ??
registry.plugins.find((entry) => entry.id === pluginId);
if (record?.origin !== "bundled") {
throw new Error(
"openKeyedStore is only available for bundled plugins in this release.",
);
}
return createPluginStateKeyedStore<T>(pluginId, options);
},
} satisfies PluginRuntime["state"];
}
if (prop !== "subagent") {
return Reflect.get(target, prop, receiver);
}
@@ -1984,6 +2007,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
): OpenClawPluginApi => {
const registrationMode = params.registrationMode ?? "full";
const registrationCapabilities = resolvePluginRegistrationCapabilities(registrationMode);
pluginRuntimeRecordById.set(record.id, record);
return buildPluginApi({
id: record.id,
name: record.name,

View File

@@ -226,7 +226,12 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}):
channel: createRuntimeChannel(),
events: createRuntimeEvents(),
logging: createRuntimeLogging(),
state: { resolveStateDir },
state: {
resolveStateDir,
openKeyedStore: () => {
throw new Error("openKeyedStore is only available through the plugin runtime proxy.");
},
},
tasks,
taskFlow,
} satisfies Omit<
@@ -262,7 +267,7 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}):
defineCachedValue(runtime, "videoGeneration", createRuntimeVideoGeneration);
defineCachedValue(runtime, "musicGeneration", createRuntimeMusicGeneration);
return runtime as PluginRuntime;
return runtime as unknown as PluginRuntime;
}
export type { PluginRuntime } from "./types.js";

View File

@@ -227,6 +227,9 @@ export type PluginRuntimeCore = {
};
state: {
resolveStateDir: typeof import("../../config/paths.js").resolveStateDir;
openKeyedStore: <T>(
options: import("../../plugin-state/plugin-state-store.types.js").OpenKeyedStoreOptions,
) => import("../../plugin-state/plugin-state-store.types.js").PluginStateKeyedStore<T>;
};
tasks: {
runs: PluginRuntimeTaskRuns;