feat(plugins): allow stackable memory plugins
Adds support for multiple memory plugins to run simultaneously. Previously, memory plugins were mutually exclusive - only one could be active at a time via the plugins.slots.memory config. Now, plugins.slots.memory can accept: - Single string: "memory-lancedb" (original behavior) - Array: ["memory-core", "memory-lancedb"] (stackable mode) - "none": disable all memory plugins Use case: Run memory-core for workspace file search alongside memory-lancedb for auto-recall/capture conversation memory. Changes: - types.plugins.ts: Updated PluginSlotsConfig type - zod-schema.ts: Updated schema to accept string | string[] - config-state.ts: Updated normalization and slot decision logic - validation.ts: Updated validation to check array entries - schema.ts: Updated help text
This commit is contained in:
parent
2044b3ca8d
commit
344f5abcb5
@ -551,7 +551,7 @@ const FIELD_HELP: Record<string, string> = {
|
||||
"plugins.load.paths": "Additional plugin files or directories to load.",
|
||||
"plugins.slots": "Select which plugins own exclusive slots (memory, etc.).",
|
||||
"plugins.slots.memory":
|
||||
'Select the active memory plugin by id, or "none" to disable memory plugins.',
|
||||
'Select the active memory plugin(s): a single id, an array of ids for stackable plugins, or "none" to disable.',
|
||||
"plugins.entries": "Per-plugin settings keyed by plugin id (enable/disable + config payloads).",
|
||||
"plugins.entries.*.enabled": "Overrides plugin enable/disable for this entry (restart required).",
|
||||
"plugins.entries.*.config": "Plugin-defined config payload (schema is provided by the plugin).",
|
||||
|
||||
@ -4,8 +4,13 @@ export type PluginEntryConfig = {
|
||||
};
|
||||
|
||||
export type PluginSlotsConfig = {
|
||||
/** Select which plugin owns the memory slot ("none" disables memory plugins). */
|
||||
memory?: string;
|
||||
/**
|
||||
* Select which plugin(s) own the memory slot.
|
||||
* - Single string: "memory-lancedb" - one plugin active
|
||||
* - Array: ["memory-core", "memory-lancedb"] - multiple plugins active (stackable)
|
||||
* - "none": disables all memory plugins
|
||||
*/
|
||||
memory?: string | string[];
|
||||
};
|
||||
|
||||
export type PluginsLoadConfig = {
|
||||
|
||||
@ -199,11 +199,21 @@ export function validateConfigObjectWithPlugins(raw: unknown):
|
||||
}
|
||||
|
||||
const memorySlot = normalizedPlugins.slots.memory;
|
||||
// Validate memory slot - can be string or array of strings
|
||||
if (typeof memorySlot === "string" && memorySlot.trim() && !knownIds.has(memorySlot)) {
|
||||
issues.push({
|
||||
path: "plugins.slots.memory",
|
||||
message: `plugin not found: ${memorySlot}`,
|
||||
});
|
||||
} else if (Array.isArray(memorySlot)) {
|
||||
for (const slotId of memorySlot) {
|
||||
if (typeof slotId === "string" && slotId.trim() && !knownIds.has(slotId)) {
|
||||
issues.push({
|
||||
path: "plugins.slots.memory",
|
||||
message: `plugin not found: ${slotId}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allowedChannels = new Set<string>(["defaults", ...CHANNEL_IDS]);
|
||||
|
||||
@ -493,7 +493,7 @@ export const ClawdbotSchema = z
|
||||
.optional(),
|
||||
slots: z
|
||||
.object({
|
||||
memory: z.string().optional(),
|
||||
memory: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
|
||||
@ -8,7 +8,7 @@ export type NormalizedPluginsConfig = {
|
||||
deny: string[];
|
||||
loadPaths: string[];
|
||||
slots: {
|
||||
memory?: string | null;
|
||||
memory?: string | string[] | null;
|
||||
};
|
||||
entries: Record<string, { enabled?: boolean; config?: unknown }>;
|
||||
};
|
||||
@ -20,7 +20,15 @@ const normalizeList = (value: unknown): string[] => {
|
||||
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
};
|
||||
|
||||
const normalizeSlotValue = (value: unknown): string | null | undefined => {
|
||||
const normalizeSlotValue = (value: unknown): string | string[] | null | undefined => {
|
||||
// Handle array of plugin ids (stackable mode)
|
||||
if (Array.isArray(value)) {
|
||||
const normalized = value
|
||||
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
||||
.filter(Boolean);
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
// Handle single string
|
||||
if (typeof value !== "string") return undefined;
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return undefined;
|
||||
@ -78,7 +86,9 @@ export function resolveEnableState(
|
||||
if (config.allow.length > 0 && !config.allow.includes(id)) {
|
||||
return { enabled: false, reason: "not in allowlist" };
|
||||
}
|
||||
if (config.slots.memory === id) {
|
||||
// Check if plugin is selected in memory slot (handles both string and array)
|
||||
const memorySlot = config.slots.memory;
|
||||
if (memorySlot === id || (Array.isArray(memorySlot) && memorySlot.includes(id))) {
|
||||
return { enabled: true };
|
||||
}
|
||||
const entry = config.entries[id];
|
||||
@ -100,13 +110,24 @@ export function resolveEnableState(
|
||||
export function resolveMemorySlotDecision(params: {
|
||||
id: string;
|
||||
kind?: string;
|
||||
slot: string | null | undefined;
|
||||
slot: string | string[] | null | undefined;
|
||||
selectedId: string | null;
|
||||
}): { enabled: boolean; reason?: string; selected?: boolean } {
|
||||
if (params.kind !== "memory") return { enabled: true };
|
||||
if (params.slot === null) {
|
||||
return { enabled: false, reason: "memory slot disabled" };
|
||||
}
|
||||
// Handle array of memory plugins (stackable mode)
|
||||
if (Array.isArray(params.slot)) {
|
||||
if (params.slot.includes(params.id)) {
|
||||
return { enabled: true, selected: true };
|
||||
}
|
||||
return {
|
||||
enabled: false,
|
||||
reason: `memory slot set to [${params.slot.join(", ")}]`,
|
||||
};
|
||||
}
|
||||
// Handle single string (exclusive mode - original behavior)
|
||||
if (typeof params.slot === "string") {
|
||||
if (params.slot === params.id) {
|
||||
return { enabled: true, selected: true };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user