@basicmemory/openclaw-basic-memory 0.1.0-alpha.9 → 0.1.2
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.
- package/README.md +140 -504
- package/bm-client.ts +33 -51
- package/config.ts +0 -24
- package/hooks/recall.ts +9 -10
- package/index.ts +54 -41
- package/openclaw.plugin.json +0 -12
- package/package.json +2 -2
- package/schema/conversation-schema.ts +33 -0
- package/tools/memory-provider.ts +21 -36
- package/analytics.ts +0 -162
package/bm-client.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { setTimeout as delay } from "node:timers/promises"
|
|
2
2
|
import { Client } from "@modelcontextprotocol/sdk/client"
|
|
3
3
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
4
|
-
import { trackToolCall } from "./analytics.ts"
|
|
5
4
|
import { log } from "./logger.ts"
|
|
6
5
|
|
|
7
6
|
const DEFAULT_RETRY_DELAYS_MS = [500, 1000, 2000]
|
|
8
7
|
|
|
9
8
|
const REQUIRED_TOOLS = [
|
|
10
9
|
"search_notes",
|
|
11
|
-
"search_by_metadata",
|
|
12
10
|
"read_note",
|
|
13
11
|
"write_note",
|
|
14
12
|
"edit_note",
|
|
@@ -154,12 +152,6 @@ export interface SchemaDiffResult {
|
|
|
154
152
|
cardinality_changes: string[]
|
|
155
153
|
}
|
|
156
154
|
|
|
157
|
-
export interface MetadataSearchResult {
|
|
158
|
-
results: SearchResult[]
|
|
159
|
-
current_page: number
|
|
160
|
-
page_size: number
|
|
161
|
-
}
|
|
162
|
-
|
|
163
155
|
function getErrorMessage(err: unknown): string {
|
|
164
156
|
return err instanceof Error ? err.message : String(err)
|
|
165
157
|
}
|
|
@@ -197,7 +189,7 @@ function isRecoverableConnectionError(err: unknown): boolean {
|
|
|
197
189
|
)
|
|
198
190
|
}
|
|
199
191
|
|
|
200
|
-
function
|
|
192
|
+
function _isNoteNotFoundError(err: unknown): boolean {
|
|
201
193
|
const msg = getErrorMessage(err).toLowerCase()
|
|
202
194
|
return (
|
|
203
195
|
msg.includes("entity not found") ||
|
|
@@ -450,7 +442,6 @@ export class BmClient {
|
|
|
450
442
|
name: string,
|
|
451
443
|
args: Record<string, unknown>,
|
|
452
444
|
): Promise<unknown> {
|
|
453
|
-
trackToolCall(name)
|
|
454
445
|
const result = await this.callToolRaw(name, args)
|
|
455
446
|
|
|
456
447
|
if (!isRecord(result) || result.structuredContent === undefined) {
|
|
@@ -504,25 +495,29 @@ export class BmClient {
|
|
|
504
495
|
}
|
|
505
496
|
|
|
506
497
|
async search(
|
|
507
|
-
query
|
|
498
|
+
query?: string,
|
|
508
499
|
limit = 10,
|
|
509
500
|
project?: string,
|
|
510
501
|
metadata?: {
|
|
511
502
|
filters?: Record<string, unknown>
|
|
512
503
|
tags?: string[]
|
|
513
504
|
status?: string
|
|
505
|
+
note_types?: string[]
|
|
506
|
+
entity_types?: string[]
|
|
514
507
|
},
|
|
515
508
|
): Promise<SearchResult[]> {
|
|
516
509
|
const args: Record<string, unknown> = {
|
|
517
|
-
query,
|
|
518
510
|
page: 1,
|
|
519
511
|
page_size: limit,
|
|
520
512
|
output_format: "json",
|
|
521
513
|
}
|
|
514
|
+
if (query) args.query = query
|
|
522
515
|
if (project) args.project = project
|
|
523
516
|
if (metadata?.filters) args.metadata_filters = metadata.filters
|
|
524
517
|
if (metadata?.tags) args.tags = metadata.tags
|
|
525
518
|
if (metadata?.status) args.status = metadata.status
|
|
519
|
+
if (metadata?.note_types) args.note_types = metadata.note_types
|
|
520
|
+
if (metadata?.entity_types) args.entity_types = metadata.entity_types
|
|
526
521
|
|
|
527
522
|
const payload = await this.callTool("search_notes", args)
|
|
528
523
|
|
|
@@ -661,11 +656,12 @@ export class BmClient {
|
|
|
661
656
|
identifier,
|
|
662
657
|
operation,
|
|
663
658
|
content,
|
|
664
|
-
find_text: options.find_text,
|
|
665
|
-
section: options.section,
|
|
666
|
-
expected_replacements: options.expected_replacements,
|
|
667
659
|
output_format: "json",
|
|
668
660
|
}
|
|
661
|
+
if (options.find_text) args.find_text = options.find_text
|
|
662
|
+
if (options.section) args.section = options.section
|
|
663
|
+
if (options.expected_replacements != null)
|
|
664
|
+
args.expected_replacements = options.expected_replacements
|
|
669
665
|
if (project) args.project = project
|
|
670
666
|
|
|
671
667
|
const payload = await this.callTool("edit_note", args)
|
|
@@ -810,31 +806,6 @@ export class BmClient {
|
|
|
810
806
|
return payload as unknown as SchemaDiffResult
|
|
811
807
|
}
|
|
812
808
|
|
|
813
|
-
async searchByMetadata(
|
|
814
|
-
filters: Record<string, unknown>,
|
|
815
|
-
limit = 20,
|
|
816
|
-
project?: string,
|
|
817
|
-
): Promise<MetadataSearchResult> {
|
|
818
|
-
const args: Record<string, unknown> = {
|
|
819
|
-
filters,
|
|
820
|
-
limit,
|
|
821
|
-
output_format: "json",
|
|
822
|
-
}
|
|
823
|
-
if (project) args.project = project
|
|
824
|
-
|
|
825
|
-
const payload = await this.callTool("search_by_metadata", args)
|
|
826
|
-
|
|
827
|
-
if (!isRecord(payload) || !Array.isArray(payload.results)) {
|
|
828
|
-
throw new Error("invalid search_by_metadata response")
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
return payload as unknown as MetadataSearchResult
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
private isNoteNotFoundError(err: unknown): boolean {
|
|
835
|
-
return isNoteNotFoundError(err)
|
|
836
|
-
}
|
|
837
|
-
|
|
838
809
|
async indexConversation(
|
|
839
810
|
userMessage: string,
|
|
840
811
|
assistantResponse: string,
|
|
@@ -856,22 +827,33 @@ export class BmClient {
|
|
|
856
827
|
"---",
|
|
857
828
|
].join("\n")
|
|
858
829
|
|
|
830
|
+
// Try append first — if it fails for ANY reason, fall through to create
|
|
859
831
|
try {
|
|
860
832
|
await this.editNote(title, "append", entry)
|
|
861
833
|
log.debug(`appended conversation to: ${title}`)
|
|
834
|
+
return
|
|
862
835
|
} catch (err) {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
return
|
|
866
|
-
}
|
|
836
|
+
log.debug(`append failed, will create: ${getErrorMessage(err)}`)
|
|
837
|
+
}
|
|
867
838
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
839
|
+
// Create the note with frontmatter and first entry
|
|
840
|
+
const content = [
|
|
841
|
+
"---",
|
|
842
|
+
`title: Conversations ${dateStr}`,
|
|
843
|
+
"type: Conversation",
|
|
844
|
+
`date: "${dateStr}"`,
|
|
845
|
+
"---",
|
|
846
|
+
"",
|
|
847
|
+
`# Conversations ${dateStr}`,
|
|
848
|
+
"",
|
|
849
|
+
entry,
|
|
850
|
+
].join("\n")
|
|
851
|
+
|
|
852
|
+
try {
|
|
853
|
+
await this.writeNote(title, content, "conversations")
|
|
854
|
+
log.debug(`created conversation note: ${title}`)
|
|
855
|
+
} catch (err) {
|
|
856
|
+
log.error("conversation index failed", err)
|
|
875
857
|
}
|
|
876
858
|
}
|
|
877
859
|
|
package/config.ts
CHANGED
|
@@ -6,11 +6,6 @@ export type CloudConfig = {
|
|
|
6
6
|
api_key: string
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export type DashboardConfig = {
|
|
10
|
-
enabled: boolean
|
|
11
|
-
port: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
9
|
export type BasicMemoryConfig = {
|
|
15
10
|
project: string
|
|
16
11
|
bmPath: string
|
|
@@ -23,7 +18,6 @@ export type BasicMemoryConfig = {
|
|
|
23
18
|
recallPrompt: string
|
|
24
19
|
debug: boolean
|
|
25
20
|
cloud?: CloudConfig
|
|
26
|
-
dashboard: DashboardConfig
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
const ALLOWED_KEYS = [
|
|
@@ -43,7 +37,6 @@ const ALLOWED_KEYS = [
|
|
|
43
37
|
"recall_prompt",
|
|
44
38
|
"debug",
|
|
45
39
|
"cloud",
|
|
46
|
-
"dashboard",
|
|
47
40
|
]
|
|
48
41
|
|
|
49
42
|
function assertAllowedKeys(
|
|
@@ -113,22 +106,6 @@ export function parseConfig(raw: unknown): BasicMemoryConfig {
|
|
|
113
106
|
}
|
|
114
107
|
}
|
|
115
108
|
|
|
116
|
-
let dashboard: DashboardConfig = { enabled: false, port: 3838 }
|
|
117
|
-
if (
|
|
118
|
-
cfg.dashboard &&
|
|
119
|
-
typeof cfg.dashboard === "object" &&
|
|
120
|
-
!Array.isArray(cfg.dashboard)
|
|
121
|
-
) {
|
|
122
|
-
const d = cfg.dashboard as Record<string, unknown>
|
|
123
|
-
dashboard = {
|
|
124
|
-
enabled: typeof d.enabled === "boolean" ? d.enabled : false,
|
|
125
|
-
port:
|
|
126
|
-
typeof d.port === "number" && d.port > 0 && d.port < 65536
|
|
127
|
-
? d.port
|
|
128
|
-
: 3838,
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
109
|
return {
|
|
133
110
|
project:
|
|
134
111
|
typeof cfg.project === "string" && cfg.project.length > 0
|
|
@@ -167,7 +144,6 @@ export function parseConfig(raw: unknown): BasicMemoryConfig {
|
|
|
167
144
|
: "Check for active tasks and recent activity. Summarize anything relevant to the current session.",
|
|
168
145
|
debug: typeof cfg.debug === "boolean" ? cfg.debug : false,
|
|
169
146
|
cloud,
|
|
170
|
-
dashboard,
|
|
171
147
|
}
|
|
172
148
|
}
|
|
173
149
|
|
package/hooks/recall.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BmClient,
|
|
3
|
-
MetadataSearchResult,
|
|
4
|
-
RecentResult,
|
|
5
|
-
} from "../bm-client.ts"
|
|
1
|
+
import type { BmClient, RecentResult, SearchResult } from "../bm-client.ts"
|
|
6
2
|
import type { BasicMemoryConfig } from "../config.ts"
|
|
7
3
|
import { log } from "../logger.ts"
|
|
8
4
|
|
|
@@ -11,14 +7,14 @@ import { log } from "../logger.ts"
|
|
|
11
7
|
* Returns empty string if nothing was found.
|
|
12
8
|
*/
|
|
13
9
|
export function formatRecallContext(
|
|
14
|
-
tasks:
|
|
10
|
+
tasks: SearchResult[],
|
|
15
11
|
recent: RecentResult[],
|
|
16
12
|
prompt: string,
|
|
17
13
|
): string {
|
|
18
14
|
const sections: string[] = []
|
|
19
15
|
|
|
20
|
-
if (tasks.
|
|
21
|
-
const taskLines = tasks.
|
|
16
|
+
if (tasks.length > 0) {
|
|
17
|
+
const taskLines = tasks.map((t) => {
|
|
22
18
|
const preview =
|
|
23
19
|
t.content.length > 120 ? `${t.content.slice(0, 120)}...` : t.content
|
|
24
20
|
return `- **${t.title}** — ${preview}`
|
|
@@ -46,7 +42,10 @@ export function buildRecallHandler(client: BmClient, cfg: BasicMemoryConfig) {
|
|
|
46
42
|
return async (_event: Record<string, unknown>) => {
|
|
47
43
|
try {
|
|
48
44
|
const [tasks, recent] = await Promise.all([
|
|
49
|
-
client.
|
|
45
|
+
client.search(undefined, 5, undefined, {
|
|
46
|
+
note_types: ["Task"],
|
|
47
|
+
status: "active",
|
|
48
|
+
}),
|
|
50
49
|
client.recentActivity("1d"),
|
|
51
50
|
])
|
|
52
51
|
|
|
@@ -54,7 +53,7 @@ export function buildRecallHandler(client: BmClient, cfg: BasicMemoryConfig) {
|
|
|
54
53
|
if (!context) return {}
|
|
55
54
|
|
|
56
55
|
log.debug(
|
|
57
|
-
`recall: ${tasks.
|
|
56
|
+
`recall: ${tasks.length} active tasks, ${recent.length} recent items`,
|
|
58
57
|
)
|
|
59
58
|
|
|
60
59
|
return { context }
|
package/index.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { execSync } from "node:child_process"
|
|
2
|
+
|
|
2
3
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
3
|
-
import {
|
|
4
|
-
EVENT_PLUGIN_INSTALLED,
|
|
5
|
-
EVENT_PLUGIN_STARTED,
|
|
6
|
-
track,
|
|
7
|
-
} from "./analytics.ts"
|
|
8
4
|
import { BmClient } from "./bm-client.ts"
|
|
9
5
|
import { registerCli } from "./commands/cli.ts"
|
|
10
6
|
import { registerSkillCommands } from "./commands/skills.ts"
|
|
@@ -14,10 +10,11 @@ import {
|
|
|
14
10
|
parseConfig,
|
|
15
11
|
resolveProjectPath,
|
|
16
12
|
} from "./config.ts"
|
|
17
|
-
|
|
13
|
+
|
|
18
14
|
import { buildCaptureHandler } from "./hooks/capture.ts"
|
|
19
15
|
import { buildRecallHandler } from "./hooks/recall.ts"
|
|
20
16
|
import { initLogger, log } from "./logger.ts"
|
|
17
|
+
import { CONVERSATION_SCHEMA_CONTENT } from "./schema/conversation-schema.ts"
|
|
21
18
|
import { TASK_SCHEMA_CONTENT } from "./schema/task-schema.ts"
|
|
22
19
|
import { registerContextTool } from "./tools/build-context.ts"
|
|
23
20
|
import { registerDeleteTool } from "./tools/delete-note.ts"
|
|
@@ -53,8 +50,6 @@ export default {
|
|
|
53
50
|
`project=${cfg.project} memoryDir=${cfg.memoryDir} memoryFile=${cfg.memoryFile}`,
|
|
54
51
|
)
|
|
55
52
|
|
|
56
|
-
track(EVENT_PLUGIN_INSTALLED)
|
|
57
|
-
|
|
58
53
|
const client = new BmClient(cfg.bmPath, cfg.project)
|
|
59
54
|
|
|
60
55
|
// --- BM Tools (always registered) ---
|
|
@@ -89,13 +84,47 @@ export default {
|
|
|
89
84
|
registerCli(api, client, cfg)
|
|
90
85
|
|
|
91
86
|
// --- Service lifecycle ---
|
|
92
|
-
let dashboardServer: Server | undefined
|
|
93
|
-
|
|
94
87
|
api.registerService({
|
|
95
88
|
id: "openclaw-basic-memory",
|
|
96
89
|
start: async (ctx: { config?: unknown; workspaceDir?: string }) => {
|
|
97
90
|
log.info("starting...")
|
|
98
91
|
|
|
92
|
+
// Auto-install bm CLI if not found
|
|
93
|
+
const bmBin = cfg.bmPath || "bm"
|
|
94
|
+
try {
|
|
95
|
+
execSync(`command -v ${bmBin}`, { stdio: "ignore" })
|
|
96
|
+
} catch {
|
|
97
|
+
log.info("bm CLI not found on PATH — attempting auto-install...")
|
|
98
|
+
try {
|
|
99
|
+
execSync("command -v uv", { stdio: "ignore" })
|
|
100
|
+
log.info(
|
|
101
|
+
"installing basic-memory via uv (this may take a minute)...",
|
|
102
|
+
)
|
|
103
|
+
const result = execSync(
|
|
104
|
+
'uv tool install "basic-memory @ git+https://github.com/basicmachines-co/basic-memory.git@main" --force',
|
|
105
|
+
{ encoding: "utf-8", timeout: 120_000, stdio: "pipe" },
|
|
106
|
+
)
|
|
107
|
+
log.info(
|
|
108
|
+
`basic-memory installed: ${result.trim().split("\n").pop()}`,
|
|
109
|
+
)
|
|
110
|
+
// Verify it worked
|
|
111
|
+
try {
|
|
112
|
+
execSync(`command -v ${bmBin}`, { stdio: "ignore" })
|
|
113
|
+
log.info("bm CLI now available on PATH")
|
|
114
|
+
} catch {
|
|
115
|
+
log.error(
|
|
116
|
+
"bm installed but not found on PATH. You may need to add uv's bin directory to your PATH (typically ~/.local/bin).",
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
} catch (_uvErr) {
|
|
120
|
+
log.error(
|
|
121
|
+
"Cannot auto-install basic-memory: uv not found. " +
|
|
122
|
+
"Install uv first (brew install uv, or curl -LsSf https://astral.sh/uv/install.sh | sh), " +
|
|
123
|
+
"then restart the gateway.",
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
99
128
|
const workspace = ctx.workspaceDir ?? process.cwd()
|
|
100
129
|
const projectPath = resolveProjectPath(cfg.projectPath, workspace)
|
|
101
130
|
cfg.projectPath = projectPath
|
|
@@ -104,46 +133,30 @@ export default {
|
|
|
104
133
|
await client.ensureProject(projectPath)
|
|
105
134
|
log.debug(`project "${cfg.project}" at ${projectPath}`)
|
|
106
135
|
|
|
107
|
-
// Seed
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
// Seed schemas if not already present
|
|
137
|
+
for (const [name, content] of [
|
|
138
|
+
["Task", TASK_SCHEMA_CONTENT],
|
|
139
|
+
["Conversation", CONVERSATION_SCHEMA_CONTENT],
|
|
140
|
+
] as const) {
|
|
112
141
|
try {
|
|
113
|
-
await client.
|
|
114
|
-
log.debug(
|
|
115
|
-
} catch
|
|
116
|
-
|
|
142
|
+
await client.readNote(`schema/${name}`)
|
|
143
|
+
log.debug(`${name} schema already exists, skipping seed`)
|
|
144
|
+
} catch {
|
|
145
|
+
try {
|
|
146
|
+
await client.writeNote(name, content, "schema")
|
|
147
|
+
log.debug(`seeded ${name} schema note`)
|
|
148
|
+
} catch (err) {
|
|
149
|
+
log.debug(`${name} schema seed failed (non-fatal)`, err)
|
|
150
|
+
}
|
|
117
151
|
}
|
|
118
152
|
}
|
|
119
153
|
|
|
120
154
|
setWorkspaceDir(workspace)
|
|
121
155
|
|
|
122
|
-
// Start dashboard if enabled
|
|
123
|
-
if (cfg.dashboard.enabled) {
|
|
124
|
-
dashboardServer = createDashboardServer({
|
|
125
|
-
port: cfg.dashboard.port,
|
|
126
|
-
client,
|
|
127
|
-
})
|
|
128
|
-
dashboardServer.listen(cfg.dashboard.port, () => {
|
|
129
|
-
log.info(
|
|
130
|
-
`dashboard running at http://localhost:${cfg.dashboard.port}`,
|
|
131
|
-
)
|
|
132
|
-
})
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
track(EVENT_PLUGIN_STARTED, { project: cfg.project })
|
|
136
156
|
log.info("connected — BM MCP stdio session running")
|
|
137
157
|
},
|
|
138
158
|
stop: async () => {
|
|
139
159
|
log.info("stopping BM MCP session...")
|
|
140
|
-
if (dashboardServer) {
|
|
141
|
-
await new Promise<void>((resolve) =>
|
|
142
|
-
dashboardServer?.close(() => resolve()),
|
|
143
|
-
)
|
|
144
|
-
dashboardServer = undefined
|
|
145
|
-
log.info("dashboard stopped")
|
|
146
|
-
}
|
|
147
160
|
await client.stop()
|
|
148
161
|
log.info("stopped")
|
|
149
162
|
},
|
package/openclaw.plugin.json
CHANGED
|
@@ -56,11 +56,6 @@
|
|
|
56
56
|
"label": "Cloud Backend",
|
|
57
57
|
"help": "Optional cloud backend config (url + api_key). If present, uses cloud instead of local BM.",
|
|
58
58
|
"advanced": true
|
|
59
|
-
},
|
|
60
|
-
"dashboard": {
|
|
61
|
-
"label": "Dashboard",
|
|
62
|
-
"help": "Web dashboard for visualizing the knowledge graph (enabled, port)",
|
|
63
|
-
"advanced": true
|
|
64
59
|
}
|
|
65
60
|
},
|
|
66
61
|
"configSchema": {
|
|
@@ -81,13 +76,6 @@
|
|
|
81
76
|
"url": { "type": "string" },
|
|
82
77
|
"api_key": { "type": "string" }
|
|
83
78
|
}
|
|
84
|
-
},
|
|
85
|
-
"dashboard": {
|
|
86
|
-
"type": "object",
|
|
87
|
-
"properties": {
|
|
88
|
-
"enabled": { "type": "boolean" },
|
|
89
|
-
"port": { "type": "number", "minimum": 1, "maximum": 65535 }
|
|
90
|
-
}
|
|
91
79
|
}
|
|
92
80
|
},
|
|
93
81
|
"required": []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basicmemory/openclaw-basic-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Basic Memory plugin for OpenClaw — local-first knowledge graph for agent memory",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"url": "https://github.com/basicmachines-co/openclaw-basic-memory"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"analytics.ts",
|
|
13
12
|
"index.ts",
|
|
14
13
|
"bm-client.ts",
|
|
15
14
|
"config.ts",
|
|
@@ -34,6 +33,7 @@
|
|
|
34
33
|
"tools/write-note.ts",
|
|
35
34
|
"types/openclaw.d.ts",
|
|
36
35
|
"schema/task-schema.ts",
|
|
36
|
+
"schema/conversation-schema.ts",
|
|
37
37
|
"skills/",
|
|
38
38
|
"scripts/setup-bm.sh",
|
|
39
39
|
"openclaw.plugin.json",
|
|
@@ -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
|
+
`
|
package/tools/memory-provider.ts
CHANGED
|
@@ -60,8 +60,8 @@ async function searchMemoryFile(
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Search for active tasks via BM knowledge graph.
|
|
63
|
-
* Uses
|
|
64
|
-
* Falls back to filesystem scan if
|
|
63
|
+
* Uses search_notes with note_types and status filters for precise querying.
|
|
64
|
+
* Falls back to filesystem scan if search fails.
|
|
65
65
|
*/
|
|
66
66
|
async function searchActiveTasks(
|
|
67
67
|
query: string,
|
|
@@ -69,44 +69,29 @@ async function searchActiveTasks(
|
|
|
69
69
|
workspaceDir: string,
|
|
70
70
|
memoryDir: string,
|
|
71
71
|
): Promise<string> {
|
|
72
|
-
// Try structured
|
|
72
|
+
// Try structured search first
|
|
73
73
|
try {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (filtered.length > 0) {
|
|
91
|
-
const matches: string[] = []
|
|
92
|
-
for (const r of filtered) {
|
|
93
|
-
const score = r.score ? ` (${r.score.toFixed(2)})` : ""
|
|
94
|
-
const preview =
|
|
95
|
-
(r.content ?? "").length > 200
|
|
96
|
-
? `${(r.content ?? "").slice(0, 200)}…`
|
|
97
|
-
: (r.content ?? "")
|
|
98
|
-
matches.push(
|
|
99
|
-
`- **${r.title}**${score} — ${r.file_path}\n > ${preview.replace(/\n/g, "\n > ")}`,
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
return matches.join("\n\n")
|
|
74
|
+
const results = await client.search(query || undefined, 10, undefined, {
|
|
75
|
+
note_types: ["task"],
|
|
76
|
+
status: "active",
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (results.length > 0) {
|
|
80
|
+
const matches: string[] = []
|
|
81
|
+
for (const r of results) {
|
|
82
|
+
const score = r.score ? ` (${r.score.toFixed(2)})` : ""
|
|
83
|
+
const preview =
|
|
84
|
+
(r.content ?? "").length > 200
|
|
85
|
+
? `${(r.content ?? "").slice(0, 200)}…`
|
|
86
|
+
: (r.content ?? "")
|
|
87
|
+
matches.push(
|
|
88
|
+
`- **${r.title}**${score} — ${r.file_path}\n > ${preview.replace(/\n/g, "\n > ")}`,
|
|
89
|
+
)
|
|
103
90
|
}
|
|
91
|
+
return matches.join("\n\n")
|
|
104
92
|
}
|
|
105
93
|
} catch (err) {
|
|
106
|
-
log.debug(
|
|
107
|
-
"BM metadata task search failed, falling back to filesystem scan",
|
|
108
|
-
err,
|
|
109
|
-
)
|
|
94
|
+
log.debug("BM task search failed, falling back to filesystem scan", err)
|
|
110
95
|
}
|
|
111
96
|
|
|
112
97
|
// Fallback: filesystem scan for tasks not yet indexed
|