clawvault 3.4.0 → 3.4.1
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 +26 -26
- package/dist/{chunk-QYQAGBTM.js → chunk-PLNK37JD.js} +164 -38
- package/dist/index.js +1 -1
- package/dist/openclaw-plugin.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -267,23 +267,24 @@ clawvault setup --theme neural --canvas --bases
|
|
|
267
267
|
|
|
268
268
|
## OpenClaw Integration
|
|
269
269
|
|
|
270
|
-
|
|
270
|
+
ClawVault integrates with OpenClaw as a plugin package (not the deprecated `openclaw hooks install/enable` flow):
|
|
271
271
|
|
|
272
272
|
```bash
|
|
273
|
-
# Install
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
# Install ClawVault
|
|
274
|
+
npm install -g clawvault
|
|
275
|
+
|
|
276
|
+
# Add ClawVault package path to plugins.load.paths in openclaw.json
|
|
277
|
+
# (You can use `npm root -g` to locate node_modules)
|
|
276
278
|
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
279
|
+
# Enable plugin + memory slot in openclaw.json:
|
|
280
|
+
# plugins.slots.memory = "clawvault"
|
|
281
|
+
# plugins.entries.clawvault.enabled = true
|
|
282
|
+
|
|
283
|
+
# Verify runtime assumptions
|
|
280
284
|
clawvault compat
|
|
281
285
|
```
|
|
282
286
|
|
|
283
|
-
The
|
|
284
|
-
- Detects context death and injects recovery alerts
|
|
285
|
-
- Auto-checkpoints before session resets
|
|
286
|
-
- Provides `--profile auto` for context queries
|
|
287
|
+
The plugin provides lifecycle hooks, memory tools, and protocol-safe messaging behavior for OpenClaw sessions.
|
|
287
288
|
|
|
288
289
|
### MEMORY.md vs Vault
|
|
289
290
|
|
|
@@ -351,27 +352,26 @@ clawvault compat
|
|
|
351
352
|
|
|
352
353
|
## OpenClaw Setup (Canonical)
|
|
353
354
|
|
|
354
|
-
|
|
355
|
+
Use this sequence for plugin-based OpenClaw integration:
|
|
355
356
|
|
|
356
357
|
```bash
|
|
357
358
|
# Install CLI
|
|
358
359
|
npm install -g clawvault
|
|
359
360
|
|
|
360
|
-
#
|
|
361
|
-
|
|
362
|
-
openclaw hooks enable clawvault
|
|
361
|
+
# Locate global node_modules
|
|
362
|
+
npm root -g
|
|
363
363
|
|
|
364
|
-
#
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
clawvault
|
|
364
|
+
# In openclaw.json:
|
|
365
|
+
# - add <npm-root>/clawvault to plugins.load.paths
|
|
366
|
+
# - set plugins.slots.memory = "clawvault"
|
|
367
|
+
# - set plugins.entries.clawvault.enabled = true
|
|
368
|
+
# - set plugins.entries.clawvault.config.vaultPath as needed
|
|
369
369
|
```
|
|
370
370
|
|
|
371
371
|
Important:
|
|
372
372
|
|
|
373
|
-
- `clawhub install clawvault` installs skill guidance, but does not
|
|
374
|
-
- After
|
|
373
|
+
- `clawhub install clawvault` installs skill guidance, but does not configure OpenClaw plugin loading.
|
|
374
|
+
- After changing plugin config, restart the OpenClaw gateway process.
|
|
375
375
|
|
|
376
376
|
## Minimal AGENTS.md Additions
|
|
377
377
|
|
|
@@ -488,10 +488,10 @@ vault/
|
|
|
488
488
|
- `qmd` fallback errors:
|
|
489
489
|
- `qmd` is optional; in-process BM25 search is available without it
|
|
490
490
|
- if you want fallback compatibility, ensure `qmd --version` works in the same shell
|
|
491
|
-
-
|
|
492
|
-
-
|
|
493
|
-
-
|
|
494
|
-
- verify
|
|
491
|
+
- Plugin not active in OpenClaw:
|
|
492
|
+
- verify `plugins.load.paths` includes the ClawVault package path
|
|
493
|
+
- verify `plugins.slots.memory` is `clawvault`
|
|
494
|
+
- verify `plugins.entries.clawvault.enabled` is `true`
|
|
495
495
|
- OpenClaw integration drift:
|
|
496
496
|
- run `clawvault compat`
|
|
497
497
|
- Session transcript corruption:
|
|
@@ -833,38 +833,128 @@ function truncateRecapSnippet(snippet) {
|
|
|
833
833
|
if (safe.length <= MAX_RECAP_SNIPPET_LENGTH) return safe;
|
|
834
834
|
return `${safe.slice(0, MAX_RECAP_SNIPPET_LENGTH - 3).trimEnd()}...`;
|
|
835
835
|
}
|
|
836
|
+
function isRecord2(value) {
|
|
837
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
838
|
+
}
|
|
839
|
+
function parseTopLevelJson(output) {
|
|
840
|
+
const text = output.trim();
|
|
841
|
+
if (!text) return null;
|
|
842
|
+
const tryParse = (candidate) => {
|
|
843
|
+
try {
|
|
844
|
+
const parsed = JSON.parse(candidate);
|
|
845
|
+
return isRecord2(parsed) ? parsed : null;
|
|
846
|
+
} catch {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
const direct = tryParse(text);
|
|
851
|
+
if (direct) return direct;
|
|
852
|
+
const findJsonEnd = (start) => {
|
|
853
|
+
const open = text[start];
|
|
854
|
+
if (open !== "{" && open !== "[") return -1;
|
|
855
|
+
const stack = [open];
|
|
856
|
+
let inString = false;
|
|
857
|
+
let escaped = false;
|
|
858
|
+
for (let i = start + 1; i < text.length; i += 1) {
|
|
859
|
+
const ch = text[i];
|
|
860
|
+
if (inString) {
|
|
861
|
+
if (escaped) {
|
|
862
|
+
escaped = false;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (ch === "\\") {
|
|
866
|
+
escaped = true;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (ch === '"') {
|
|
870
|
+
inString = false;
|
|
871
|
+
}
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (ch === '"') {
|
|
875
|
+
inString = true;
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (ch === "{" || ch === "[") {
|
|
879
|
+
stack.push(ch);
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
if (ch === "}" || ch === "]") {
|
|
883
|
+
const expected = ch === "}" ? "{" : "[";
|
|
884
|
+
const top = stack[stack.length - 1];
|
|
885
|
+
if (top !== expected) return -1;
|
|
886
|
+
stack.pop();
|
|
887
|
+
if (stack.length === 0) {
|
|
888
|
+
return i;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return -1;
|
|
893
|
+
};
|
|
894
|
+
for (let start = 0; start < text.length; start += 1) {
|
|
895
|
+
const ch = text[start];
|
|
896
|
+
if (ch !== "{" && ch !== "[") continue;
|
|
897
|
+
const end = findJsonEnd(start);
|
|
898
|
+
if (end < 0) continue;
|
|
899
|
+
const parsed = tryParse(text.slice(start, end + 1));
|
|
900
|
+
if (parsed) return parsed;
|
|
901
|
+
}
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
function resolveEntryArray(parsed, keys) {
|
|
905
|
+
for (const key of keys) {
|
|
906
|
+
const value = parsed[key];
|
|
907
|
+
if (!Array.isArray(value)) continue;
|
|
908
|
+
return value.filter((item) => isRecord2(item));
|
|
909
|
+
}
|
|
910
|
+
return [];
|
|
911
|
+
}
|
|
836
912
|
function parseContextJson(output, maxResults) {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (!parsed || !Array.isArray(parsed.context)) return [];
|
|
840
|
-
return parsed.context.slice(0, maxResults).map((entry) => ({
|
|
841
|
-
title: sanitizeForDisplay(entry.title ?? "Untitled"),
|
|
842
|
-
path: sanitizeForDisplay(entry.path ?? ""),
|
|
843
|
-
age: sanitizeForDisplay(entry.age ?? "unknown age"),
|
|
844
|
-
snippet: truncateSnippet(String(entry.snippet ?? "")),
|
|
845
|
-
score: Number.isFinite(Number(entry.score)) ? Number(entry.score) : 0
|
|
846
|
-
})).filter((entry) => entry.snippet.length > 0);
|
|
847
|
-
} catch {
|
|
913
|
+
const parsed = parseTopLevelJson(output);
|
|
914
|
+
if (!parsed) {
|
|
848
915
|
return [];
|
|
849
916
|
}
|
|
917
|
+
const rows = resolveEntryArray(parsed, ["context", "results", "entries", "memories"]);
|
|
918
|
+
return rows.slice(0, maxResults).map((entry) => {
|
|
919
|
+
const nestedDocument = isRecord2(entry.document) ? entry.document : null;
|
|
920
|
+
const title = sanitizeForDisplay(
|
|
921
|
+
entry.title ?? nestedDocument?.title ?? nestedDocument?.id ?? entry.path ?? "Untitled"
|
|
922
|
+
);
|
|
923
|
+
const resolvedPath = sanitizeForDisplay(
|
|
924
|
+
entry.path ?? entry.relPath ?? nestedDocument?.path ?? ""
|
|
925
|
+
);
|
|
926
|
+
const resolvedAge = sanitizeForDisplay(entry.age ?? entry.modified ?? "unknown age");
|
|
927
|
+
const snippetSource = String(
|
|
928
|
+
entry.snippet ?? entry.text ?? entry.content ?? nestedDocument?.snippet ?? nestedDocument?.content ?? ""
|
|
929
|
+
);
|
|
930
|
+
return {
|
|
931
|
+
title,
|
|
932
|
+
path: resolvedPath,
|
|
933
|
+
age: resolvedAge,
|
|
934
|
+
snippet: truncateSnippet(snippetSource),
|
|
935
|
+
score: Number.isFinite(Number(entry.score)) ? Number(entry.score) : 0
|
|
936
|
+
};
|
|
937
|
+
}).filter((entry) => entry.snippet.length > 0);
|
|
850
938
|
}
|
|
851
939
|
function parseSessionRecapJson(output, maxResults) {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
if (!parsed || !Array.isArray(parsed.messages)) return [];
|
|
855
|
-
return parsed.messages.map((entry) => {
|
|
856
|
-
const role = typeof entry.role === "string" ? entry.role.toLowerCase() : "";
|
|
857
|
-
if (role !== "user" && role !== "assistant") return null;
|
|
858
|
-
const text = truncateRecapSnippet(typeof entry.text === "string" ? entry.text : "");
|
|
859
|
-
if (!text) return null;
|
|
860
|
-
return {
|
|
861
|
-
role: role === "user" ? "User" : "Assistant",
|
|
862
|
-
text
|
|
863
|
-
};
|
|
864
|
-
}).filter((entry) => Boolean(entry)).slice(-maxResults);
|
|
865
|
-
} catch {
|
|
940
|
+
const parsed = parseTopLevelJson(output);
|
|
941
|
+
if (!parsed) {
|
|
866
942
|
return [];
|
|
867
943
|
}
|
|
944
|
+
const rows = resolveEntryArray(parsed, ["messages", "turns", "recap"]);
|
|
945
|
+
return rows.map((entry) => {
|
|
946
|
+
const role = typeof entry.role === "string" ? entry.role.toLowerCase() : "";
|
|
947
|
+
const normalizedRole = role === "user" || role === "human" ? "User" : role === "assistant" || role === "ai" ? "Assistant" : "";
|
|
948
|
+
if (!normalizedRole) return null;
|
|
949
|
+
const text = truncateRecapSnippet(
|
|
950
|
+
typeof entry.text === "string" ? entry.text : typeof entry.content === "string" ? entry.content : ""
|
|
951
|
+
);
|
|
952
|
+
if (!text) return null;
|
|
953
|
+
return {
|
|
954
|
+
role: normalizedRole,
|
|
955
|
+
text
|
|
956
|
+
};
|
|
957
|
+
}).filter((entry) => Boolean(entry)).slice(-maxResults);
|
|
868
958
|
}
|
|
869
959
|
function formatSessionContextInjection(recapEntries, memoryEntries) {
|
|
870
960
|
const lines = [
|
|
@@ -1132,8 +1222,8 @@ function toSafeFilePath(vaultPath, relPath) {
|
|
|
1132
1222
|
if (!mapped || mapped.includes("..")) {
|
|
1133
1223
|
throw new Error("Invalid memory path");
|
|
1134
1224
|
}
|
|
1135
|
-
if (
|
|
1136
|
-
throw new Error("memory_get only allows
|
|
1225
|
+
if (!mapped.toLowerCase().endsWith(".md")) {
|
|
1226
|
+
throw new Error("memory_get only allows Markdown note paths inside the vault");
|
|
1137
1227
|
}
|
|
1138
1228
|
const absolute = path4.resolve(vaultPath, mapped);
|
|
1139
1229
|
const vaultRootWithSep = vaultPath.endsWith(path4.sep) ? vaultPath : `${vaultPath}${path4.sep}`;
|
|
@@ -1260,8 +1350,17 @@ function buildToolSchema(properties, required = []) {
|
|
|
1260
1350
|
additionalProperties: false
|
|
1261
1351
|
};
|
|
1262
1352
|
}
|
|
1353
|
+
function resolveToolInput(toolCallIdOrInput, maybeInput) {
|
|
1354
|
+
if (maybeInput && typeof maybeInput === "object" && !Array.isArray(maybeInput)) {
|
|
1355
|
+
return maybeInput;
|
|
1356
|
+
}
|
|
1357
|
+
if (toolCallIdOrInput && typeof toolCallIdOrInput === "object" && !Array.isArray(toolCallIdOrInput)) {
|
|
1358
|
+
return toolCallIdOrInput;
|
|
1359
|
+
}
|
|
1360
|
+
return {};
|
|
1361
|
+
}
|
|
1263
1362
|
function createMemorySearchToolFactory(memoryManager) {
|
|
1264
|
-
return () => {
|
|
1363
|
+
return (_toolContext) => {
|
|
1265
1364
|
const inputSchema = buildToolSchema({
|
|
1266
1365
|
query: {
|
|
1267
1366
|
type: "string",
|
|
@@ -1284,7 +1383,8 @@ function createMemorySearchToolFactory(memoryManager) {
|
|
|
1284
1383
|
description: "Optional OpenClaw session key for scoped recall."
|
|
1285
1384
|
}
|
|
1286
1385
|
}, ["query"]);
|
|
1287
|
-
const execute = async (
|
|
1386
|
+
const execute = async (toolCallIdOrInput, maybeInput) => {
|
|
1387
|
+
const input = resolveToolInput(toolCallIdOrInput, maybeInput);
|
|
1288
1388
|
const query = typeof input.query === "string" ? input.query : "";
|
|
1289
1389
|
if (!query.trim()) {
|
|
1290
1390
|
return { query, count: 0, results: [] };
|
|
@@ -1301,6 +1401,7 @@ function createMemorySearchToolFactory(memoryManager) {
|
|
|
1301
1401
|
};
|
|
1302
1402
|
};
|
|
1303
1403
|
return {
|
|
1404
|
+
label: "Memory Search",
|
|
1304
1405
|
name: "memory_search",
|
|
1305
1406
|
description: "Search ClawVault memory for relevant snippets before answering.",
|
|
1306
1407
|
inputSchema,
|
|
@@ -1313,11 +1414,15 @@ function createMemorySearchToolFactory(memoryManager) {
|
|
|
1313
1414
|
};
|
|
1314
1415
|
}
|
|
1315
1416
|
function createMemoryGetToolFactory(memoryManager) {
|
|
1316
|
-
return () => {
|
|
1417
|
+
return (_toolContext) => {
|
|
1317
1418
|
const inputSchema = buildToolSchema({
|
|
1419
|
+
path: {
|
|
1420
|
+
type: "string",
|
|
1421
|
+
description: "Relative path from memory_search result (for OpenClaw compatibility)."
|
|
1422
|
+
},
|
|
1318
1423
|
relPath: {
|
|
1319
1424
|
type: "string",
|
|
1320
|
-
description: "
|
|
1425
|
+
description: "Alias of path (e.g. memory/2026-01-01.md)."
|
|
1321
1426
|
},
|
|
1322
1427
|
from: {
|
|
1323
1428
|
type: "number",
|
|
@@ -1330,9 +1435,11 @@ function createMemoryGetToolFactory(memoryManager) {
|
|
|
1330
1435
|
maximum: 400,
|
|
1331
1436
|
description: "Optional number of lines to read."
|
|
1332
1437
|
}
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1438
|
+
});
|
|
1439
|
+
inputSchema.anyOf = [{ required: ["path"] }, { required: ["relPath"] }];
|
|
1440
|
+
const execute = async (toolCallIdOrInput, maybeInput) => {
|
|
1441
|
+
const input = resolveToolInput(toolCallIdOrInput, maybeInput);
|
|
1442
|
+
const relPath = typeof input.path === "string" ? input.path : typeof input.relPath === "string" ? input.relPath : "";
|
|
1336
1443
|
if (!relPath.trim()) {
|
|
1337
1444
|
return { path: relPath, text: "" };
|
|
1338
1445
|
}
|
|
@@ -1343,6 +1450,7 @@ function createMemoryGetToolFactory(memoryManager) {
|
|
|
1343
1450
|
});
|
|
1344
1451
|
};
|
|
1345
1452
|
return {
|
|
1453
|
+
label: "Memory Get",
|
|
1346
1454
|
name: "memory_get",
|
|
1347
1455
|
description: "Read a specific memory file or line range from ClawVault.",
|
|
1348
1456
|
inputSchema,
|
|
@@ -1509,11 +1617,27 @@ async function fetchMemoryContextEntries(options) {
|
|
|
1509
1617
|
vaultPath
|
|
1510
1618
|
];
|
|
1511
1619
|
const contextResult = runClawvault(contextArgs, options.pluginConfig, { timeoutMs: 25e3 });
|
|
1512
|
-
if (
|
|
1620
|
+
if (contextResult.success) {
|
|
1621
|
+
return {
|
|
1622
|
+
entries: parseContextJson(contextResult.output, maxResults),
|
|
1623
|
+
vaultPath
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
const fallbackSearchArgs = [
|
|
1627
|
+
"search",
|
|
1628
|
+
prompt,
|
|
1629
|
+
"--json",
|
|
1630
|
+
"-n",
|
|
1631
|
+
String(maxResults),
|
|
1632
|
+
"-v",
|
|
1633
|
+
vaultPath
|
|
1634
|
+
];
|
|
1635
|
+
const fallbackSearchResult = runClawvault(fallbackSearchArgs, options.pluginConfig, { timeoutMs: 25e3 });
|
|
1636
|
+
if (!fallbackSearchResult.success) {
|
|
1513
1637
|
return { entries: [], vaultPath };
|
|
1514
1638
|
}
|
|
1515
1639
|
return {
|
|
1516
|
-
entries: parseContextJson(
|
|
1640
|
+
entries: parseContextJson(fallbackSearchResult.output, maxResults),
|
|
1517
1641
|
vaultPath
|
|
1518
1642
|
};
|
|
1519
1643
|
}
|
|
@@ -2007,8 +2131,10 @@ async function registerOpenClawPlugin(api) {
|
|
|
2007
2131
|
warn: api.logger.warn
|
|
2008
2132
|
}
|
|
2009
2133
|
});
|
|
2010
|
-
|
|
2011
|
-
|
|
2134
|
+
const memorySearchTool = createMemorySearchToolFactory(memoryManager)();
|
|
2135
|
+
const memoryGetTool = createMemoryGetToolFactory(memoryManager)();
|
|
2136
|
+
api.registerTool(memorySearchTool, { name: "memory_search" });
|
|
2137
|
+
api.registerTool(memoryGetTool, { name: "memory_get" });
|
|
2012
2138
|
api.on("before_prompt_build", createBeforePromptBuildHandler({
|
|
2013
2139
|
pluginConfig,
|
|
2014
2140
|
runtimeState
|
package/dist/index.js
CHANGED
package/dist/openclaw-plugin.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawvault",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "Structured memory system for AI agents — typed storage, knowledge graph, context profiles, canvas dashboards, neural graph themes, and Obsidian-native task views. An elephant never forgets. 🐘",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|