Skip to content

Commit 2bf0898

Browse files
committed
chore: align OpenClaw plugin packaging
1 parent 27391e0 commit 2bf0898

37 files changed

Lines changed: 212 additions & 133 deletions

DEVELOPMENT.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Clone and link locally for plugin development:
88
git clone https://github.com/basicmachines-co/openclaw-basic-memory.git
99
cd openclaw-basic-memory
1010
bun install
11+
bun run fetch-skills
1112
openclaw plugins install -l "$PWD"
1213
openclaw plugins enable openclaw-basic-memory --slot memory
1314
openclaw gateway restart
@@ -37,8 +38,9 @@ Or load directly from a path in your OpenClaw config:
3738

3839
```bash
3940
bun run check-types # Type checking
41+
bun run build # Compile package runtime to dist/
4042
bun run lint # Linting
41-
bun test # Run tests (156 tests)
43+
bun test # Run tests
4244
bun run test:int # Real BM MCP integration tests
4345
```
4446

@@ -64,7 +66,7 @@ BASIC_MEMORY_REPO=/absolute/path/to/basic-memory bun run test:int
6466
This package is published as `@basicmemory/openclaw-basic-memory`.
6567

6668
```bash
67-
# Verify release readiness (types + tests + npm pack dry run)
69+
# Verify release readiness (types + build + tests + npm pack dry run)
6870
just release-check
6971

7072
# Inspect publish payload

README.md

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Then install the plugin:
4545

4646
```bash
4747
openclaw plugins install @basicmemory/openclaw-basic-memory
48+
openclaw plugins enable openclaw-basic-memory --slot memory
4849
openclaw gateway restart
4950
```
5051

@@ -54,7 +55,8 @@ Verify:
5455

5556
```bash
5657
openclaw plugins list
57-
openclaw plugins info openclaw-basic-memory
58+
openclaw plugins inspect openclaw-basic-memory --json
59+
openclaw plugins doctor
5860
```
5961

6062
## Configuration
@@ -63,8 +65,15 @@ openclaw plugins info openclaw-basic-memory
6365

6466
```json5
6567
{
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+
}
6877
}
6978
}
7079
```
@@ -75,16 +84,23 @@ This uses sensible defaults: auto-generated project name, maps to your workspace
7584

7685
```json5
7786
{
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"
88104
}
89105
}
90106
}
@@ -172,19 +188,23 @@ openclaw basic-memory status
172188

173189
## Bundled skills
174190

175-
Six skills ship with the plugin — no installation needed:
191+
Ten skills ship with the plugin — no installation needed:
176192

177-
- **memory-tasks** — structured task tracking that survives context compaction
178-
- **memory-reflect** — periodic consolidation of recent notes into durable memory
179193
- **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
181197
- **memory-metadata-search** — query notes by frontmatter fields
182198
- **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
183203

184204
### Updating skills
185205

186206
```bash
187-
npx skills add basicmachines-co/basic-memory-skills --agent openclaw
207+
bun run fetch-skills
188208
```
189209

190210
## Task notes

commands/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
1+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"
22
import type { BmClient } from "../bm-client.ts"
33
import type { BasicMemoryConfig } from "../config.ts"
44
import { log } from "../logger.ts"

commands/skills.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it, jest } from "bun:test"
2-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
2+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"
33
import { registerSkillCommands } from "./skills.ts"
44

