mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-28 17:53:36 +00:00
* docs: document markdown marker renderer * docs: document rendered markdown chunking * docs: document markdown text chunking * docs: document shared text chunking * docs: document plugin text chunking exports * docs: document avatar policy constants * docs: document node match candidates * docs: document scoped expiring id cache * docs: document runtime import normalization * docs: document string sample summaries * docs: document session usage timeseries types * docs: document session usage response types * docs: document manifest frontmatter shapes * docs: document channel route input metadata * docs: document pair loop guard settings * docs: document migration config patch helpers * docs: document api provider registry * docs: document tool call repair payloads * docs: document plugin tool payload helpers * docs: document lazy promise loader * docs: document store writer queue state * docs: document thread binding lifecycle * docs: document concurrency helper contract * docs: document gateway client info contract * docs: document delivery context contracts * docs: document secret ref defaults contract * docs: document command gating contract * docs: document avatar policy contract * docs: document node match policy * docs: document message channel normalization * docs: document boolean parsing contract * docs: document zod parse helpers * docs: document direct dm guard policy * docs: document fixed window limiter contract * docs: document node presence event contract * docs: document secret normalization contract * docs: document progress draft line removal * docs: document usage formatting contracts * docs: document agent run status contract * docs: document runtime import helpers * docs: document provider utility ownership * docs: document invalid config helpers * docs: document json compat parser * docs: document channel config metadata ownership * docs: document channel logging helpers * docs: document sender identity validation ownership * docs: document string sampling helper * docs: document global singleton helpers * docs: document transcript tool helpers * docs: document exec safe-bin normalization * docs: document reaction level resolver * docs: document account snapshot redaction boundary * docs: document messaging target helpers * docs: document thread binding messages * docs: document conversation binding context * docs: document conversation resolution helper * docs: document owner display secret retention * docs: document provider request config types * docs: document skills config types * docs: document memory config types * docs: document imessage config types * docs: document crestodian config types * docs: document tools config policies * docs: document shared config base types * docs: document channel config contracts * docs: document openclaw config state types * docs: document model config contracts * docs: document shared agent config types * docs: document agent defaults config types * docs: document secret input contracts * docs: document auth config contracts * docs: document gateway config contracts * docs: document tool call stream repair contracts * docs: document memory host facades * docs: document llm core contracts * docs: document markdown core contracts * docs: document gateway connect error contracts * docs: document gateway protocol primitives * docs: document gateway frame schemas * docs: document gateway device schemas * docs: document gateway environment schemas * docs: document gateway push schemas * docs: document gateway plugin schemas * docs: document gateway artifact schemas * docs: document gateway command schemas * docs: document gateway task schemas * docs: document gateway exec approval schemas * docs: document gateway secret schemas * docs: document gateway config schemas * docs: document gateway snapshot schemas * docs: document gateway chat schemas * docs: document gateway wizard schemas * docs: document gateway node schemas * docs: document gateway plugin approval schemas * docs: document gateway talk schemas * docs: document gateway agent schemas * docs: document gateway session schemas * docs: document gateway cron schemas * docs: document gateway agent model skill schemas * docs: document gateway skill proposal tool schemas * docs: document gateway protocol registry * docs: document gateway channel status schemas * docs: document gateway schema regression tests * docs: document gateway schema barrel * docs: document gateway validator tests * docs: document gateway primitive push tests * docs: document gateway contract tests * docs: document native protocol guard * docs: document channel schema tests * docs: document gateway protocol smoke tests * docs: document gateway protocol entrypoint * docs: document gateway protocol type exports * docs: document gateway error codes * docs: document protocol schema registry * docs: document talk audio codec * docs: document talk activation names * docs: document talk consult questions * docs: document talk consult tool * docs: document talk run control contracts * docs: document talk run control adapter * docs: document talkback consult queue * docs: document talk consult transcript guard * docs: document talk fast context runtime * docs: document forced talk consult coordinator * docs: document talk output activity tracker * docs: document talk event metrics * docs: document talk diagnostics * docs: document talk observability hook * docs: document talk provider resolver * docs: document talk provider registry * docs: document talk runtime primitives * docs: document talk consult controller logs * docs: document channel identity helpers * docs: document channel account allowlist helpers * docs: document channel metadata draft controls * docs: document channel ingress policy * docs: document channel sender access gates * docs: document channel catalog message contracts * docs: document channel account plugin helpers * docs: document configured binding helpers * docs: document channel acp approval config helpers * docs: document channel bundled config write helpers * docs: document channel plugin utility contracts * docs: document channel config access helpers * docs: document channel message action helpers * docs: document channel outbound runtime helpers * docs: document channel pairing promotion helpers * docs: document channel registry helpers * docs: document channel setup wizard helpers * docs: document channel lifecycle status helpers * docs: document channel target thread helpers * docs: document channel session binding helpers * docs: document channel package module probes * docs: document channel setup wizard contracts * docs: document channel plugin API barrels * docs: document channel contract test helpers * docs: document channel core helpers * docs: document small core facades * docs: document provider runtime helpers * docs: document persistence and realtime helpers * docs: document mcp and state helpers * docs: document tool planner contracts * docs: document music generation runtime * docs: document crestodian command flow * docs: document utility helpers * docs: document node host helpers * docs: document transcript contracts * docs: document trajectory export contracts * docs: document image generation contracts * docs: document routing helper contracts * docs: document session helper contracts * docs: document video generation contracts * docs: document model catalog contracts * docs: document proxy capture contracts * docs: document status rendering contracts * docs: document test helper contracts * docs: document wizard setup contracts * docs: document process contracts * docs: document memory host sdk contracts * docs: document tts contracts * docs: document secrets runtime contracts * docs: document shared helper contracts * docs: document hook runtime contracts * docs: document security audit contracts * docs: document flow contracts * docs: document media understanding contracts * docs: document tui contracts * docs: document logging contracts * docs: document llm contracts * docs: document cron contracts * docs: document daemon contracts * docs: document task contracts * docs: document acp contracts * docs: document test utility contracts * docs: document skill contracts * docs: document config contracts * docs: document outbound infra contracts * docs: document command analysis contracts * docs: document provider usage infra contracts * docs: document file safety infra contracts * docs: document exec approval infra contracts * docs: document gateway runtime infra contracts * docs: document infra utility contracts * docs: document infra queue storage contracts * docs: document heartbeat infra contracts * docs: document remaining infra contracts * docs: document gateway auth contracts * docs: document gateway display helpers * docs: document gateway http helpers * docs: document gateway node helpers * docs: document gateway mcp helpers * docs: document gateway support helpers * docs: document gateway server runtime helpers * docs: document gateway runtime bootstrap helpers * docs: document gateway session events * docs: document gateway utility helpers * docs: document gateway talk helpers * docs: document gateway helper contracts * docs: document gateway server method helpers * docs: document gateway server auth helpers * docs: document gateway server tests * docs: document gateway test helpers * docs: document gateway node tests * docs: document gateway channel tests * docs: document gateway session tests * docs: document gateway server startup tests * docs: document gateway tool test helpers * docs: document gateway server test helpers * docs: document gateway server method tests * docs: document remaining gateway tests * docs: document plugin sdk public subpaths * docs: document plugin sdk runtime helpers * docs: document plugin sdk memory provider helpers * docs: document plugin sdk runtime facades * docs: document plugin sdk command approval helpers * docs: document plugin sdk runtime types * docs: document plugin sdk browser account helpers * docs: document plugin sdk media memory helpers * docs: document plugin sdk core tests * docs: document plugin sdk contract helpers * docs: document plugin sdk test helpers * docs: document remaining plugin sdk tests * docs: document cli utility helpers * docs: document cli runtime helpers * docs: document cli command registration helpers * docs: document node cli helpers * docs: document cli program registration * docs: document message cli registration * docs: document daemon cli helpers * docs: document cli route parsers
496 lines
15 KiB
TypeScript
496 lines
15 KiB
TypeScript
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
|
|
import {
|
|
clearPluginStateDatabaseForTests,
|
|
closePluginStateDatabase,
|
|
MAX_PLUGIN_STATE_VALUE_BYTES,
|
|
pluginStateClear,
|
|
pluginStateConsume,
|
|
pluginStateDelete,
|
|
pluginStateEntries,
|
|
pluginStateLookup,
|
|
pluginStateRegister,
|
|
pluginStateRegisterIfAbsent,
|
|
pluginStateUpdate,
|
|
} from "./plugin-state-store.sqlite.js";
|
|
import type {
|
|
OpenKeyedStoreOptions,
|
|
PluginStateEntry,
|
|
PluginStateKeyedStore,
|
|
PluginStateSyncKeyedStore,
|
|
PluginStateStoreOperation,
|
|
} from "./plugin-state-store.types.js";
|
|
import { PluginStateStoreError } from "./plugin-state-store.types.js";
|
|
|
|
// Public plugin-state facade over the sqlite-backed store. It validates plugin
|
|
// ids, namespaces, JSON values, TTLs, and per-plugin limits before persistence.
|
|
export type {
|
|
OpenKeyedStoreOptions,
|
|
PluginStateEntry,
|
|
PluginStateKeyedStore,
|
|
PluginStateSyncKeyedStore,
|
|
PluginStateStoreErrorCode,
|
|
PluginStateStoreOperation,
|
|
PluginStateStoreProbeResult,
|
|
PluginStateStoreProbeStep,
|
|
} from "./plugin-state-store.types.js";
|
|
export { PluginStateStoreError } from "./plugin-state-store.types.js";
|
|
export {
|
|
closePluginStateDatabase,
|
|
closePluginStateSqliteStore,
|
|
countPluginStateLiveEntries,
|
|
isPluginStateDatabaseOpen,
|
|
MAX_PLUGIN_STATE_ENTRIES_PER_PLUGIN,
|
|
probePluginStateStore,
|
|
setMaxPluginStateEntriesPerPluginForTests,
|
|
sweepExpiredPluginStateEntries,
|
|
} from "./plugin-state-store.sqlite.js";
|
|
|
|
const NAMESPACE_PATTERN = /^[a-z0-9][a-z0-9._-]*$/iu;
|
|
const MAX_NAMESPACE_BYTES = 128;
|
|
const MAX_KEY_BYTES = 512;
|
|
const MAX_JSON_DEPTH = 64;
|
|
|
|
type StoreOptionSignature = {
|
|
maxEntries: number;
|
|
defaultTtlMs?: number;
|
|
};
|
|
|
|
type PreparedRegisterParams = {
|
|
key: string;
|
|
valueJson: string;
|
|
ttlMs?: number;
|
|
};
|
|
|
|
const namespaceOptionSignatures = new Map<string, StoreOptionSignature>();
|
|
const textEncoder = new TextEncoder();
|
|
|
|
function invalidInput(
|
|
message: string,
|
|
operation: PluginStateStoreOperation = "register",
|
|
): PluginStateStoreError {
|
|
return new PluginStateStoreError(message, {
|
|
code: "PLUGIN_STATE_INVALID_INPUT",
|
|
operation,
|
|
});
|
|
}
|
|
|
|
function assertMaxBytes(
|
|
label: string,
|
|
value: string,
|
|
max: number,
|
|
operation: PluginStateStoreOperation = "register",
|
|
): void {
|
|
if (textEncoder.encode(value).byteLength > max) {
|
|
throw invalidInput(`plugin state ${label} must be <= ${max} bytes`, operation);
|
|
}
|
|
}
|
|
|
|
function validateNamespace(value: string, operation: PluginStateStoreOperation = "open"): string {
|
|
const trimmed = value.trim();
|
|
if (!NAMESPACE_PATTERN.test(trimmed)) {
|
|
throw invalidInput(`plugin state namespace must be a safe path segment: ${value}`, operation);
|
|
}
|
|
assertMaxBytes("namespace", trimmed, MAX_NAMESPACE_BYTES, operation);
|
|
return trimmed;
|
|
}
|
|
|
|
function validateKey(value: string, operation: PluginStateStoreOperation = "register"): string {
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
throw invalidInput("plugin state entry key must not be empty", operation);
|
|
}
|
|
assertMaxBytes("entry key", trimmed, MAX_KEY_BYTES, operation);
|
|
return trimmed;
|
|
}
|
|
|
|
function validateMaxEntries(value: number): number {
|
|
if (!Number.isInteger(value) || value < 1) {
|
|
throw invalidInput("plugin state maxEntries must be an integer >= 1", "open");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function validateOptionalTtlMs(
|
|
value: number | undefined,
|
|
operation: PluginStateStoreOperation = "register",
|
|
): number | undefined {
|
|
if (value == null) {
|
|
return undefined;
|
|
}
|
|
if (!Number.isSafeInteger(value) || value < 1) {
|
|
throw invalidInput("plugin state ttlMs must be a positive integer", operation);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function assertPlainJsonValue(
|
|
value: unknown,
|
|
seen: WeakSet<object>,
|
|
path: string,
|
|
depth = 0,
|
|
): void {
|
|
if (depth > MAX_JSON_DEPTH) {
|
|
throw new PluginStateStoreError(
|
|
`plugin state value nesting exceeds maximum depth of ${MAX_JSON_DEPTH}`,
|
|
{ code: "PLUGIN_STATE_LIMIT_EXCEEDED", operation: "register" },
|
|
);
|
|
}
|
|
if (value === null) {
|
|
return;
|
|
}
|
|
const valueType = typeof value;
|
|
if (valueType === "string" || valueType === "boolean") {
|
|
return;
|
|
}
|
|
if (valueType === "number") {
|
|
if (!Number.isFinite(value)) {
|
|
throw invalidInput(`plugin state value at ${path} must be a finite number`);
|
|
}
|
|
return;
|
|
}
|
|
if (valueType !== "object") {
|
|
throw invalidInput(`plugin state value at ${path} must be JSON-serializable`);
|
|
}
|
|
|
|
const objectValue = value as object;
|
|
if (seen.has(objectValue)) {
|
|
throw invalidInput(`plugin state value at ${path} must not contain circular references`);
|
|
}
|
|
seen.add(objectValue);
|
|
try {
|
|
if (Array.isArray(value)) {
|
|
for (let index = 0; index < value.length; index += 1) {
|
|
if (!(index in value)) {
|
|
throw invalidInput(`plugin state array at ${path} must not be sparse`);
|
|
}
|
|
assertPlainJsonValue(value[index], seen, `${path}[${index}]`, depth + 1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (Object.getPrototypeOf(objectValue) !== Object.prototype) {
|
|
throw invalidInput(`plugin state object at ${path} must be a plain object`);
|
|
}
|
|
|
|
// Reject accessors, symbols, sparse arrays, and non-enumerable state so stored
|
|
// values cannot execute code or round-trip differently through JSON.
|
|
const descriptorEntries = Object.entries(Object.getOwnPropertyDescriptors(objectValue));
|
|
const enumerableKeys = Object.keys(objectValue);
|
|
if (Object.getOwnPropertySymbols(objectValue).length > 0) {
|
|
throw invalidInput(`plugin state object at ${path} must not use symbol keys`);
|
|
}
|
|
if (descriptorEntries.length !== enumerableKeys.length) {
|
|
throw invalidInput(`plugin state object at ${path} must not use non-enumerable properties`);
|
|
}
|
|
for (const [key, descriptor] of descriptorEntries) {
|
|
if (descriptor.get || descriptor.set || !("value" in descriptor)) {
|
|
throw invalidInput(`plugin state object at ${path}.${key} must use data properties`);
|
|
}
|
|
assertPlainJsonValue(descriptor.value, seen, `${path}.${key}`, depth + 1);
|
|
}
|
|
} finally {
|
|
seen.delete(objectValue);
|
|
}
|
|
}
|
|
|
|
function assertJsonSerializable(value: unknown): void {
|
|
assertPlainJsonValue(value, new WeakSet<object>(), "value");
|
|
}
|
|
|
|
function assertValueSize(json: string): void {
|
|
if (textEncoder.encode(json).byteLength > MAX_PLUGIN_STATE_VALUE_BYTES) {
|
|
throw new PluginStateStoreError("plugin state value exceeds 64KB limit", {
|
|
code: "PLUGIN_STATE_LIMIT_EXCEEDED",
|
|
operation: "register",
|
|
});
|
|
}
|
|
}
|
|
|
|
function prepareRegisterParams(
|
|
key: string,
|
|
value: unknown,
|
|
defaultTtlMs?: number,
|
|
opts?: { ttlMs?: number },
|
|
): PreparedRegisterParams {
|
|
const normalizedKey = validateKey(key, "register");
|
|
assertJsonSerializable(value);
|
|
const json = JSON.stringify(value);
|
|
if (json === undefined) {
|
|
throw invalidInput("plugin state value must be JSON-serializable", "register");
|
|
}
|
|
assertValueSize(json);
|
|
const ttlMs = validateOptionalTtlMs(opts?.ttlMs, "register") ?? defaultTtlMs;
|
|
return {
|
|
key: normalizedKey,
|
|
valueJson: json,
|
|
...(ttlMs != null ? { ttlMs } : {}),
|
|
};
|
|
}
|
|
|
|
function assertConsistentOptions(
|
|
pluginId: string,
|
|
namespace: string,
|
|
signature: StoreOptionSignature,
|
|
): void {
|
|
const key = `${pluginId}\0${namespace}`;
|
|
const existing = namespaceOptionSignatures.get(key);
|
|
if (!existing) {
|
|
namespaceOptionSignatures.set(key, signature);
|
|
return;
|
|
}
|
|
if (
|
|
existing.maxEntries !== signature.maxEntries ||
|
|
existing.defaultTtlMs !== signature.defaultTtlMs
|
|
) {
|
|
// A namespace is a shared storage contract. Reopening it with different
|
|
// limits would make eviction/TTL behavior depend on call order.
|
|
throw invalidInput(
|
|
`plugin state namespace ${namespace} for ${pluginId} was reopened with incompatible options`,
|
|
"open",
|
|
);
|
|
}
|
|
}
|
|
|
|
function createKeyedStoreForPluginId<T>(
|
|
pluginId: string,
|
|
options: OpenKeyedStoreOptions,
|
|
): PluginStateKeyedStore<T> {
|
|
const namespace = validateNamespace(options.namespace);
|
|
const maxEntries = validateMaxEntries(options.maxEntries);
|
|
const defaultTtlMs = validateOptionalTtlMs(options.defaultTtlMs);
|
|
const env = options.env;
|
|
assertConsistentOptions(pluginId, namespace, { maxEntries, defaultTtlMs });
|
|
|
|
return {
|
|
async register(key, value, opts) {
|
|
const params = prepareRegisterParams(key, value, defaultTtlMs, opts);
|
|
pluginStateRegister({
|
|
pluginId,
|
|
namespace,
|
|
key: params.key,
|
|
valueJson: params.valueJson,
|
|
maxEntries,
|
|
...(env ? { env } : {}),
|
|
...(params.ttlMs != null ? { ttlMs: params.ttlMs } : {}),
|
|
});
|
|
},
|
|
async registerIfAbsent(key, value, opts) {
|
|
const params = prepareRegisterParams(key, value, defaultTtlMs, opts);
|
|
return pluginStateRegisterIfAbsent({
|
|
pluginId,
|
|
namespace,
|
|
key: params.key,
|
|
valueJson: params.valueJson,
|
|
maxEntries,
|
|
...(env ? { env } : {}),
|
|
...(params.ttlMs != null ? { ttlMs: params.ttlMs } : {}),
|
|
});
|
|
},
|
|
async update(key, updateValue, opts) {
|
|
const normalizedKey = validateKey(key, "register");
|
|
return pluginStateUpdate({
|
|
pluginId,
|
|
namespace,
|
|
key: normalizedKey,
|
|
maxEntries,
|
|
updateValueJson: (current) => {
|
|
const next = updateValue(current as T | undefined);
|
|
if (next === undefined) {
|
|
return undefined;
|
|
}
|
|
const params = prepareRegisterParams(normalizedKey, next, defaultTtlMs, opts);
|
|
return {
|
|
valueJson: params.valueJson,
|
|
...(params.ttlMs != null ? { ttlMs: params.ttlMs } : {}),
|
|
};
|
|
},
|
|
...(env ? { env } : {}),
|
|
});
|
|
},
|
|
async lookup(key) {
|
|
const normalizedKey = validateKey(key, "lookup");
|
|
return pluginStateLookup({
|
|
pluginId,
|
|
namespace,
|
|
key: normalizedKey,
|
|
...(env ? { env } : {}),
|
|
}) as T | undefined;
|
|
},
|
|
async consume(key) {
|
|
const normalizedKey = validateKey(key, "consume");
|
|
return pluginStateConsume({
|
|
pluginId,
|
|
namespace,
|
|
key: normalizedKey,
|
|
...(env ? { env } : {}),
|
|
}) as T | undefined;
|
|
},
|
|
async delete(key) {
|
|
const normalizedKey = validateKey(key, "delete");
|
|
return pluginStateDelete({
|
|
pluginId,
|
|
namespace,
|
|
key: normalizedKey,
|
|
...(env ? { env } : {}),
|
|
});
|
|
},
|
|
async entries() {
|
|
return pluginStateEntries({
|
|
pluginId,
|
|
namespace,
|
|
...(env ? { env } : {}),
|
|
}) as PluginStateEntry<T>[];
|
|
},
|
|
async clear() {
|
|
pluginStateClear({ pluginId, namespace, ...(env ? { env } : {}) });
|
|
},
|
|
};
|
|
}
|
|
|
|
function createSyncKeyedStoreForPluginId<T>(
|
|
pluginId: string,
|
|
options: OpenKeyedStoreOptions,
|
|
): PluginStateSyncKeyedStore<T> {
|
|
const namespace = validateNamespace(options.namespace);
|
|
const maxEntries = validateMaxEntries(options.maxEntries);
|
|
const defaultTtlMs = validateOptionalTtlMs(options.defaultTtlMs);
|
|
const env = options.env;
|
|
assertConsistentOptions(pluginId, namespace, { maxEntries, defaultTtlMs });
|
|
|
|
return {
|
|
register(key, value, opts) {
|
|
const params = prepareRegisterParams(key, value, defaultTtlMs, opts);
|
|
pluginStateRegister({
|
|
pluginId,
|
|
namespace,
|
|
key: params.key,
|
|
valueJson: params.valueJson,
|
|
maxEntries,
|
|
...(env ? { env } : {}),
|
|
...(params.ttlMs != null ? { ttlMs: params.ttlMs } : {}),
|
|
});
|
|
},
|
|
registerIfAbsent(key, value, opts) {
|
|
const params = prepareRegisterParams(key, value, defaultTtlMs, opts);
|
|
return pluginStateRegisterIfAbsent({
|
|
pluginId,
|
|
namespace,
|
|
key: params.key,
|
|
valueJson: params.valueJson,
|
|
maxEntries,
|
|
...(env ? { env } : {}),
|
|
...(params.ttlMs != null ? { ttlMs: params.ttlMs } : {}),
|
|
});
|
|
},
|
|
update(key, updateValue, opts) {
|
|
const normalizedKey = validateKey(key, "register");
|
|
return pluginStateUpdate({
|
|
pluginId,
|
|
namespace,
|
|
key: normalizedKey,
|
|
maxEntries,
|
|
updateValueJson: (current) => {
|
|
const next = updateValue(current as T | undefined);
|
|
if (next === undefined) {
|
|
return undefined;
|
|
}
|
|
const params = prepareRegisterParams(normalizedKey, next, defaultTtlMs, opts);
|
|
return {
|
|
valueJson: params.valueJson,
|
|
...(params.ttlMs != null ? { ttlMs: params.ttlMs } : {}),
|
|
};
|
|
},
|
|
...(env ? { env } : {}),
|
|
});
|
|
},
|
|
lookup(key) {
|
|
const normalizedKey = validateKey(key, "lookup");
|
|
return pluginStateLookup({
|
|
pluginId,
|
|
namespace,
|
|
key: normalizedKey,
|
|
...(env ? { env } : {}),
|
|
}) as T | undefined;
|
|
},
|
|
consume(key) {
|
|
const normalizedKey = validateKey(key, "consume");
|
|
return pluginStateConsume({
|
|
pluginId,
|
|
namespace,
|
|
key: normalizedKey,
|
|
...(env ? { env } : {}),
|
|
}) as T | undefined;
|
|
},
|
|
delete(key) {
|
|
const normalizedKey = validateKey(key, "delete");
|
|
return pluginStateDelete({
|
|
pluginId,
|
|
namespace,
|
|
key: normalizedKey,
|
|
...(env ? { env } : {}),
|
|
});
|
|
},
|
|
entries() {
|
|
return pluginStateEntries({
|
|
pluginId,
|
|
namespace,
|
|
...(env ? { env } : {}),
|
|
}) as PluginStateEntry<T>[];
|
|
},
|
|
clear() {
|
|
pluginStateClear({ pluginId, namespace, ...(env ? { env } : {}) });
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Opens an async plugin-state namespace for a non-core plugin id. */
|
|
export function createPluginStateKeyedStore<T>(
|
|
pluginId: string,
|
|
options: OpenKeyedStoreOptions,
|
|
): PluginStateKeyedStore<T> {
|
|
if (pluginId.startsWith("core:")) {
|
|
throw invalidInput("Plugin ids starting with 'core:' are reserved for core consumers.", "open");
|
|
}
|
|
return createKeyedStoreForPluginId<T>(pluginId, options);
|
|
}
|
|
|
|
/** Opens a sync plugin-state namespace for a non-core plugin id. */
|
|
export function createPluginStateSyncKeyedStore<T>(
|
|
pluginId: string,
|
|
options: OpenKeyedStoreOptions,
|
|
): PluginStateSyncKeyedStore<T> {
|
|
if (pluginId.startsWith("core:")) {
|
|
throw invalidInput("Plugin ids starting with 'core:' are reserved for core consumers.", "open");
|
|
}
|
|
return createSyncKeyedStoreForPluginId<T>(pluginId, options);
|
|
}
|
|
|
|
/** Opens an async plugin-state namespace for a trusted core owner id. */
|
|
export function createCorePluginStateKeyedStore<T>(
|
|
options: OpenKeyedStoreOptions & { ownerId: `core:${string}` },
|
|
): PluginStateKeyedStore<T> {
|
|
return createKeyedStoreForPluginId<T>(options.ownerId, options);
|
|
}
|
|
|
|
/** Opens a sync plugin-state namespace for a trusted core owner id. */
|
|
export function createCorePluginStateSyncKeyedStore<T>(
|
|
options: OpenKeyedStoreOptions & { ownerId: `core:${string}` },
|
|
): PluginStateSyncKeyedStore<T> {
|
|
return createSyncKeyedStoreForPluginId<T>(options.ownerId, options);
|
|
}
|
|
|
|
/** Clears plugin-state rows and option signatures for tests. */
|
|
export function clearPluginStateStoreForTests(): void {
|
|
clearPluginStateDatabaseForTests();
|
|
namespaceOptionSignatures.clear();
|
|
}
|
|
|
|
/** Resets plugin-state module/database state for isolated tests. */
|
|
export function resetPluginStateStoreForTests(options: { closeDatabase?: boolean } = {}): void {
|
|
if (options.closeDatabase !== false) {
|
|
closePluginStateDatabase();
|
|
closeOpenClawStateDatabaseForTest();
|
|
}
|
|
namespaceOptionSignatures.clear();
|
|
}
|