@basicmemory/openclaw-basic-memory 0.2.2 → 0.2.4

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 (73) hide show
  1. package/README.md +38 -18
  2. package/commands/cli.ts +1 -1
  3. package/commands/skills.ts +22 -10
  4. package/commands/slash.ts +4 -2
  5. package/config.ts +1 -0
  6. package/context-engine/basic-memory-context-engine.ts +38 -57
  7. package/dist/bm-client.d.ts +177 -0
  8. package/dist/bm-client.js +572 -0
  9. package/dist/commands/cli.d.ts +4 -0
  10. package/dist/commands/cli.js +117 -0
  11. package/dist/commands/skills.d.ts +2 -0
  12. package/dist/commands/skills.js +47 -0
  13. package/dist/commands/slash.d.ts +3 -0
  14. package/dist/commands/slash.js +92 -0
  15. package/dist/config.d.ts +22 -0
  16. package/dist/config.js +109 -0
  17. package/dist/context-engine/basic-memory-context-engine.d.ts +94 -0
  18. package/dist/context-engine/basic-memory-context-engine.js +176 -0
  19. package/dist/hooks/capture.d.ts +24 -0
  20. package/dist/hooks/capture.js +88 -0
  21. package/dist/hooks/recall.d.ts +24 -0
  22. package/dist/hooks/recall.js +56 -0
  23. package/dist/index.d.ts +8 -0
  24. package/dist/index.js +125 -0
  25. package/dist/logger.d.ts +13 -0
  26. package/dist/logger.js +34 -0
  27. package/dist/schema/conversation-schema.d.ts +5 -0
  28. package/dist/schema/conversation-schema.js +33 -0
  29. package/dist/schema/task-schema.d.ts +5 -0
  30. package/dist/schema/task-schema.js +34 -0
  31. package/dist/tools/build-context.d.ts +3 -0
  32. package/dist/tools/build-context.js +100 -0
  33. package/dist/tools/delete-note.d.ts +3 -0
  34. package/dist/tools/delete-note.js +52 -0
  35. package/dist/tools/edit-note.d.ts +3 -0
  36. package/dist/tools/edit-note.js +81 -0
  37. package/dist/tools/list-memory-projects.d.ts +3 -0
  38. package/dist/tools/list-memory-projects.js +85 -0
  39. package/dist/tools/list-workspaces.d.ts +3 -0
  40. package/dist/tools/list-workspaces.js +64 -0
  41. package/dist/tools/memory-provider.d.ts +15 -0
  42. package/dist/tools/memory-provider.js +274 -0
  43. package/dist/tools/move-note.d.ts +3 -0
  44. package/dist/tools/move-note.js +56 -0
  45. package/dist/tools/read-note.d.ts +3 -0
  46. package/dist/tools/read-note.js +57 -0
  47. package/dist/tools/schema-diff.d.ts +3 -0
  48. package/dist/tools/schema-diff.js +81 -0
  49. package/dist/tools/schema-infer.d.ts +3 -0
  50. package/dist/tools/schema-infer.js +66 -0
  51. package/dist/tools/schema-validate.d.ts +3 -0
  52. package/dist/tools/schema-validate.js +73 -0
  53. package/dist/tools/search-notes.d.ts +3 -0
  54. package/dist/tools/search-notes.js +97 -0
  55. package/dist/tools/write-note.d.ts +3 -0
  56. package/dist/tools/write-note.js +80 -0
  57. package/index.ts +4 -5
  58. package/openclaw.plugin.json +28 -8
  59. package/package.json +24 -6
  60. package/tools/build-context.ts +1 -1
  61. package/tools/delete-note.ts +1 -1
  62. package/tools/edit-note.ts +1 -1
  63. package/tools/list-memory-projects.ts +1 -1
  64. package/tools/list-workspaces.ts +1 -1
  65. package/tools/memory-provider.ts +1 -1
  66. package/tools/move-note.ts +1 -1
  67. package/tools/read-note.ts +1 -1
  68. package/tools/schema-diff.ts +1 -1
  69. package/tools/schema-infer.ts +1 -1
  70. package/tools/schema-validate.ts +1 -1
  71. package/tools/search-notes.ts +9 -5
  72. package/tools/write-note.ts +1 -1
  73. package/context-engine/basic-memory-context-engine.test.ts +0 -398