55
describe("skill slash commands", () => {

commands/skills.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
1-
import { readFileSync } from "node:fs"
1+
import { existsSync, readFileSync } from "node:fs"
22
import { dirname, resolve } from "node:path"
33
import { fileURLToPath } from "node:url"
4-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
4+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"
55

66
const __dirname = dirname(fileURLToPath(import.meta.url))
7-
const SKILLS_DIR = resolve(__dirname, "..", "skills")
8-
const MANIFEST_PATH = resolve(SKILLS_DIR, "manifest.json")
97

108
interface ManifestEntry {
119
dir: string
1210
name: string
1311
description: string
1412
}
1513

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[] {
1728
try {
18-
const raw = readFileSync(MANIFEST_PATH, "utf-8")
29+
const raw = readFileSync(resolve(skillsDir, "manifest.json"), "utf-8")
1930
return JSON.parse(raw) as ManifestEntry[]
2031
} catch {
2132
throw new Error(
@@ -24,16 +35,17 @@ function loadManifest(): ManifestEntry[] {
2435
}
2536
}
2637

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")
2940
}
3041

3142
export function registerSkillCommands(api: OpenClawPluginApi): void {
32-
const manifest = loadManifest()
43+
const skillsDir = resolveSkillsDir(api)
44+
const manifest = loadManifest(skillsDir)
3345

3446
for (const entry of manifest) {
3547
const commandName = entry.dir.replace(/^memory-/, "")
36-
const content = loadSkill(entry.dir)
48+
const content = loadSkill(skillsDir, entry.dir)
3749

3850
api.registerCommand({
3951
name: commandName,

commands/slash.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { execSync } from "node:child_process"
22
import { dirname, resolve } from "node:path"
33
import { fileURLToPath } from "node:url"
4-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
4+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"
55
import type { BmClient } from "../bm-client.ts"
66
import { log } from "../logger.ts"
77

@@ -16,7 +16,9 @@ export function registerCommands(
1616
description: "Install or update the Basic Memory CLI (requires uv)",
1717
requireAuth: true,
1818
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")
2022
log.info(`/bm-setup: running ${scriptPath}`)
2123

2224
try {

config.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@ describe("config", () => {
147147
expect(parseConfig({ cloud: null }).cloud).toBeUndefined()
148148
})
149149

150+
it("should throw error for unknown cloud config keys", () => {
151+
expect(() =>
152+
parseConfig({
153+
cloud: {
154+
url: "https://cloud.basicmemory.com",
155+
api_key: "test-key",
156+
extra: true,
157+
},
158+
}),
159+
).toThrow("basic-memory cloud config has unknown keys: extra")
160+
})
161+
150162
it("should throw error for unknown config keys", () => {
151163
expect(() => parseConfig({ unknownKey: "value" })).toThrow(
152164
"basic-memory config has unknown keys: unknownKey",

config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export function parseConfig(raw: unknown): BasicMemoryConfig {
101101
let cloud: CloudConfig | undefined
102102
if (cfg.cloud && typeof cfg.cloud === "object" && !Array.isArray(cfg.cloud)) {
103103
const c = cfg.cloud as Record<string, unknown>
104+
assertAllowedKeys(c, ["url", "api_key"], "basic-memory cloud config")
104105
if (typeof c.url === "string" && typeof c.api_key === "string") {
105106
cloud = { url: c.url, api_key: c.api_key }
106107
}

context-engine/basic-memory-context-engine.ts

Lines changed: 38 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,52 @@
1-
import { createRequire } from "node:module"
2-
import { dirname, resolve } from "node:path"
3-
import { pathToFileURL } from "node:url"
41
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"
113
import type { BmClient } from "../bm-client.ts"
124
import type { BasicMemoryConfig } from "../config.ts"
135
import { selectCaptureTurn } from "../hooks/capture.ts"
146
import { loadRecallState } from "../hooks/recall.ts"
157
import { log } from "../logger.ts"
168

17-
const require = createRequire(import.meta.url)
189
export const MAX_ASSEMBLE_RECALL_CHARS = 1200
1910
const TRUNCATED_RECALL_SUFFIX = "\n\n[Basic Memory recall truncated]"
2011
const SUBAGENT_HANDOFF_FOLDER = "agent/subagents"
2112
const MAX_SUBAGENT_RECALL_CHARS = 800
2213

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+
2350
interface SessionMemoryState {
2451
recallContext: string
2552
}
@@ -104,36 +131,6 @@ function buildSubagentCompletionUpdate(params: {
104131
].join("\n")
105132
}
106133

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-
137134
export class BasicMemoryContextEngine implements ContextEngine {
138135
readonly info = {
139136
id: "openclaw-basic-memory",
@@ -144,9 +141,6 @@ export class BasicMemoryContextEngine implements ContextEngine {
144141

145142
private readonly sessionState = new Map<string, SessionMemoryState>()
146143
private readonly subagentState = new Map<string, SubagentHandoffState>()
147-
private legacyContextEnginePromise: Promise<
148-
InstanceType<LegacyContextEngineModule["LegacyContextEngine"]>
149-
> | null = null
150144

151145
constructor(
152146
private readonly client: BmClient,
@@ -243,8 +237,7 @@ export class BasicMemoryContextEngine implements ContextEngine {
243237
customInstructions?: string
244238
runtimeContext?: Record<string, unknown>
245239
}): Promise<CompactResult> {
246-
const legacy = await this.getLegacyContextEngine()
247-
return legacy.compact(params)
240+
return delegateCompactionToRuntime(params)
248241
}
249242

250243
async prepareSubagentSpawn(params: {
@@ -315,16 +308,4 @@ export class BasicMemoryContextEngine implements ContextEngine {
315308
this.sessionState.clear()
316309
this.subagentState.clear()
317310
}
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-
}
330311
}

0 commit comments

Comments
 (0)