feat(plugins): add memory slot plugin
This commit is contained in:
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
Docs: https://docs.clawd.bot
|
Docs: https://docs.clawd.bot
|
||||||
|
|
||||||
|
## 2026.1.18-3
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Plugins: add exclusive plugin slots with a dedicated memory slot selector.
|
||||||
|
- Memory: ship core memory tools + CLI as the bundled `memory-core` plugin.
|
||||||
|
- Docs: document plugin slots and memory plugin behavior.
|
||||||
|
|
||||||
## 2026.1.18-2
|
## 2026.1.18-2
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
@@ -25,6 +32,7 @@ Docs: https://docs.clawd.bot
|
|||||||
- macOS: bundle Textual resources in packaged app builds to avoid code block crashes. (#1006)
|
- macOS: bundle Textual resources in packaged app builds to avoid code block crashes. (#1006)
|
||||||
- Tools: return a companion-app-required message when `system.run` is requested without a supporting node.
|
- Tools: return a companion-app-required message when `system.run` is requested without a supporting node.
|
||||||
- Discord: only emit slow listener warnings after 30s.
|
- Discord: only emit slow listener warnings after 30s.
|
||||||
|
|
||||||
## 2026.1.17-3
|
## 2026.1.17-3
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ read_when:
|
|||||||
# `clawdbot memory`
|
# `clawdbot memory`
|
||||||
|
|
||||||
Memory search tools (semantic memory status/index/search).
|
Memory search tools (semantic memory status/index/search).
|
||||||
|
Provided by the active memory plugin (default: `memory-core`; use `plugins.slots.memory = "none"` to disable).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
- Memory concept: [Memory](/concepts/memory)
|
- Memory concept: [Memory](/concepts/memory)
|
||||||
|
- Plugins: [Plugins](/plugins)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ read_when:
|
|||||||
Clawdbot memory is **plain Markdown in the agent workspace**. The files are the
|
Clawdbot memory is **plain Markdown in the agent workspace**. The files are the
|
||||||
source of truth; the model only "remembers" what gets written to disk.
|
source of truth; the model only "remembers" what gets written to disk.
|
||||||
|
|
||||||
|
Memory search tools are provided by the active memory plugin (default:
|
||||||
|
`memory-core`). Disable memory plugins with `plugins.slots.memory = "none"`.
|
||||||
|
|
||||||
## Memory files (Markdown)
|
## Memory files (Markdown)
|
||||||
|
|
||||||
The default workspace layout uses two memory layers:
|
The default workspace layout uses two memory layers:
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ See [Voice Call](/plugins/voice-call) for a concrete example plugin.
|
|||||||
## Available plugins (official)
|
## Available plugins (official)
|
||||||
|
|
||||||
- Microsoft Teams is plugin-only as of 2026.1.15; install `@clawdbot/msteams` if you use Teams.
|
- Microsoft Teams is plugin-only as of 2026.1.15; install `@clawdbot/msteams` if you use Teams.
|
||||||
|
- Memory (Core) — bundled memory search plugin (enabled by default via `plugins.slots.memory`)
|
||||||
- [Voice Call](/plugins/voice-call) — `@clawdbot/voice-call`
|
- [Voice Call](/plugins/voice-call) — `@clawdbot/voice-call`
|
||||||
- [Zalo Personal](/plugins/zalouser) — `@clawdbot/zalouser`
|
- [Zalo Personal](/plugins/zalouser) — `@clawdbot/zalouser`
|
||||||
- [Matrix](/channels/matrix) — `@clawdbot/matrix`
|
- [Matrix](/channels/matrix) — `@clawdbot/matrix`
|
||||||
@@ -137,6 +138,24 @@ Fields:
|
|||||||
|
|
||||||
Config changes **require a gateway restart**.
|
Config changes **require a gateway restart**.
|
||||||
|
|
||||||
|
## Plugin slots (exclusive categories)
|
||||||
|
|
||||||
|
Some plugin categories are **exclusive** (only one active at a time). Use
|
||||||
|
`plugins.slots` to select which plugin owns the slot:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
slots: {
|
||||||
|
memory: "memory-core" // or "none" to disable memory plugins
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If multiple plugins declare `kind: "memory"`, only the selected one loads. Others
|
||||||
|
are disabled with diagnostics.
|
||||||
|
|
||||||
## Control UI (schema + labels)
|
## Control UI (schema + labels)
|
||||||
|
|
||||||
The Control UI uses `config.schema` (JSON Schema + `uiHints`) to render better forms.
|
The Control UI uses `config.schema` (JSON Schema + `uiHints`) to render better forms.
|
||||||
|
|||||||
37
extensions/memory-core/index.ts
Normal file
37
extensions/memory-core/index.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { ClawdbotPluginApi } from "../../src/plugins/types.js";
|
||||||
|
|
||||||
|
import { createMemoryGetTool, createMemorySearchTool } from "../../src/agents/tools/memory-tool.js";
|
||||||
|
import { registerMemoryCli } from "../../src/cli/memory-cli.js";
|
||||||
|
|
||||||
|
const memoryCorePlugin = {
|
||||||
|
id: "memory-core",
|
||||||
|
name: "Memory (Core)",
|
||||||
|
description: "File-backed memory search tools and CLI",
|
||||||
|
kind: "memory",
|
||||||
|
register(api: ClawdbotPluginApi) {
|
||||||
|
api.registerTool(
|
||||||
|
(ctx) => {
|
||||||
|
const memorySearchTool = createMemorySearchTool({
|
||||||
|
config: ctx.config,
|
||||||
|
agentSessionKey: ctx.sessionKey,
|
||||||
|
});
|
||||||
|
const memoryGetTool = createMemoryGetTool({
|
||||||
|
config: ctx.config,
|
||||||
|
agentSessionKey: ctx.sessionKey,
|
||||||
|
});
|
||||||
|
if (!memorySearchTool || !memoryGetTool) return null;
|
||||||
|
return [memorySearchTool, memoryGetTool];
|
||||||
|
},
|
||||||
|
{ names: ["memory_search", "memory_get"] },
|
||||||
|
);
|
||||||
|
|
||||||
|
api.registerCli(
|
||||||
|
({ program }) => {
|
||||||
|
registerMemoryCli(program);
|
||||||
|
},
|
||||||
|
{ commands: ["memory"] },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memoryCorePlugin;
|
||||||
@@ -9,7 +9,6 @@ import type { AnyAgentTool } from "./tools/common.js";
|
|||||||
import { createCronTool } from "./tools/cron-tool.js";
|
import { createCronTool } from "./tools/cron-tool.js";
|
||||||
import { createGatewayTool } from "./tools/gateway-tool.js";
|
import { createGatewayTool } from "./tools/gateway-tool.js";
|
||||||
import { createImageTool } from "./tools/image-tool.js";
|
import { createImageTool } from "./tools/image-tool.js";
|
||||||
import { createMemoryGetTool, createMemorySearchTool } from "./tools/memory-tool.js";
|
|
||||||
import { createMessageTool } from "./tools/message-tool.js";
|
import { createMessageTool } from "./tools/message-tool.js";
|
||||||
import { createNodesTool } from "./tools/nodes-tool.js";
|
import { createNodesTool } from "./tools/nodes-tool.js";
|
||||||
import { createSessionStatusTool } from "./tools/session-status-tool.js";
|
import { createSessionStatusTool } from "./tools/session-status-tool.js";
|
||||||
@@ -49,14 +48,6 @@ export function createClawdbotTools(options?: {
|
|||||||
sandboxRoot: options?.sandboxRoot,
|
sandboxRoot: options?.sandboxRoot,
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
const memorySearchTool = createMemorySearchTool({
|
|
||||||
config: options?.config,
|
|
||||||
agentSessionKey: options?.agentSessionKey,
|
|
||||||
});
|
|
||||||
const memoryGetTool = createMemoryGetTool({
|
|
||||||
config: options?.config,
|
|
||||||
agentSessionKey: options?.agentSessionKey,
|
|
||||||
});
|
|
||||||
const webSearchTool = createWebSearchTool({
|
const webSearchTool = createWebSearchTool({
|
||||||
config: options?.config,
|
config: options?.config,
|
||||||
sandboxed: options?.sandboxed,
|
sandboxed: options?.sandboxed,
|
||||||
@@ -119,7 +110,6 @@ export function createClawdbotTools(options?: {
|
|||||||
agentSessionKey: options?.agentSessionKey,
|
agentSessionKey: options?.agentSessionKey,
|
||||||
config: options?.config,
|
config: options?.config,
|
||||||
}),
|
}),
|
||||||
...(memorySearchTool && memoryGetTool ? [memorySearchTool, memoryGetTool] : []),
|
|
||||||
...(webSearchTool ? [webSearchTool] : []),
|
...(webSearchTool ? [webSearchTool] : []),
|
||||||
...(webFetchTool ? [webFetchTool] : []),
|
...(webFetchTool ? [webFetchTool] : []),
|
||||||
...(imageTool ? [imageTool] : []),
|
...(imageTool ? [imageTool] : []),
|
||||||
|
|||||||
@@ -117,8 +117,6 @@ describe("createClawdbotCodingTools", () => {
|
|||||||
"sessions_send",
|
"sessions_send",
|
||||||
"sessions_spawn",
|
"sessions_spawn",
|
||||||
"session_status",
|
"session_status",
|
||||||
"memory_search",
|
|
||||||
"memory_get",
|
|
||||||
"image",
|
"image",
|
||||||
]);
|
]);
|
||||||
const offenders: Array<{
|
const offenders: Array<{
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { registerGatewayCli } from "../gateway-cli.js";
|
|||||||
import { registerHooksCli } from "../hooks-cli.js";
|
import { registerHooksCli } from "../hooks-cli.js";
|
||||||
import { registerWebhooksCli } from "../webhooks-cli.js";
|
import { registerWebhooksCli } from "../webhooks-cli.js";
|
||||||
import { registerLogsCli } from "../logs-cli.js";
|
import { registerLogsCli } from "../logs-cli.js";
|
||||||
import { registerMemoryCli } from "../memory-cli.js";
|
|
||||||
import { registerModelsCli } from "../models-cli.js";
|
import { registerModelsCli } from "../models-cli.js";
|
||||||
import { registerNodesCli } from "../nodes-cli.js";
|
import { registerNodesCli } from "../nodes-cli.js";
|
||||||
import { registerPairingCli } from "../pairing-cli.js";
|
import { registerPairingCli } from "../pairing-cli.js";
|
||||||
@@ -26,7 +25,6 @@ export function registerSubCliCommands(program: Command) {
|
|||||||
registerDaemonCli(program);
|
registerDaemonCli(program);
|
||||||
registerGatewayCli(program);
|
registerGatewayCli(program);
|
||||||
registerLogsCli(program);
|
registerLogsCli(program);
|
||||||
registerMemoryCli(program);
|
|
||||||
registerModelsCli(program);
|
registerModelsCli(program);
|
||||||
registerNodesCli(program);
|
registerNodesCli(program);
|
||||||
registerSandboxCli(program);
|
registerSandboxCli(program);
|
||||||
|
|||||||
@@ -277,6 +277,8 @@ const FIELD_LABELS: Record<string, string> = {
|
|||||||
"plugins.allow": "Plugin Allowlist",
|
"plugins.allow": "Plugin Allowlist",
|
||||||
"plugins.deny": "Plugin Denylist",
|
"plugins.deny": "Plugin Denylist",
|
||||||
"plugins.load.paths": "Plugin Load Paths",
|
"plugins.load.paths": "Plugin Load Paths",
|
||||||
|
"plugins.slots": "Plugin Slots",
|
||||||
|
"plugins.slots.memory": "Memory Plugin",
|
||||||
"plugins.entries": "Plugin Entries",
|
"plugins.entries": "Plugin Entries",
|
||||||
"plugins.entries.*.enabled": "Plugin Enabled",
|
"plugins.entries.*.enabled": "Plugin Enabled",
|
||||||
"plugins.entries.*.config": "Plugin Config",
|
"plugins.entries.*.config": "Plugin Config",
|
||||||
@@ -413,6 +415,9 @@ const FIELD_HELP: Record<string, string> = {
|
|||||||
"plugins.allow": "Optional allowlist of plugin ids; when set, only listed plugins load.",
|
"plugins.allow": "Optional allowlist of plugin ids; when set, only listed plugins load.",
|
||||||
"plugins.deny": "Optional denylist of plugin ids; deny wins over allowlist.",
|
"plugins.deny": "Optional denylist of plugin ids; deny wins over allowlist.",
|
||||||
"plugins.load.paths": "Additional plugin files or directories to load.",
|
"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.',
|
||||||
"plugins.entries": "Per-plugin settings keyed by plugin id (enable/disable + config payloads).",
|
"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.*.enabled": "Overrides plugin enable/disable for this entry (restart required).",
|
||||||
"plugins.entries.*.config": "Plugin-defined config payload (schema is provided by the plugin).",
|
"plugins.entries.*.config": "Plugin-defined config payload (schema is provided by the plugin).",
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ export type PluginEntryConfig = {
|
|||||||
config?: Record<string, unknown>;
|
config?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PluginSlotsConfig = {
|
||||||
|
/** Select which plugin owns the memory slot ("none" disables memory plugins). */
|
||||||
|
memory?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type PluginsLoadConfig = {
|
export type PluginsLoadConfig = {
|
||||||
/** Additional plugin/extension paths to load. */
|
/** Additional plugin/extension paths to load. */
|
||||||
paths?: string[];
|
paths?: string[];
|
||||||
@@ -25,6 +30,7 @@ export type PluginsConfig = {
|
|||||||
/** Optional plugin denylist (plugin ids). */
|
/** Optional plugin denylist (plugin ids). */
|
||||||
deny?: string[];
|
deny?: string[];
|
||||||
load?: PluginsLoadConfig;
|
load?: PluginsLoadConfig;
|
||||||
|
slots?: PluginSlotsConfig;
|
||||||
entries?: Record<string, PluginEntryConfig>;
|
entries?: Record<string, PluginEntryConfig>;
|
||||||
installs?: Record<string, PluginInstallRecord>;
|
installs?: Record<string, PluginInstallRecord>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -324,6 +324,11 @@ export const ClawdbotSchema = z
|
|||||||
paths: z.array(z.string()).optional(),
|
paths: z.array(z.string()).optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
slots: z
|
||||||
|
.object({
|
||||||
|
memory: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
entries: z
|
entries: z
|
||||||
.record(
|
.record(
|
||||||
z.string(),
|
z.string(),
|
||||||
|
|||||||
@@ -74,6 +74,31 @@ describe("loadClawdbotPlugins", () => {
|
|||||||
const enabled = enabledRegistry.plugins.find((entry) => entry.id === "bundled");
|
const enabled = enabledRegistry.plugins.find((entry) => entry.id === "bundled");
|
||||||
expect(enabled?.status).toBe("loaded");
|
expect(enabled?.status).toBe("loaded");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("enables bundled memory plugin when selected by slot", () => {
|
||||||
|
const bundledDir = makeTempDir();
|
||||||
|
const bundledPath = path.join(bundledDir, "memory-core.ts");
|
||||||
|
fs.writeFileSync(
|
||||||
|
bundledPath,
|
||||||
|
'export default { id: "memory-core", kind: "memory", register() {} };',
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = bundledDir;
|
||||||
|
|
||||||
|
const registry = loadClawdbotPlugins({
|
||||||
|
cache: false,
|
||||||
|
config: {
|
||||||
|
plugins: {
|
||||||
|
slots: {
|
||||||
|
memory: "memory-core",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const memory = registry.plugins.find((entry) => entry.id === "memory-core");
|
||||||
|
expect(memory?.status).toBe("loaded");
|
||||||
|
});
|
||||||
it("loads plugins from config paths", () => {
|
it("loads plugins from config paths", () => {
|
||||||
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
|
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
|
||||||
const plugin = writePlugin({
|
const plugin = writePlugin({
|
||||||
@@ -237,6 +262,54 @@ describe("loadClawdbotPlugins", () => {
|
|||||||
expect(disabled?.status).toBe("disabled");
|
expect(disabled?.status).toBe("disabled");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("enforces memory slot selection", () => {
|
||||||
|
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
|
||||||
|
const memoryA = writePlugin({
|
||||||
|
id: "memory-a",
|
||||||
|
body: `export default { id: "memory-a", kind: "memory", register() {} };`,
|
||||||
|
});
|
||||||
|
const memoryB = writePlugin({
|
||||||
|
id: "memory-b",
|
||||||
|
body: `export default { id: "memory-b", kind: "memory", register() {} };`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const registry = loadClawdbotPlugins({
|
||||||
|
cache: false,
|
||||||
|
config: {
|
||||||
|
plugins: {
|
||||||
|
load: { paths: [memoryA.file, memoryB.file] },
|
||||||
|
slots: { memory: "memory-b" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const a = registry.plugins.find((entry) => entry.id === "memory-a");
|
||||||
|
const b = registry.plugins.find((entry) => entry.id === "memory-b");
|
||||||
|
expect(b?.status).toBe("loaded");
|
||||||
|
expect(a?.status).toBe("disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables memory plugins when slot is none", () => {
|
||||||
|
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
|
||||||
|
const memory = writePlugin({
|
||||||
|
id: "memory-off",
|
||||||
|
body: `export default { id: "memory-off", kind: "memory", register() {} };`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const registry = loadClawdbotPlugins({
|
||||||
|
cache: false,
|
||||||
|
config: {
|
||||||
|
plugins: {
|
||||||
|
load: { paths: [memory.file] },
|
||||||
|
slots: { memory: "none" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const entry = registry.plugins.find((item) => item.id === "memory-off");
|
||||||
|
expect(entry?.status).toBe("disabled");
|
||||||
|
});
|
||||||
|
|
||||||
it("prefers higher-precedence plugins with the same id", () => {
|
it("prefers higher-precedence plugins with the same id", () => {
|
||||||
const bundledDir = makeTempDir();
|
const bundledDir = makeTempDir();
|
||||||
fs.writeFileSync(path.join(bundledDir, "shadow.js"), "export default function () {}", "utf-8");
|
fs.writeFileSync(path.join(bundledDir, "shadow.js"), "export default function () {}", "utf-8");
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ type NormalizedPluginsConfig = {
|
|||||||
allow: string[];
|
allow: string[];
|
||||||
deny: string[];
|
deny: string[];
|
||||||
loadPaths: string[];
|
loadPaths: string[];
|
||||||
|
slots: {
|
||||||
|
memory?: string | null;
|
||||||
|
};
|
||||||
entries: Record<string, { enabled?: boolean; config?: Record<string, unknown> }>;
|
entries: Record<string, { enabled?: boolean; config?: Record<string, unknown> }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,6 +46,14 @@ const normalizeList = (value: unknown): string[] => {
|
|||||||
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeSlotValue = (value: unknown): string | null | undefined => {
|
||||||
|
if (typeof value !== "string") return undefined;
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return undefined;
|
||||||
|
if (trimmed.toLowerCase() === "none") return null;
|
||||||
|
return trimmed;
|
||||||
|
};
|
||||||
|
|
||||||
const normalizePluginEntries = (entries: unknown): NormalizedPluginsConfig["entries"] => {
|
const normalizePluginEntries = (entries: unknown): NormalizedPluginsConfig["entries"] => {
|
||||||
if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
|
if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
|
||||||
return {};
|
return {};
|
||||||
@@ -67,11 +78,15 @@ const normalizePluginEntries = (entries: unknown): NormalizedPluginsConfig["entr
|
|||||||
};
|
};
|
||||||
|
|
||||||
const normalizePluginsConfig = (config?: ClawdbotConfig["plugins"]): NormalizedPluginsConfig => {
|
const normalizePluginsConfig = (config?: ClawdbotConfig["plugins"]): NormalizedPluginsConfig => {
|
||||||
|
const memorySlot = normalizeSlotValue(config?.slots?.memory);
|
||||||
return {
|
return {
|
||||||
enabled: config?.enabled !== false,
|
enabled: config?.enabled !== false,
|
||||||
allow: normalizeList(config?.allow),
|
allow: normalizeList(config?.allow),
|
||||||
deny: normalizeList(config?.deny),
|
deny: normalizeList(config?.deny),
|
||||||
loadPaths: normalizeList(config?.load?.paths),
|
loadPaths: normalizeList(config?.load?.paths),
|
||||||
|
slots: {
|
||||||
|
memory: memorySlot ?? "memory-core",
|
||||||
|
},
|
||||||
entries: normalizePluginEntries(config?.entries),
|
entries: normalizePluginEntries(config?.entries),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -84,6 +99,34 @@ function buildCacheKey(params: {
|
|||||||
return `${workspaceKey}::${JSON.stringify(params.plugins)}`;
|
return `${workspaceKey}::${JSON.stringify(params.plugins)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveMemorySlotDecision(params: {
|
||||||
|
id: string;
|
||||||
|
kind?: string;
|
||||||
|
slot: 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" };
|
||||||
|
}
|
||||||
|
if (typeof params.slot === "string") {
|
||||||
|
if (params.slot === params.id) {
|
||||||
|
return { enabled: true, selected: true };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
enabled: false,
|
||||||
|
reason: `memory slot set to "${params.slot}"`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (params.selectedId && params.selectedId !== params.id) {
|
||||||
|
return {
|
||||||
|
enabled: false,
|
||||||
|
reason: `memory slot already filled by "${params.selectedId}"`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { enabled: true, selected: true };
|
||||||
|
}
|
||||||
|
|
||||||
function resolveEnableState(
|
function resolveEnableState(
|
||||||
id: string,
|
id: string,
|
||||||
origin: PluginRecord["origin"],
|
origin: PluginRecord["origin"],
|
||||||
@@ -98,6 +141,9 @@ function resolveEnableState(
|
|||||||
if (config.allow.length > 0 && !config.allow.includes(id)) {
|
if (config.allow.length > 0 && !config.allow.includes(id)) {
|
||||||
return { enabled: false, reason: "not in allowlist" };
|
return { enabled: false, reason: "not in allowlist" };
|
||||||
}
|
}
|
||||||
|
if (config.slots.memory === id) {
|
||||||
|
return { enabled: true };
|
||||||
|
}
|
||||||
const entry = config.entries[id];
|
const entry = config.entries[id];
|
||||||
if (entry?.enabled === true) {
|
if (entry?.enabled === true) {
|
||||||
return { enabled: true };
|
return { enabled: true };
|
||||||
@@ -245,6 +291,9 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi
|
|||||||
});
|
});
|
||||||
|
|
||||||
const seenIds = new Map<string, PluginRecord["origin"]>();
|
const seenIds = new Map<string, PluginRecord["origin"]>();
|
||||||
|
const memorySlot = normalized.slots.memory;
|
||||||
|
let selectedMemoryPluginId: string | null = null;
|
||||||
|
let memorySlotMatched = false;
|
||||||
|
|
||||||
for (const candidate of discovery.candidates) {
|
for (const candidate of discovery.candidates) {
|
||||||
const existingOrigin = seenIds.get(candidate.idHint);
|
const existingOrigin = seenIds.get(candidate.idHint);
|
||||||
@@ -321,6 +370,7 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi
|
|||||||
record.name = definition?.name ?? record.name;
|
record.name = definition?.name ?? record.name;
|
||||||
record.description = definition?.description ?? record.description;
|
record.description = definition?.description ?? record.description;
|
||||||
record.version = definition?.version ?? record.version;
|
record.version = definition?.version ?? record.version;
|
||||||
|
record.kind = definition?.kind;
|
||||||
record.configSchema = Boolean(definition?.configSchema);
|
record.configSchema = Boolean(definition?.configSchema);
|
||||||
record.configUiHints =
|
record.configUiHints =
|
||||||
definition?.configSchema &&
|
definition?.configSchema &&
|
||||||
@@ -345,6 +395,30 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi
|
|||||||
>)
|
>)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
if (record.kind === "memory" && memorySlot === record.id) {
|
||||||
|
memorySlotMatched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const memoryDecision = resolveMemorySlotDecision({
|
||||||
|
id: record.id,
|
||||||
|
kind: record.kind,
|
||||||
|
slot: memorySlot,
|
||||||
|
selectedId: selectedMemoryPluginId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!memoryDecision.enabled) {
|
||||||
|
record.enabled = false;
|
||||||
|
record.status = "disabled";
|
||||||
|
record.error = memoryDecision.reason;
|
||||||
|
registry.plugins.push(record);
|
||||||
|
seenIds.set(candidate.idHint, candidate.origin);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memoryDecision.selected && record.kind === "memory") {
|
||||||
|
selectedMemoryPluginId = record.id;
|
||||||
|
}
|
||||||
|
|
||||||
const validatedConfig = validatePluginConfig({
|
const validatedConfig = validatePluginConfig({
|
||||||
schema: definition?.configSchema,
|
schema: definition?.configSchema,
|
||||||
value: entry?.config,
|
value: entry?.config,
|
||||||
@@ -409,6 +483,13 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof memorySlot === "string" && !memorySlotMatched) {
|
||||||
|
registry.diagnostics.push({
|
||||||
|
level: "warn",
|
||||||
|
message: `memory slot plugin not found or not marked as memory: ${memorySlot}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (cacheEnabled) {
|
if (cacheEnabled) {
|
||||||
registryCache.set(cacheKey, registry);
|
registryCache.set(cacheKey, registry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import type {
|
|||||||
PluginDiagnostic,
|
PluginDiagnostic,
|
||||||
PluginLogger,
|
PluginLogger,
|
||||||
PluginOrigin,
|
PluginOrigin,
|
||||||
|
PluginKind,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
export type PluginToolRegistration = {
|
export type PluginToolRegistration = {
|
||||||
@@ -65,6 +66,7 @@ export type PluginRecord = {
|
|||||||
name: string;
|
name: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
kind?: PluginKind;
|
||||||
source: string;
|
source: string;
|
||||||
origin: PluginOrigin;
|
origin: PluginOrigin;
|
||||||
workspaceDir?: string;
|
workspaceDir?: string;
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export type PluginConfigUiHint = {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PluginKind = "memory";
|
||||||
|
|
||||||
export type PluginConfigValidation =
|
export type PluginConfigValidation =
|
||||||
| { ok: true; value?: unknown }
|
| { ok: true; value?: unknown }
|
||||||
| { ok: false; errors: string[] };
|
| { ok: false; errors: string[] };
|
||||||
@@ -144,6 +146,7 @@ export type ClawdbotPluginDefinition = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
|
kind?: PluginKind;
|
||||||
configSchema?: ClawdbotPluginConfigSchema;
|
configSchema?: ClawdbotPluginConfigSchema;
|
||||||
register?: (api: ClawdbotPluginApi) => void | Promise<void>;
|
register?: (api: ClawdbotPluginApi) => void | Promise<void>;
|
||||||
activate?: (api: ClawdbotPluginApi) => void | Promise<void>;
|
activate?: (api: ClawdbotPluginApi) => void | Promise<void>;
|
||||||
|
|||||||
Reference in New Issue
Block a user