package/README.md CHANGED
@@ -45,6 +45,7 @@ Then install the plugin:
45
45
 
46
46
  ```bash
47
47
  openclaw plugins install @basicmemory/openclaw-basic-memory
48
+ openclaw plugins enable openclaw-basic-memory --slot memory
48
49
  openclaw gateway restart
49
50
  ```
50
51
 
@@ -54,7 +55,8 @@ Verify:
54
55
 
55
56
  ```bash
56
57
  openclaw plugins list
57
- openclaw plugins info openclaw-basic-memory
58
+ openclaw plugins inspect openclaw-basic-memory --json
59
+ openclaw plugins doctor
58
60
  ```
59
61
 
60
62
  ## Configuration
@@ -63,8 +65,15 @@ openclaw plugins info openclaw-basic-memory
63
65
 
64
66
  ```json5
65
67
  {
66
- "openclaw-basic-memory": {
67
- enabled: true
68
+ plugins: {
69
+ entries: {
70
+ "openclaw-basic-memory": {
71
+ enabled: true
72
+ }
73
+ },
74
+ slots: {
75
+ memory: "openclaw-basic-memory"
76
+ }
68
77
  }
69
78
  }
70
79
  ```
@@ -75,16 +84,23 @@ This uses sensible defaults: auto-generated project name, maps to your workspace
75
84
 
76
85
  ```json5
77
86
  {
78
- "openclaw-basic-memory": {
79
- enabled: true,
80
- config: {
81
- project: "my-agent", // BM project name (default: "openclaw-{hostname}")
82
- projectPath: ".", // Project directory (default: workspace root)
83
- memoryDir: "memory/", // Where task notes live
84
- memoryFile: "MEMORY.md", // Working memory file
85
- autoCapture: true, // Record conversations as daily notes
86
- autoRecall: true, // Inject active tasks + recent activity at session start
87
- debug: false // Verbose logging
87
+ plugins: {
88
+ entries: {
89
+ "openclaw-basic-memory": {
90
+ enabled: true,
91
+ config: {
92
+ project: "my-agent", // BM project name (default: "openclaw-{hostname}")
93
+ projectPath: ".", // Project directory (default: workspace root)
94
+ memoryDir: "memory/", // Where task notes live
95
+ memoryFile: "MEMORY.md", // Working memory file
96
+ autoCapture: true, // Record conversations as daily notes
97
+ autoRecall: true, // Inject active tasks + recent activity at session start
98
+ debug: false // Verbose logging
99
+ }
100
+ }
101
+ },
102
+ slots: {
103
+ memory: "openclaw-basic-memory"
88
104
  }
89
105
  }
90
106
  }
@@ -172,19 +188,23 @@ openclaw basic-memory status
172
188
 
173
189
  ## Bundled skills
174
190
 
175
- Six skills ship with the plugin — no installation needed:
191
+ Ten skills ship with the plugin — no installation needed:
176
192
 
177
- - **memory-tasks** — structured task tracking that survives context compaction
178
- - **memory-reflect** — periodic consolidation of recent notes into durable memory
179
193
  - **memory-defrag** — cleanup and reorganization of memory files
180
- - **memory-schema** — schema lifecycle (infer, create, validate, diff)
194
+ - **memory-ingest** — import existing material into Basic Memory
195
+ - **memory-lifecycle** — manage note/project lifecycle workflows
196
+ - **memory-literary-analysis** — analyze texts and reading notes
181
197
  - **memory-metadata-search** — query notes by frontmatter fields
182
198
  - **memory-notes** — guidance for writing well-structured notes
199
+ - **memory-reflect** — periodic consolidation of recent notes into durable memory
200
+ - **memory-research** — research synthesis into durable notes
201
+ - **memory-schema** — schema lifecycle (infer, create, validate, diff)
202
+ - **memory-tasks** — structured task tracking that survives context compaction
183
203
 
184
204
  ### Updating skills
185
205
 
186
206
  ```bash
