@basicmemory/openclaw-basic-memory 0.2.3 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +49 -19
  2. package/bm-client.ts +55 -21
  3. package/commands/cli.ts +1 -2
  4. package/commands/skills.ts +71 -10
  5. package/commands/slash.ts +4 -2
  6. package/config.ts +0 -16
  7. package/context-engine/basic-memory-context-engine.ts +22 -90
  8. package/dist/bm-client.d.ts +179 -0
  9. package/dist/bm-client.js +582 -0
  10. package/dist/commands/cli.d.ts +4 -0
  11. package/dist/commands/cli.js +116 -0
  12. package/dist/commands/skills.d.ts +2 -0
  13. package/dist/commands/skills.js +86 -0
  14. package/dist/commands/slash.d.ts +3 -0
  15. package/dist/commands/slash.js +92 -0
  16. package/dist/config.d.ts +17 -0
  17. package/dist/config.js +99 -0
  18. package/dist/context-engine/basic-memory-context-engine.d.ts +34 -0
  19. package/dist/context-engine/basic-memory-context-engine.js +176 -0
  20. package/dist/hooks/capture.d.ts +24 -0
  21. package/dist/hooks/capture.js +88 -0
  22. package/dist/hooks/recall.d.ts +24 -0
  23. package/dist/hooks/recall.js +56 -0
  24. package/dist/index.d.ts +9 -0
  25. package/dist/index.js +135 -0
  26. package/dist/logger.d.ts +13 -0
  27. package/dist/logger.js +34 -0
  28. package/dist/schema/conversation-schema.d.ts +5 -0
  29. package/dist/schema/conversation-schema.js +33 -0
  30. package/dist/schema/task-schema.d.ts +5 -0
  31. package/dist/schema/task-schema.js +34 -0
  32. package/dist/tools/build-context.d.ts +3 -0
  33. package/dist/tools/build-context.js +100 -0
  34. package/dist/tools/delete-note.d.ts +3 -0
  35. package/dist/tools/delete-note.js +52 -0
  36. package/dist/tools/edit-note.d.ts +3 -0
  37. package/dist/tools/edit-note.js +81 -0
  38. package/dist/tools/list-memory-projects.d.ts +3 -0
  39. package/dist/tools/list-memory-projects.js +86 -0
  40. package/dist/tools/list-workspaces.d.ts +3 -0
  41. package/dist/tools/list-workspaces.js +64 -0
  42. package/dist/tools/memory-provider.d.ts +15 -0
  43. package/dist/tools/memory-provider.js +274 -0
  44. package/dist/tools/move-note.d.ts +3 -0
  45. package/dist/tools/move-note.js +56 -0
  46. package/dist/tools/read-note.d.ts +3 -0
  47. package/dist/tools/read-note.js +57 -0
  48. package/dist/tools/schema-diff.d.ts +3 -0
  49. package/dist/tools/schema-diff.js +81 -0
  50. package/dist/tools/schema-infer.d.ts +3 -0
  51. package/dist/tools/schema-infer.js +66 -0
  52. package/dist/tools/schema-validate.d.ts +3 -0
  53. package/dist/tools/schema-validate.js +73 -0
  54. package/dist/tools/search-notes.d.ts +3 -0
  55. package/dist/tools/search-notes.js +97 -0
  56. package/dist/tools/write-note.d.ts +3 -0
  57. package/dist/tools/write-note.js +80 -0
  58. package/index.ts +27 -17
  59. package/openclaw.plugin.json +27 -20
  60. package/package.json +27 -8
  61. package/scripts/fetch-skills.ts +62 -67
  62. package/scripts/setup-bm.sh +27 -10
  63. package/skills/manifest.json +25 -0
  64. package/skills/memory-capture/SKILL.md +275 -0
  65. package/skills/memory-ci-capture/SKILL.md +56 -0
  66. package/skills/memory-continue/SKILL.md +148 -0
  67. package/skills/memory-curate/SKILL.md +243 -0
  68. package/skills/memory-literary-analysis/SKILL.md +15 -12
  69. package/skills/memory-notes/SKILL.md +22 -6
  70. package/skills/memory-onboarding/SKILL.md +142 -0
  71. package/skills/memory-onboarding/evals/evals.json +29 -0
  72. package/skills/memory-onboarding/references/assistant-setup.md +64 -0
  73. package/skills/memory-onboarding/references/conventions.md +118 -0
  74. package/skills/memory-onboarding/references/domain-playbooks.md +159 -0
  75. package/skills/memory-onboarding/references/schema-guide.md +136 -0
  76. package/skills/memory-schema/SKILL.md +7 -5
  77. package/skills/memory-tasks/SKILL.md +1 -1
  78. package/tools/build-context.ts +1 -1
  79. package/tools/delete-note.ts +1 -1
  80. package/tools/edit-note.ts +1 -1
  81. package/tools/list-memory-projects.ts +3 -2
  82. package/tools/list-workspaces.ts +1 -1
  83. package/tools/memory-provider.ts +1 -1
  84. package/tools/move-note.ts +1 -1
  85. package/tools/read-note.ts +1 -1
  86. package/tools/schema-diff.ts +1 -1
  87. package/tools/schema-infer.ts +1 -1
  88. package/tools/schema-validate.ts +1 -1
  89. package/tools/search-notes.ts +1 -1
  90. package/tools/write-note.ts +1 -1
  91. package/context-engine/basic-memory-context-engine.test.ts +0 -398