187
- npx skills add basicmachines-co/basic-memory-skills --agent openclaw
207
+ bun run fetch-skills
188
208
  ```
189
209
 
190
210
  ## Task notes
package/commands/cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"
2
2
  import type { BmClient } from "../bm-client.ts"
3
3
  import type { BasicMemoryConfig } from "../config.ts"
4
4
  import { log } from "../logger.ts"
@@ -1,11 +1,9 @@
1
- import { readFileSync } from "node:fs"
1
+ import { existsSync, readFileSync } from "node:fs"
2
2
  import { dirname, resolve } from "node:path"
3
3
  import { fileURLToPath } from "node:url"
4
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
4
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"
5
5
 
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url))
7
- const SKILLS_DIR = resolve(__dirname, "..", "skills")
8
- const MANIFEST_PATH = resolve(SKILLS_DIR, "manifest.json")
9
7
 
10
8
  interface ManifestEntry {
11
9
  dir: string
@@ -13,9 +11,22 @@ interface ManifestEntry {
13
11
  description: string
14
12
  }
15
13
 
16
- function loadManifest(): ManifestEntry[] {
14
+ function resolveSkillsDir(api: OpenClawPluginApi): string {
15
+ if (api.resolvePath) {
16
+ return api.resolvePath("skills")
17
+ }
18
+
19
+ const sourceRootSkills = resolve(__dirname, "..", "skills")
20
+ if (existsSync(sourceRootSkills)) {
21
+ return sourceRootSkills
22
+ }
23
+
24
+ return resolve(__dirname, "..", "..", "skills")
25
+ }
26
+
27
+ function loadManifest(skillsDir: string): ManifestEntry[] {
17
28
  try {
18
- const raw = readFileSync(MANIFEST_PATH, "utf-8")
29
+ const raw = readFileSync(resolve(skillsDir, "manifest.json"), "utf-8")
19
30
  return JSON.parse(raw) as ManifestEntry[]
20
31
  } catch {
21
32
  throw new Error(
@@ -24,16 +35,17 @@ function loadManifest(): ManifestEntry[] {
24
35
  }
25
36
  }
26
37
 
27
- function loadSkill(dir: string): string {
28
- return readFileSync(resolve(SKILLS_DIR, dir, "SKILL.md"), "utf-8")
38
+ function loadSkill(skillsDir: string, dir: string): string {
39
+ return readFileSync(resolve(skillsDir, dir, "SKILL.md"), "utf-8")
29
40
  }
30
41
 
31
42
  export function registerSkillCommands(api: OpenClawPluginApi): void {
32
- const manifest = loadManifest()
43
+ const skillsDir = resolveSkillsDir(api)
44
+ const manifest = loadManifest(skillsDir)
33
45
 
34
46
  for (const entry of manifest) {
35
47
  const commandName = entry.dir.replace(/^memory-/, "")
36
- const content = loadSkill(entry.dir)
48
+ const content = loadSkill(skillsDir, entry.dir)
37
49
 
38
50
  api.registerCommand({
39
51
  name: commandName,
package/commands/slash.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { execSync } from "node:child_process"
2
2
  import { dirname, resolve } from "node:path"
3
3
  import { fileURLToPath } from "node:url"
4
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
4
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"
5
5
  import type { BmClient } from "../bm-client.ts"
6
6
  import { log } from "../logger.ts"
7
7
 
@@ -16,7 +16,9 @@ export function registerCommands(
16
16
  description: "Install or update the Basic Memory CLI (requires uv)",
17
17
  requireAuth: true,
18
18
  handler: async () => {
19
- const scriptPath = resolve(__dirname, "..", "scripts", "setup-bm.sh")
19
+ const scriptPath = api.resolvePath
20
+ ? api.resolvePath("scripts/setup-bm.sh")
21
+ : resolve(__dirname, "..", "scripts", "setup-bm.sh")
20
22
  log.info(`/bm-setup: running ${scriptPath}`)
21
23
 
22
24
  try {
package/config.ts CHANGED
@@ -101,6 +101,7 @@ export function parseConfig(raw: unknown): BasicMemoryConfig {
101
101
  let cloud: CloudConfig | undefined
102
102
  if (cfg.cloud && typeof cfg.cloud === "object" && !Array.isArray(cfg.cloud)) {
103
103
  const c = cfg.cloud as Record<string, unknown>
104
+ assertAllowedKeys(c, ["url", "api_key"], "basic-memory cloud config")
104
105
  if (typeof c.url === "string" && typeof c.api_key === "string") {
105
106
  cloud = { url: c.url, api_key: c.api_key }
106
107
  }
@@ -1,25 +1,52 @@
1
- import { createRequire } from "node:module"
2
- import { dirname, resolve } from "node:path"
3
- import { pathToFileURL } from "node:url"
4
1
  import type { AgentMessage } from "@mariozechner/pi-agent-core"
5
- import type {
6
- AssembleResult,
7
- BootstrapResult,
8
- CompactResult,
9
- ContextEngine,
10
- } from "openclaw/plugin-sdk"
2
+ import { delegateCompactionToRuntime } from "openclaw/plugin-sdk/core"
11
3
  import type { BmClient } from "../bm-client.ts"
12
4
  import type { BasicMemoryConfig } from "../config.ts"
13
5
  import { selectCaptureTurn } from "../hooks/capture.ts"
14
6
  import { loadRecallState } from "../hooks/recall.ts"
15
7
  import { log } from "../logger.ts"
16
8
 
17
- const require = createRequire(import.meta.url)
18
9
  export const MAX_ASSEMBLE_RECALL_CHARS = 1200
19
10
  const TRUNCATED_RECALL_SUFFIX = "\n\n[Basic Memory recall truncated]"
20
11
  const SUBAGENT_HANDOFF_FOLDER = "agent/subagents"
21
12
  const MAX_SUBAGENT_RECALL_CHARS = 800
22
13
 
14
+ type AssembleResult = {
15
+ messages: AgentMessage[]
16
+ estimatedTokens: number
17
+ systemPromptAddition?: string
18
+ }
19
+
20
+ type BootstrapResult = {
21
+ bootstrapped: boolean
22
+ importedMessages?: number
23
+ reason?: string
24
+ }
25
+
26
+ type CompactResult = {
27
+ ok: boolean
28
+ compacted: boolean
29
+ reason?: string
30
+ result?: {
31
+ summary?: string
32
+ firstKeptEntryId?: string
33
+ tokensBefore: number
34
+ tokensAfter?: number
35
+ details?: unknown
36
+ sessionId?: string
37
+ sessionFile?: string
38
+ }
39
+ }
40
+
41
+ interface ContextEngine {
42
+ readonly info: {
43
+ id: string
44
+ name: string
45
+ version?: string
46
+ ownsCompaction?: boolean
47
+ }
48
+ }
49
+
23
50
  interface SessionMemoryState {
24
51
  recallContext: string
25
52
  }
@@ -104,36 +131,6 @@ function buildSubagentCompletionUpdate(params: {
104
131
  ].join("\n")
105
132
  }
106
133
 
107
- type LegacyContextEngineModule = {
108
- LegacyContextEngine: new () => {
109
- compact(params: {
110
- sessionId: string
111
- sessionFile: string
112
- tokenBudget?: number
113
- force?: boolean
114
- currentTokenCount?: number
115
- compactionTarget?: "budget" | "threshold"
116
- customInstructions?: string
117
- runtimeContext?: Record<string, unknown>
118
- }): Promise<CompactResult>
119
- }
120
- }
121
-
122
- async function loadLegacyContextEngine(): Promise<
123
- LegacyContextEngineModule["LegacyContextEngine"]
124
- > {
125
- const pluginSdkPath = require.resolve("openclaw/plugin-sdk")
126
- const legacyPath = resolve(
127
- dirname(pluginSdkPath),
128
- "context-engine",
129
- "legacy.js",
130
- )
131
- const module = (await import(
132
- pathToFileURL(legacyPath).href
133
- )) as LegacyContextEngineModule
134
- return module.LegacyContextEngine
135
- }
136
-
137
134
  export class BasicMemoryContextEngine implements ContextEngine {
138
135
  readonly info = {
139
136
  id: "openclaw-basic-memory",
@@ -144,9 +141,6 @@ export class BasicMemoryContextEngine implements ContextEngine {
144
141
 
145
142
  private readonly sessionState = new Map<string, SessionMemoryState>()
146
143
  private readonly subagentState = new Map<string, SubagentHandoffState>()
147
- private legacyContextEnginePromise: Promise<
148
- InstanceType<LegacyContextEngineModule["LegacyContextEngine"]>
149
- > | null = null
150
144
 
151
145
  constructor(
152
146
  private readonly client: BmClient,
@@ -243,8 +237,7 @@ export class BasicMemoryContextEngine implements ContextEngine {
243
237
  customInstructions?: string
244
238
  runtimeContext?: Record<string, unknown>
245
239
  }): Promise<CompactResult> {
246
- const legacy = await this.getLegacyContextEngine()
247
- return legacy.compact(params)
240
+ return delegateCompactionToRuntime(params)
248
241
  }
249
242
 
250
243
  async prepareSubagentSpawn(params: {
@@ -315,16 +308,4 @@ export class BasicMemoryContextEngine implements ContextEngine {
315
308
  this.sessionState.clear()
316
309
  this.subagentState.clear()
317
310
  }
318
-
319
- private async getLegacyContextEngine(): Promise<
320
- InstanceType<LegacyContextEngineModule["LegacyContextEngine"]>
321
- > {
322
- if (!this.legacyContextEnginePromise) {
323
- this.legacyContextEnginePromise = loadLegacyContextEngine().then(
324
- (LegacyContextEngine) => new LegacyContextEngine(),
325
- )
326
- }
327
-
328
- return this.legacyContextEnginePromise
329
- }
330
311
  }
@@ -0,0 +1,177 @@
1
+ export declare class NoteAlreadyExistsError extends Error {
2
+ readonly permalink: string;
3
+ constructor(title: string, permalink: string);
4
+ }
5
+ export interface SearchResult {
6
+ title: string;
7
+ permalink: string;
8
+ content: string;
9
+ score?: number;
10
+ file_path: string;
11
+ }
12
+ export interface NoteResult {
13
+ title: string;
14
+ permalink: string;
15
+ content: string;
16
+ file_path: string;
17
+ frontmatter?: Record<string, unknown> | null;
18
+ checksum?: string | null;
19
+ action?: "created" | "updated";
20
+ }
21
+ export interface EditNoteResult {
22
+ title: string;
23
+ permalink: string;
24
+ file_path: string;
25
+ operation: "append" | "prepend" | "find_replace" | "replace_section";
26
+ checksum?: string | null;
27
+ }
28
+ interface ReadNoteOptions {
29
+ includeFrontmatter?: boolean;
30
+ }
31
+ interface EditNoteOptions {
32
+ find_text?: string;
33
+ section?: string;
34
+ expected_replacements?: number;
35
+ }
36
+ export interface ContextResult {
37
+ results: Array<{
38
+ primary_result: NoteResult;
39
+ observations: Array<{
40
+ category: string;
41
+ content: string;
42
+ }>;
43
+ related_results: Array<{
44
+ type: "relation" | "entity";
45
+ title?: string;
46
+ permalink: string;
47
+ relation_type?: string;
48
+ from_entity?: string;
49
+ to_entity?: string;
50
+ }>;
51
+ }>;
52
+ }
53
+ export interface RecentResult {
54
+ title: string;
55
+ permalink: string;
56
+ file_path: string;
57
+ created_at: string;
58
+ }
59
+ export interface ProjectListResult {
60
+ name: string;
61
+ path: string;
62
+ display_name?: string | null;
63
+ is_private?: boolean;
64
+ is_default?: boolean;
65
+ isDefault?: boolean;
66
+ workspace_name?: string | null;
67
+ workspace_type?: string | null;
68
+ workspace_tenant_id?: string | null;
69
+ }
70
+ export interface WorkspaceResult {
71
+ tenant_id: string;
72
+ name: string;
73
+ workspace_type: string;
74
+ role: string;
75
+ organization_id?: string | null;
76
+ has_active_subscription: boolean;
77
+ }
78
+ export interface SchemaValidationResult {
79
+ entity_type: string | null;
80
+ total_notes: number;
81
+ total_entities: number;
82
+ valid_count: number;
83
+ warning_count: number;
84
+ error_count: number;
85
+ results: Array<{
86
+ identifier: string;
87
+ valid: boolean;
88
+ warnings: string[];
89
+ errors: string[];
90
+ }>;
91
+ }
92
+ export interface SchemaInferResult {
93
+ entity_type: string;
94
+ notes_analyzed: number;
95
+ field_frequencies: Array<{
96
+ name: string;
97
+ percentage: number;
98
+ count: number;
99
+ total: number;
100
+ source: string;
101
+ sample_values?: string[];
102
+ is_array?: boolean;
103
+ target_type?: string | null;
104
+ }>;
105
+ suggested_schema: Record<string, unknown>;
106
+ suggested_required: string[];
107
+ suggested_optional: string[];
108
+ excluded: string[];
109
+ }
110
+ export interface SchemaDiffResult {
111
+ entity_type: string;
112
+ schema_found: boolean;
113
+ new_fields: Array<{
114
+ name: string;
115
+ source: string;
116
+ count: number;
117
+ total: number;
118
+ percentage: number;
119
+ }>;
120
+ dropped_fields: Array<{
121
+ name: string;
122
+ source: string;
123
+ declared_in?: string;
124
+ }>;
125
+ cardinality_changes: string[];
126
+ }
127
+ export declare class BmClient {
128
+ private bmPath;
129
+ private project;
130
+ private cwd?;
131
+ private env?;
132
+ private shouldRun;
133
+ private client;
134
+ private transport;
135
+ private connectPromise;
136
+ private retryDelaysMs;
137
+ constructor(bmPath: string, project: string);
138
+ start(options?: {
139
+ cwd?: string;
140
+ env?: Record<string, string>;
141
+ }): Promise<void>;
142
+ stop(): Promise<void>;
143
+ private connectWithRetries;
144
+ private ensureConnected;
145
+ private connectFresh;
146
+ private assertRequiredTools;
147
+ private disconnectCurrent;
148
+ private callToolRaw;
149
+ private callTool;
150
+ ensureProject(projectPath: string): Promise<void>;
151
+ listWorkspaces(): Promise<WorkspaceResult[]>;
152
+ listProjects(workspace?: string): Promise<ProjectListResult[]>;
153
+ search(query?: string, limit?: number, project?: string, metadata?: {
154
+ filters?: Record<string, unknown>;
155
+ tags?: string[];
156
+ status?: string;
157
+ note_types?: string[];
158
+ entity_types?: string[];
159
+ }): Promise<SearchResult[]>;
160
+ readNote(identifier: string, options?: ReadNoteOptions, project?: string): Promise<NoteResult>;
161
+ writeNote(title: string, content: string, folder: string, project?: string, overwrite?: boolean): Promise<NoteResult>;
162
+ buildContext(url: string, depth?: number, project?: string): Promise<ContextResult>;
163
+ recentActivity(timeframe?: string, project?: string): Promise<RecentResult[]>;
164
+ editNote(identifier: string, operation: "append" | "prepend" | "find_replace" | "replace_section", content: string, options?: EditNoteOptions, project?: string): Promise<EditNoteResult>;
165
+ deleteNote(identifier: string, project?: string): Promise<{
166
+ title: string;
167
+ permalink: string;
168
+ file_path: string;
169
+ }>;
170
+ moveNote(identifier: string, newFolder: string, project?: string): Promise<NoteResult>;
171
+ schemaValidate(noteType?: string, identifier?: string, project?: string): Promise<SchemaValidationResult>;
172
+ schemaInfer(noteType: string, threshold?: number, project?: string): Promise<SchemaInferResult>;
173
+ schemaDiff(noteType: string, project?: string): Promise<SchemaDiffResult>;
174
+ indexConversation(userMessage: string, assistantResponse: string): Promise<void>;
175
+ getProject(): string;
176
+ }
177
+ export {};