@@ -0,0 +1,56 @@
1
+ import { log } from "../logger.js";
2
+ /**
3
+ * Format recalled context from active tasks and recent activity.
4
+ * Returns empty string if nothing was found.
5
+ */
6
+ export function formatRecallContext(tasks, recent, prompt) {
7
+ const sections = [];
8
+ if (tasks.length > 0) {
9
+ const taskLines = tasks.map((t) => {
10
+ const preview = t.content.length > 120 ? `${t.content.slice(0, 120)}...` : t.content;
11
+ return `- **${t.title}** — ${preview}`;
12
+ });
13
+ sections.push(`## Active Tasks\n${taskLines.join("\n")}`);
14
+ }
15
+ if (recent.length > 0) {
16
+ const recentLines = recent.map((r) => `- ${r.title} (${r.file_path})`);
17
+ sections.push(`## Recent Activity\n${recentLines.join("\n")}`);
18
+ }
19
+ if (sections.length === 0)
20
+ return "";
21
+ return `${sections.join("\n\n")}\n\n---\n${prompt}`;
22
+ }
23
+ export async function loadRecallState(client, cfg) {
24
+ const [tasks, recent] = await Promise.all([
25
+ client.search(undefined, 5, undefined, {
26
+ note_types: ["Task"],
27
+ status: "active",
28
+ }),
29
+ client.recentActivity("1d"),
30
+ ]);
31
+ const context = formatRecallContext(tasks, recent, cfg.recallPrompt);
32
+ if (!context)
33
+ return null;
34
+ return { tasks, recent, context };
35
+ }
36
+ /**
37
+ * Build the pre-turn recall handler.
38
+ *
39
+ * On agent_start, queries active tasks and recent activity from Basic Memory,
40
+ * then returns formatted context for injection into the conversation.
41
+ */
42
+ export function buildRecallHandler(client, cfg) {
43
+ return async (_event) => {
44
+ try {
45
+ const recall = await loadRecallState(client, cfg);
46
+ if (!recall)
47
+ return {};
48
+ log.debug(`recall: ${recall.tasks.length} active tasks, ${recall.recent.length} recent items`);
49
+ return { context: recall.context };
50
+ }
51
+ catch (err) {
52
+ log.error("recall failed", err);
53
+ return {};
54
+ }
55
+ };
56
+ }
@@ -0,0 +1,9 @@
1
+ export declare function isCommandAvailable(command: string): boolean;
2
+ declare const _default: {
3
+ id: string;
4
+ name: string;
5
+ description: string;
6
+ configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
7
+ register: NonNullable<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition["register"]>;
8
+ } & Pick<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
9
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,135 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
+ import { BmClient } from "./bm-client.js";
4
+ import { registerCli } from "./commands/cli.js";
5
+ import { registerSkillCommands } from "./commands/skills.js";
6
+ import { registerCommands } from "./commands/slash.js";
7
+ import { basicMemoryConfigSchema, parseConfig, resolveProjectPath, } from "./config.js";
8
+ import { BasicMemoryContextEngine } from "./context-engine/basic-memory-context-engine.js";
9
+ import { initLogger, log } from "./logger.js";
10
+ import { CONVERSATION_SCHEMA_CONTENT } from "./schema/conversation-schema.js";
11
+ import { TASK_SCHEMA_CONTENT } from "./schema/task-schema.js";
12
+ import { registerContextTool } from "./tools/build-context.js";
13
+ import { registerDeleteTool } from "./tools/delete-note.js";
14
+ import { registerEditTool } from "./tools/edit-note.js";
15
+ import { registerProjectListTool } from "./tools/list-memory-projects.js";
16
+ import { registerWorkspaceListTool } from "./tools/list-workspaces.js";
17
+ import { registerMemoryProvider, setWorkspaceDir, } from "./tools/memory-provider.js";
18
+ import { registerMoveTool } from "./tools/move-note.js";
19
+ import { registerReadTool } from "./tools/read-note.js";
20
+ import { registerSchemaDiffTool } from "./tools/schema-diff.js";
21
+ import { registerSchemaInferTool } from "./tools/schema-infer.js";
22
+ import { registerSchemaValidateTool } from "./tools/schema-validate.js";
23
+ import { registerSearchTool } from "./tools/search-notes.js";
24
+ import { registerWriteTool } from "./tools/write-note.js";
25
+ export function isCommandAvailable(command) {
26
+ try {
27
+ execFileSync(command, ["--version"], { stdio: "ignore" });
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ export default definePluginEntry({
35
+ id: "openclaw-basic-memory",
36
+ name: "Basic Memory",
37
+ description: "Local-first knowledge graph for OpenClaw — persistent memory with graph search and composited memory_search",
38
+ configSchema: basicMemoryConfigSchema,
39
+ register(api) {
40
+ const cfg = parseConfig(api.pluginConfig);
41
+ initLogger(api.logger, cfg.debug);
42
+ log.info(`project=${cfg.project} memoryDir=${cfg.memoryDir} memoryFile=${cfg.memoryFile}`);
43
+ const client = new BmClient(cfg.bmPath, cfg.project);
44
+ // --- BM Tools (always registered) ---
45
+ registerSearchTool(api, client);
46
+ registerProjectListTool(api, client);
47
+ registerWorkspaceListTool(api, client);
48
+ registerReadTool(api, client);
49
+ registerWriteTool(api, client);
50
+ registerEditTool(api, client);
51
+ registerContextTool(api, client);
52
+ registerDeleteTool(api, client);
53
+ registerMoveTool(api, client);
54
+ registerSchemaValidateTool(api, client);
55
+ registerSchemaInferTool(api, client);
56
+ registerSchemaDiffTool(api, client);
57
+ // --- Composited memory_search + memory_get (always registered) ---
58
+ registerMemoryProvider(api, client, cfg);
59
+ log.info("registered composited memory_search + memory_get");
60
+ api.registerContextEngine("openclaw-basic-memory", () => new BasicMemoryContextEngine(client, cfg));
61
+ log.info("registered Basic Memory context engine");
62
+ // --- Commands ---
63
+ registerCommands(api, client);
64
+ registerSkillCommands(api);
65
+ registerCli(api, client, cfg);
66
+ // --- Service lifecycle ---
67
+ api.registerService({
68
+ id: "openclaw-basic-memory",
69
+ start: async (ctx) => {
70
+ log.info("starting...");
71
+ // Auto-install bm CLI if not found
72
+ const bmBin = cfg.bmPath || "bm";
73
+ if (!isCommandAvailable(bmBin)) {
74
+ log.info("bm CLI not found on PATH — attempting auto-install...");
75
+ try {
76
+ if (!isCommandAvailable("uv")) {
77
+ throw new Error("uv not found");
78
+ }
79
+ log.info("installing basic-memory via uv (this may take a minute)...");
80
+ const result = execFileSync("uv", ["tool", "install", "basic-memory", "--force"], {
81
+ encoding: "utf-8",
82
+ timeout: 120_000,
83
+ stdio: ["ignore", "pipe", "pipe"],
84
+ });
85
+ log.info(`basic-memory installed: ${result.trim().split("\n").pop()}`);
86
+ // Verify it worked
87
+ if (isCommandAvailable(bmBin)) {
88
+ log.info("bm CLI now available on PATH");
89
+ }
90
+ else {
91
+ log.error("bm installed but not found on PATH. You may need to add uv's bin directory to your PATH (typically ~/.local/bin).");
92
+ }
93
+ }
94
+ catch (_uvErr) {
95
+ log.error("Cannot auto-install basic-memory: uv not found. " +
96
+ "Install uv first (brew install uv, or curl -LsSf https://astral.sh/uv/install.sh | sh), " +
97
+ "then restart the gateway.");
98
+ }
99
+ }
100
+ const workspace = ctx.workspaceDir ?? process.cwd();
101
+ const projectPath = resolveProjectPath(cfg.projectPath, workspace);
102
+ cfg.projectPath = projectPath;
103
+ await client.start({ cwd: workspace });
104
+ await client.ensureProject(projectPath);
105
+ log.debug(`project "${cfg.project}" at ${projectPath}`);
106
+ // Seed schemas if not already present
107
+ for (const [name, content] of [
108
+ ["Task", TASK_SCHEMA_CONTENT],
109
+ ["Conversation", CONVERSATION_SCHEMA_CONTENT],
110
+ ]) {
111
+ try {
112
+ await client.readNote(`schema/${name}`);
113
+ log.debug(`${name} schema already exists, skipping seed`);
114
+ }
115
+ catch {
116
+ try {
117
+ await client.writeNote(name, content, "schema");
118
+ log.debug(`seeded ${name} schema note`);
119
+ }
120
+ catch (err) {
121
+ log.debug(`${name} schema seed failed (non-fatal)`, err);
122
+ }
123
+ }
124
+ }
125
+ setWorkspaceDir(workspace);
126
+ log.info("connected — BM MCP stdio session running");
127
+ },
128
+ stop: async () => {
129
+ log.info("stopping BM MCP session...");
130
+ await client.stop();
131
+ log.info("stopped");
132
+ },
133
+ });
134
+ },
135
+ });
@@ -0,0 +1,13 @@
1
+ export type LoggerBackend = {
2
+ info(msg: string, ...args: unknown[]): void;
3
+ warn(msg: string, ...args: unknown[]): void;
4
+ error(msg: string, ...args: unknown[]): void;
5
+ debug?(msg: string, ...args: unknown[]): void;
6
+ };
7
+ export declare function initLogger(backend: LoggerBackend, debug: boolean): void;
8
+ export declare const log: {
9
+ info(msg: string, ...args: unknown[]): void;
10
+ warn(msg: string, ...args: unknown[]): void;
11
+ error(msg: string, err?: unknown): void;
12
+ debug(msg: string, ...args: unknown[]): void;
13
+ };
package/dist/logger.js ADDED
@@ -0,0 +1,34 @@
1
+ const NOOP_LOGGER = {
2
+ info() { },
3
+ warn() { },
4
+ error() { },
5
+ debug() { },
6
+ };
7
+ let _backend = NOOP_LOGGER;
8
+ let _debug = false;
9
+ export function initLogger(backend, debug) {
10
+ _backend = backend;
11
+ _debug = debug;
12
+ }
13
+ export const log = {
14
+ info(msg, ...args) {
15
+ _backend.info(`basic-memory: ${msg}`, ...args);
16
+ },
17
+ warn(msg, ...args) {
18
+ _backend.warn(`basic-memory: ${msg}`, ...args);
19
+ },
20
+ error(msg, err) {
21
+ const detail = err instanceof Error
22
+ ? err.message
23
+ : err !== undefined && err !== ""
24
+ ? String(err)
25
+ : "";
26
+ _backend.error(`basic-memory: ${msg}${detail ? ` — ${detail}` : ""}`);
27
+ },
28
+ debug(msg, ...args) {
29
+ if (!_debug)
30
+ return;
31
+ const fn = _backend.debug ?? _backend.info;
32
+ fn(`basic-memory [debug]: ${msg}`, ...args);
33
+ },
34
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Canonical Conversation schema note content, seeded into new projects on first startup.
3
+ * If the schema already exists (user may have customized it), seeding is skipped.
4
+ */
5
+ export declare const CONVERSATION_SCHEMA_CONTENT = "---\ntitle: Conversation\ntype: schema\nentity: Conversation\nversion: 1\nschema:\n date: \"string, ISO date YYYY-MM-DD\"\n session_id?: \"string, unique session identifier\"\n channel?: \"string, where the conversation happened (webchat, telegram, discord, etc)\"\n participants?: \"array, who was in the conversation\"\n topic?: \"string, brief description of main topic\"\n summary?: \"string, one-paragraph summary of key points\"\n key_decisions?: \"array, decisions made during conversation\"\n action_items?: \"array, things to do as a result\"\nsettings:\n validation: warn\n---\n\n# Conversation\n\nA record of a conversation session between the agent and user(s).\n\n## Observations\n- [convention] Conversation files live in memory/conversations/ with format conversations-YYYY-MM-DD.md\n- [convention] Messages are appended as the conversation progresses\n- [convention] Summary and key_decisions populated at session end or by memory-reflect\n- [convention] Skip routine greetings, heartbeat acks, and tool call details\n- [convention] Focus on decisions, actions taken, and key context needed for continuity\n";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Canonical Conversation schema note content, seeded into new projects on first startup.
3
+ * If the schema already exists (user may have customized it), seeding is skipped.
4
+ */
5
+ export const CONVERSATION_SCHEMA_CONTENT = `---
6
+ title: Conversation
7
+ type: schema
8
+ entity: Conversation
9
+ version: 1
10
+ schema:
11
+ date: "string, ISO date YYYY-MM-DD"
12
+ session_id?: "string, unique session identifier"
13
+ channel?: "string, where the conversation happened (webchat, telegram, discord, etc)"
14
+ participants?: "array, who was in the conversation"
15
+ topic?: "string, brief description of main topic"
16
+ summary?: "string, one-paragraph summary of key points"
17
+ key_decisions?: "array, decisions made during conversation"
18
+ action_items?: "array, things to do as a result"
19
+ settings:
20
+ validation: warn
21
+ ---
22
+
23
+ # Conversation
24
+
25
+ A record of a conversation session between the agent and user(s).
26
+
27
+ ## Observations
28
+ - [convention] Conversation files live in memory/conversations/ with format conversations-YYYY-MM-DD.md
29
+ - [convention] Messages are appended as the conversation progresses
30
+ - [convention] Summary and key_decisions populated at session end or by memory-reflect
31
+ - [convention] Skip routine greetings, heartbeat acks, and tool call details
32
+ - [convention] Focus on decisions, actions taken, and key context needed for continuity
33
+ `;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Canonical Task schema note content, seeded into new projects on first startup.
3
+ * If the schema already exists (user may have customized it), seeding is skipped.
4
+ */
5
+ export declare const TASK_SCHEMA_CONTENT = "---\ntitle: Task\ntype: schema\nentity: Task\nversion: 1\nschema:\n description: \"string, what needs to be done\"\n status?(enum): \"[active, blocked, done, abandoned], current state\"\n assigned_to?: \"string, who is working on this\"\n steps?(array): \"string, ordered steps to complete\"\n current_step?: \"integer, which step number we're on (1-indexed)\"\n context?: \"string, key context needed to resume after memory loss\"\n started?: \"string, when work began\"\n completed?: \"string, when work finished\"\n blockers?(array): \"string, what's preventing progress\"\n parent_task?: \"Task, parent task if this is a subtask\"\nsettings:\n validation: warn\n---\n\n# Task\n\nStructured task note for tracking multi-step work that survives context compaction.\n\n## Observations\n- [convention] Task files live in memory/tasks/ with format YYYY-MM-DD-short-name.md\n- [convention] Status transitions: active \u2192 blocked \u2192 done or abandoned\n- [convention] Include a ## Context section for resumption after memory loss\n- [convention] Include a ## Steps section with numbered checkbox items for step tracking\n";
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Canonical Task schema note content, seeded into new projects on first startup.
3
+ * If the schema already exists (user may have customized it), seeding is skipped.
4
+ */
5
+ export const TASK_SCHEMA_CONTENT = `---
6
+ title: Task
7
+ type: schema
8
+ entity: Task
9
+ version: 1
10
+ schema:
11
+ description: "string, what needs to be done"
12
+ status?(enum): "[active, blocked, done, abandoned], current state"
13
+ assigned_to?: "string, who is working on this"
14
+ steps?(array): "string, ordered steps to complete"
15
+ current_step?: "integer, which step number we're on (1-indexed)"
16
+ context?: "string, key context needed to resume after memory loss"
17
+ started?: "string, when work began"
18
+ completed?: "string, when work finished"
19
+ blockers?(array): "string, what's preventing progress"
20
+ parent_task?: "Task, parent task if this is a subtask"
21
+ settings:
22
+ validation: warn
23
+ ---
24
+
25
+ # Task
26
+
27
+ Structured task note for tracking multi-step work that survives context compaction.
28
+
29
+ ## Observations
30
+ - [convention] Task files live in memory/tasks/ with format YYYY-MM-DD-short-name.md
31
+ - [convention] Status transitions: active → blocked → done or abandoned
32
+ - [convention] Include a ## Context section for resumption after memory loss
33
+ - [convention] Include a ## Steps section with numbered checkbox items for step tracking
34
+ `;
@@ -0,0 +1,3 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
2
+ import type { BmClient } from "../bm-client.ts";
3
+ export declare function registerContextTool(api: OpenClawPluginApi, client: BmClient): void;
@@ -0,0 +1,100 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { log } from "../logger.js";
3
+ export function registerContextTool(api, client) {
4
+ api.registerTool({
5
+ name: "build_context",
6
+ label: "Build Context",
7
+ description: "Navigate the Basic Memory knowledge graph via memory:// URLs. " +
8
+ "Returns the target note plus related notes connected through the graph. " +
9
+ "Use this to follow relations and discover connected concepts.",
10
+ parameters: Type.Object({
11
+ url: Type.String({
12
+ description: 'Memory URL to navigate, e.g. "memory://agents/decisions" or "projects/my-project"',
13
+ }),
14
+ depth: Type.Optional(Type.Number({
15
+ description: "How many relation hops to follow (default: 1)",
16
+ })),
17
+ project: Type.Optional(Type.String({
18
+ description: "Target project name (defaults to current project)",
19
+ })),
20
+ }),
21
+ async execute(_toolCallId, params) {
22
+ const depth = params.depth ?? 1;
23
+ log.debug(`build_context: url="${params.url}" depth=${depth}`);
24
+ try {
25
+ const ctx = await client.buildContext(params.url, depth, params.project);
26
+ if (!ctx.results || ctx.results.length === 0) {
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: `No context found for "${params.url}".`,
32
+ },
33
+ ],
34
+ details: {
35
+ url: params.url,
36
+ depth,
37
+ resultCount: 0,
38
+ },
39
+ };
40
+ }
41
+ const sections = [];
42
+ for (const result of ctx.results) {
43
+ const primary = result.primary_result;
44
+ sections.push(`## ${primary.title}\n${primary.content}`);
45
+ if (result.observations?.length > 0) {
46
+ const obs = result.observations
47
+ .map((o) => `- [${o.category}] ${o.content}`)
48
+ .join("\n");
49
+ sections.push(`### Observations\n${obs}`);
50
+ }
51
+ if (result.related_results?.length > 0) {
52
+ const rels = result.related_results
53
+ .map((r) => {
54
+ if (r.type === "relation") {
55
+ return `- ${r.relation_type} → **${r.to_entity}**`;
56
+ }
57
+ const label = r.relation_type
58
+ ? `${r.relation_type} → **${r.title}**`
59
+ : `**${r.title}**`;
60
+ return r.permalink
61
+ ? `- ${label} (${r.permalink})`
62
+ : `- ${label}`;
63
+ })
64
+ .join("\n");
65
+ sections.push(`### Related\n${rels}`);
66
+ }
67
+ }
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: sections.join("\n\n"),
73
+ },
74
+ ],
75
+ details: {
76
+ url: params.url,
77
+ depth,
78
+ resultCount: ctx.results.length,
79
+ },
80
+ };
81
+ }
82
+ catch (err) {
83
+ log.error("build_context failed", err);
84
+ return {
85
+ content: [
86
+ {
87
+ type: "text",
88
+ text: `Failed to build context for "${params.url}". Check logs for details.`,
89
+ },
90
+ ],
91
+ details: {
92
+ url: params.url,
93
+ depth,
94
+ error: "build_context_failed",
95
+ },
96
+ };
97
+ }
98
+ },
99
+ }, { name: "build_context" });
100
+ }
@@ -0,0 +1,3 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
2
+ import type { BmClient } from "../bm-client.ts";
3
+ export declare function registerDeleteTool(api: OpenClawPluginApi, client: BmClient): void;
@@ -0,0 +1,52 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { log } from "../logger.js";
3
+ export function registerDeleteTool(api, client) {
4
+ api.registerTool({
5
+ name: "delete_note",
6
+ label: "Delete Note",
7
+ description: "Delete a note from the Basic Memory knowledge graph. " +
8
+ "The note is permanently removed from the filesystem and the search index.",
9
+ parameters: Type.Object({
10
+ identifier: Type.String({
11
+ description: "Note title, permalink, or memory:// URL to delete",
12
+ }),
13
+ project: Type.Optional(Type.String({
14
+ description: "Target project name (defaults to current project)",
15
+ })),
16
+ }),
17
+ async execute(_toolCallId, params) {
18
+ log.debug(`delete_note: identifier="${params.identifier}"`);
19
+ try {
20
+ const result = await client.deleteNote(params.identifier, params.project);
21
+ return {
22
+ content: [
23
+ {
24
+ type: "text",
25
+ text: `Deleted: ${result.title} (${result.permalink})`,
26
+ },
27
+ ],
28
+ details: {
29
+ title: result.title,
30
+ permalink: result.permalink,
31
+ file_path: result.file_path,
32
+ },
33
+ };
34
+ }
35
+ catch (err) {
36
+ log.error("delete_note failed", err);
37
+ return {
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text: `Failed to delete "${params.identifier}". It may not exist.`,
42
+ },
43
+ ],
44
+ details: {
45
+ identifier: params.identifier,
46
+ error: "delete_note_failed",
47
+ },
48
+ };
49
+ }
50
+ },
51
+ }, { name: "delete_note" });
52
+ }
@@ -0,0 +1,3 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
2
+ import type { BmClient } from "../bm-client.ts";
3
+ export declare function registerEditTool(api: OpenClawPluginApi, client: BmClient): void;
@@ -0,0 +1,81 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { log } from "../logger.js";
3
+ export function registerEditTool(api, client) {
4
+ api.registerTool({
5
+ name: "edit_note",
6
+ label: "Edit Note",
7
+ description: "Incrementally edit an existing note in the Basic Memory knowledge graph. " +
8
+ "Supports append, prepend, find/replace, and section replacement " +
9
+ "without rewriting the entire note.",
10
+ parameters: Type.Object({
11
+ identifier: Type.String({
12
+ description: "Note title, permalink, or memory:// URL to edit",
13
+ }),
14
+ operation: Type.Union([
15
+ Type.Literal("append"),
16
+ Type.Literal("prepend"),
17
+ Type.Literal("find_replace"),
18
+ Type.Literal("replace_section"),
19
+ ], {
20
+ description: "Edit operation: append (add to end), prepend (add to start), " +
21
+ "find_replace (replace matching text), replace_section (replace a heading section)",
22
+ }),
23
+ content: Type.String({
24
+ description: "New content to add or replace with",
25
+ }),
26
+ find_text: Type.Optional(Type.String({
27
+ description: "Text to find (required for find_replace)",
28
+ })),
29
+ section: Type.Optional(Type.String({
30
+ description: "Section heading to replace (required for replace_section)",
31
+ })),
32
+ expected_replacements: Type.Optional(Type.Number({
33
+ description: "Expected replacement count for find_replace (default: 1)",
34
+ })),
35
+ project: Type.Optional(Type.String({
36
+ description: "Target project name (defaults to current project)",
37
+ })),
38
+ }),
39
+ async execute(_toolCallId, params) {
40
+ log.debug(`edit_note: id="${params.identifier}" op=${params.operation}`);
41
+ try {
42
+ const note = await client.editNote(params.identifier, params.operation, params.content, {
43
+ find_text: params.find_text,
44
+ section: params.section,
45
+ expected_replacements: params.expected_replacements,
46
+ }, params.project);
47
+ return {
48
+ content: [
49
+ {
50
+ type: "text",
51
+ text: `Note updated: ${note.title} (${note.permalink})`,
52
+ },
53
+ ],
54
+ details: {
55
+ title: note.title,
56
+ permalink: note.permalink,
57
+ file_path: note.file_path,
58
+ operation: params.operation,
59
+ checksum: note.checksum ?? null,
60
+ },
61
+ };
62
+ }
63
+ catch (err) {
64
+ log.error("edit_note failed", err);
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: `Failed to edit note "${params.identifier}". It may not exist.`,
70
+ },
71
+ ],
72
+ details: {
73
+ identifier: params.identifier,
74
+ operation: params.operation,
75
+ error: "edit_note_failed",
76
+ },
77
+ };
78
+ }
79
+ },
80
+ }, { name: "edit_note" });
81
+ }
@@ -0,0 +1,3 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
2
+ import type { BmClient } from "../bm-client.ts";
3
+ export declare function registerProjectListTool(api: OpenClawPluginApi, client: BmClient): void;