clawvault 3.4.1 → 3.5.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-DCF4KMFD.js → chunk-DPK6P6BO.js} +3 -3
- package/dist/{chunk-BLQXXX7Q.js → chunk-FNFP7N6A.js} +2 -2
- package/dist/{chunk-QFWERBDP.js → chunk-J6DW6HBX.js} +1 -1
- package/dist/{chunk-VXAGOLDP.js → chunk-LCBHM3D6.js} +1 -1
- package/dist/{chunk-HGDDW24U.js → chunk-NTQD55S3.js} +3 -3
- package/dist/{chunk-PLNK37JD.js → chunk-P35SHNAU.js} +163 -320
- package/dist/cli/index.js +9 -9
- package/dist/commands/inject.js +2 -2
- package/dist/commands/maintain.js +2 -2
- package/dist/commands/observe.js +5 -5
- package/dist/commands/rebuild.js +3 -3
- package/dist/commands/replay.js +4 -4
- package/dist/commands/sleep.js +5 -5
- package/dist/commands/status.js +7 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +25 -22
- package/dist/{openclaw-plugin--gqA2BZw.d.ts → openclaw-plugin-9M9qCZgl.d.ts} +2 -2
- package/dist/openclaw-plugin.d.ts +1 -1
- package/dist/openclaw-plugin.js +11 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -5
- package/hooks/clawvault/HOOK.md +0 -130
- package/hooks/clawvault/handler.js +0 -1696
- package/hooks/clawvault/handler.test.js +0 -576
- package/hooks/clawvault/integrity.js +0 -112
- package/hooks/clawvault/integrity.test.js +0 -32
- package/hooks/clawvault/openclaw.plugin.json +0 -190
- package/dist/{chunk-7SWP5FKU.js → chunk-FSYISBTU.js} +4 -4
- package/dist/{chunk-D5U3Q4N5.js → chunk-IOKLQR4W.js} +4 -4
- package/dist/{chunk-OFOCU2V4.js → chunk-QL34TMGN.js} +3 -3
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildRecallResult
|
|
3
3
|
} from "./chunk-RL2L6I6K.js";
|
|
4
|
+
import {
|
|
5
|
+
recover
|
|
6
|
+
} from "./chunk-OIWVQYQF.js";
|
|
7
|
+
import {
|
|
8
|
+
buildSessionRecap
|
|
9
|
+
} from "./chunk-ZKGY7WTT.js";
|
|
10
|
+
import {
|
|
11
|
+
checkpoint,
|
|
12
|
+
flush
|
|
13
|
+
} from "./chunk-F55HGNU4.js";
|
|
4
14
|
import {
|
|
5
15
|
synthesizeEntityProfiles
|
|
6
16
|
} from "./chunk-NSXYM6EZ.js";
|
|
17
|
+
import {
|
|
18
|
+
runReflection
|
|
19
|
+
} from "./chunk-TWMI3SNN.js";
|
|
7
20
|
import {
|
|
8
21
|
normalizeForDedup,
|
|
9
22
|
similarityScore
|
|
@@ -707,7 +720,7 @@ import * as fs4 from "fs";
|
|
|
707
720
|
import * as path4 from "path";
|
|
708
721
|
|
|
709
722
|
// src/plugin/clawvault-cli.ts
|
|
710
|
-
import { execFileSync } from "child_process";
|
|
723
|
+
import { execFileSync, spawn } from "child_process";
|
|
711
724
|
import * as fs3 from "fs";
|
|
712
725
|
import * as path3 from "path";
|
|
713
726
|
|
|
@@ -806,8 +819,6 @@ function verifyExecutableIntegrity(executablePath, expectedSha256) {
|
|
|
806
819
|
|
|
807
820
|
// src/plugin/clawvault-cli.ts
|
|
808
821
|
var MAX_CONTEXT_PROMPT_LENGTH = 500;
|
|
809
|
-
var MAX_CONTEXT_SNIPPET_LENGTH = 220;
|
|
810
|
-
var MAX_RECAP_SNIPPET_LENGTH = 220;
|
|
811
822
|
var CLAWVAULT_EXECUTABLE = "clawvault";
|
|
812
823
|
var ONE_KIB = 1024;
|
|
813
824
|
var ONE_MIB = ONE_KIB * ONE_KIB;
|
|
@@ -823,139 +834,6 @@ function sanitizePromptForContext(value) {
|
|
|
823
834
|
if (typeof value !== "string") return "";
|
|
824
835
|
return value.replace(/[\x00-\x1f\x7f]/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_CONTEXT_PROMPT_LENGTH);
|
|
825
836
|
}
|
|
826
|
-
function truncateSnippet(snippet) {
|
|
827
|
-
const safe = sanitizeForDisplay(snippet).replace(/\s+/g, " ").trim();
|
|
828
|
-
if (safe.length <= MAX_CONTEXT_SNIPPET_LENGTH) return safe;
|
|
829
|
-
return `${safe.slice(0, MAX_CONTEXT_SNIPPET_LENGTH - 3).trimEnd()}...`;
|
|
830
|
-
}
|
|
831
|
-
function truncateRecapSnippet(snippet) {
|
|
832
|
-
const safe = sanitizeForDisplay(snippet).replace(/\s+/g, " ").trim();
|
|
833
|
-
if (safe.length <= MAX_RECAP_SNIPPET_LENGTH) return safe;
|
|
834
|
-
return `${safe.slice(0, MAX_RECAP_SNIPPET_LENGTH - 3).trimEnd()}...`;
|
|
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
|
-
}
|
|
912
|
-
function parseContextJson(output, maxResults) {
|
|
913
|
-
const parsed = parseTopLevelJson(output);
|
|
914
|
-
if (!parsed) {
|
|
915
|
-
return [];
|
|
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);
|
|
938
|
-
}
|
|
939
|
-
function parseSessionRecapJson(output, maxResults) {
|
|
940
|
-
const parsed = parseTopLevelJson(output);
|
|
941
|
-
if (!parsed) {
|
|
942
|
-
return [];
|
|
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);
|
|
958
|
-
}
|
|
959
837
|
function formatSessionContextInjection(recapEntries, memoryEntries) {
|
|
960
838
|
const lines = [
|
|
961
839
|
"[ClawVault] Session context restored:",
|
|
@@ -983,82 +861,6 @@ function formatSessionContextInjection(recapEntries, memoryEntries) {
|
|
|
983
861
|
function resolveVaultPathForAgent(pluginConfig, options = {}) {
|
|
984
862
|
return findVaultPath(pluginConfig, options);
|
|
985
863
|
}
|
|
986
|
-
function runClawvault(args, pluginConfig, options = {}) {
|
|
987
|
-
if (!isOptInEnabled(pluginConfig, "allowClawvaultExec")) {
|
|
988
|
-
return {
|
|
989
|
-
success: false,
|
|
990
|
-
skipped: true,
|
|
991
|
-
output: "ClawVault CLI execution is disabled. Set allowClawvaultExec=true to enable.",
|
|
992
|
-
code: 0
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
const timeoutMs = Number.isFinite(options.timeoutMs) ? Math.max(1e3, Number(options.timeoutMs)) : 15e3;
|
|
996
|
-
const executablePath = resolveExecutablePath(CLAWVAULT_EXECUTABLE, {
|
|
997
|
-
explicitPath: getConfiguredExecutablePath(pluginConfig)
|
|
998
|
-
});
|
|
999
|
-
if (!executablePath) {
|
|
1000
|
-
return {
|
|
1001
|
-
success: false,
|
|
1002
|
-
output: "Unable to resolve clawvault executable path.",
|
|
1003
|
-
code: 1
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
const expectedSha256 = getConfiguredExecutableSha256(pluginConfig);
|
|
1007
|
-
const integrityResult = verifyExecutableIntegrity(executablePath, expectedSha256);
|
|
1008
|
-
if (!integrityResult.ok) {
|
|
1009
|
-
return {
|
|
1010
|
-
success: false,
|
|
1011
|
-
output: `Executable integrity verification failed for ${executablePath}.`,
|
|
1012
|
-
code: 1
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
let sanitizedArgs;
|
|
1016
|
-
try {
|
|
1017
|
-
sanitizedArgs = sanitizeExecArgs(args);
|
|
1018
|
-
} catch (error) {
|
|
1019
|
-
return {
|
|
1020
|
-
success: false,
|
|
1021
|
-
output: error instanceof Error ? error.message : "Invalid command arguments",
|
|
1022
|
-
code: 1
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
try {
|
|
1026
|
-
const output = execFileSync(executablePath, sanitizedArgs, {
|
|
1027
|
-
encoding: "utf-8",
|
|
1028
|
-
timeout: timeoutMs,
|
|
1029
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1030
|
-
shell: false
|
|
1031
|
-
});
|
|
1032
|
-
return {
|
|
1033
|
-
success: true,
|
|
1034
|
-
output: output.trim(),
|
|
1035
|
-
code: 0
|
|
1036
|
-
};
|
|
1037
|
-
} catch (error) {
|
|
1038
|
-
const details = error;
|
|
1039
|
-
return {
|
|
1040
|
-
success: false,
|
|
1041
|
-
output: details.stderr?.toString() || details.message || "unknown error",
|
|
1042
|
-
code: details.status || 1
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
function parseRecoveryOutput(output) {
|
|
1047
|
-
if (!output || typeof output !== "string") {
|
|
1048
|
-
return { hadDeath: false, workingOn: null };
|
|
1049
|
-
}
|
|
1050
|
-
const hadDeath = output.includes("Context death detected") || output.includes("died") || output.includes("\u26A0\uFE0F");
|
|
1051
|
-
if (!hadDeath) {
|
|
1052
|
-
return { hadDeath: false, workingOn: null };
|
|
1053
|
-
}
|
|
1054
|
-
const workingOnLine = output.split("\n").find((line) => line.toLowerCase().includes("working on"));
|
|
1055
|
-
if (!workingOnLine) {
|
|
1056
|
-
return { hadDeath: true, workingOn: null };
|
|
1057
|
-
}
|
|
1058
|
-
const parts = workingOnLine.split(":");
|
|
1059
|
-
const workingOn = parts.length > 1 ? sanitizeForDisplay(parts.slice(1).join(":").trim()) : null;
|
|
1060
|
-
return { hadDeath: true, workingOn: workingOn || null };
|
|
1061
|
-
}
|
|
1062
864
|
function getObserveCursorPath(vaultPath) {
|
|
1063
865
|
return path3.join(vaultPath, ".clawvault", OBSERVE_CURSOR_FILE);
|
|
1064
866
|
}
|
|
@@ -1141,12 +943,40 @@ function shouldObserveActiveSessions(vaultPath, agentId, pluginConfig) {
|
|
|
1141
943
|
return false;
|
|
1142
944
|
}
|
|
1143
945
|
function runObserverCron(vaultPath, agentId, pluginConfig, options = {}) {
|
|
946
|
+
if (!isOptInEnabled(pluginConfig, "allowClawvaultExec")) {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
const executablePath = resolveExecutablePath(CLAWVAULT_EXECUTABLE, {
|
|
950
|
+
explicitPath: getConfiguredExecutablePath(pluginConfig)
|
|
951
|
+
});
|
|
952
|
+
if (!executablePath) {
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
const expectedSha256 = getConfiguredExecutableSha256(pluginConfig);
|
|
956
|
+
const integrityResult = verifyExecutableIntegrity(executablePath, expectedSha256);
|
|
957
|
+
if (!integrityResult.ok) {
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
1144
960
|
const args = ["observe", "--cron", "--agent", agentId, "-v", vaultPath];
|
|
1145
961
|
if (Number.isFinite(options.minNewBytes) && Number(options.minNewBytes) > 0) {
|
|
1146
962
|
args.push("--min-new", String(Math.floor(Number(options.minNewBytes))));
|
|
1147
963
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
964
|
+
let sanitizedArgs;
|
|
965
|
+
try {
|
|
966
|
+
sanitizedArgs = sanitizeExecArgs(args);
|
|
967
|
+
} catch {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
try {
|
|
971
|
+
const child = spawn(executablePath, sanitizedArgs, {
|
|
972
|
+
stdio: "ignore",
|
|
973
|
+
shell: false
|
|
974
|
+
});
|
|
975
|
+
child.unref();
|
|
976
|
+
return true;
|
|
977
|
+
} catch {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
1150
980
|
}
|
|
1151
981
|
function resolveSessionKey(input) {
|
|
1152
982
|
return sanitizeSessionKey(input);
|
|
@@ -1222,8 +1052,8 @@ function toSafeFilePath(vaultPath, relPath) {
|
|
|
1222
1052
|
if (!mapped || mapped.includes("..")) {
|
|
1223
1053
|
throw new Error("Invalid memory path");
|
|
1224
1054
|
}
|
|
1225
|
-
if (!mapped.
|
|
1226
|
-
throw new Error("memory_get only allows
|
|
1055
|
+
if (mapped !== "MEMORY.md" && !mapped.startsWith("memory/")) {
|
|
1056
|
+
throw new Error("memory_get only allows MEMORY.md or memory/* paths");
|
|
1227
1057
|
}
|
|
1228
1058
|
const absolute = path4.resolve(vaultPath, mapped);
|
|
1229
1059
|
const vaultRootWithSep = vaultPath.endsWith(path4.sep) ? vaultPath : `${vaultPath}${path4.sep}`;
|
|
@@ -1350,17 +1180,8 @@ function buildToolSchema(properties, required = []) {
|
|
|
1350
1180
|
additionalProperties: false
|
|
1351
1181
|
};
|
|
1352
1182
|
}
|
|
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
|
-
}
|
|
1362
1183
|
function createMemorySearchToolFactory(memoryManager) {
|
|
1363
|
-
return (
|
|
1184
|
+
return () => {
|
|
1364
1185
|
const inputSchema = buildToolSchema({
|
|
1365
1186
|
query: {
|
|
1366
1187
|
type: "string",
|
|
@@ -1383,8 +1204,7 @@ function createMemorySearchToolFactory(memoryManager) {
|
|
|
1383
1204
|
description: "Optional OpenClaw session key for scoped recall."
|
|
1384
1205
|
}
|
|
1385
1206
|
}, ["query"]);
|
|
1386
|
-
const execute = async (
|
|
1387
|
-
const input = resolveToolInput(toolCallIdOrInput, maybeInput);
|
|
1207
|
+
const execute = async (input) => {
|
|
1388
1208
|
const query = typeof input.query === "string" ? input.query : "";
|
|
1389
1209
|
if (!query.trim()) {
|
|
1390
1210
|
return { query, count: 0, results: [] };
|
|
@@ -1401,7 +1221,6 @@ function createMemorySearchToolFactory(memoryManager) {
|
|
|
1401
1221
|
};
|
|
1402
1222
|
};
|
|
1403
1223
|
return {
|
|
1404
|
-
label: "Memory Search",
|
|
1405
1224
|
name: "memory_search",
|
|
1406
1225
|
description: "Search ClawVault memory for relevant snippets before answering.",
|
|
1407
1226
|
inputSchema,
|
|
@@ -1414,15 +1233,11 @@ function createMemorySearchToolFactory(memoryManager) {
|
|
|
1414
1233
|
};
|
|
1415
1234
|
}
|
|
1416
1235
|
function createMemoryGetToolFactory(memoryManager) {
|
|
1417
|
-
return (
|
|
1236
|
+
return () => {
|
|
1418
1237
|
const inputSchema = buildToolSchema({
|
|
1419
|
-
path: {
|
|
1420
|
-
type: "string",
|
|
1421
|
-
description: "Relative path from memory_search result (for OpenClaw compatibility)."
|
|
1422
|
-
},
|
|
1423
1238
|
relPath: {
|
|
1424
1239
|
type: "string",
|
|
1425
|
-
description: "
|
|
1240
|
+
description: "Relative path from memory_search result (e.g. memory/2026-01-01.md)."
|
|
1426
1241
|
},
|
|
1427
1242
|
from: {
|
|
1428
1243
|
type: "number",
|
|
@@ -1435,11 +1250,9 @@ function createMemoryGetToolFactory(memoryManager) {
|
|
|
1435
1250
|
maximum: 400,
|
|
1436
1251
|
description: "Optional number of lines to read."
|
|
1437
1252
|
}
|
|
1438
|
-
});
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
const input = resolveToolInput(toolCallIdOrInput, maybeInput);
|
|
1442
|
-
const relPath = typeof input.path === "string" ? input.path : typeof input.relPath === "string" ? input.relPath : "";
|
|
1253
|
+
}, ["relPath"]);
|
|
1254
|
+
const execute = async (input) => {
|
|
1255
|
+
const relPath = typeof input.relPath === "string" ? input.relPath : "";
|
|
1443
1256
|
if (!relPath.trim()) {
|
|
1444
1257
|
return { path: relPath, text: "" };
|
|
1445
1258
|
}
|
|
@@ -1450,7 +1263,6 @@ function createMemoryGetToolFactory(memoryManager) {
|
|
|
1450
1263
|
});
|
|
1451
1264
|
};
|
|
1452
1265
|
return {
|
|
1453
|
-
label: "Memory Get",
|
|
1454
1266
|
name: "memory_get",
|
|
1455
1267
|
description: "Read a specific memory file or line range from ClawVault.",
|
|
1456
1268
|
inputSchema,
|
|
@@ -1575,20 +1387,82 @@ function rewriteQuestionWithMemoryEvidence(originalContent, memoryHits) {
|
|
|
1575
1387
|
}
|
|
1576
1388
|
|
|
1577
1389
|
// src/plugin/vault-context-injector.ts
|
|
1390
|
+
import * as path5 from "path";
|
|
1578
1391
|
var DEFAULT_MAX_CONTEXT_RESULTS = 4;
|
|
1579
1392
|
var DEFAULT_MAX_RECAP_RESULTS = 6;
|
|
1393
|
+
var DEFAULT_MIN_SCORE2 = 0.2;
|
|
1394
|
+
var MAX_CONTEXT_SNIPPET_LENGTH = 220;
|
|
1395
|
+
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1396
|
+
function clamp2(value, min, max) {
|
|
1397
|
+
return Math.max(min, Math.min(max, value));
|
|
1398
|
+
}
|
|
1399
|
+
function truncateContextSnippet(snippet) {
|
|
1400
|
+
const normalized = sanitizeForDisplay(snippet).replace(/\s+/g, " ").trim();
|
|
1401
|
+
if (normalized.length <= MAX_CONTEXT_SNIPPET_LENGTH) {
|
|
1402
|
+
return normalized;
|
|
1403
|
+
}
|
|
1404
|
+
return `${normalized.slice(0, MAX_CONTEXT_SNIPPET_LENGTH - 3).trimEnd()}...`;
|
|
1405
|
+
}
|
|
1406
|
+
function toRelativeVaultPath(vaultPath, absolutePath) {
|
|
1407
|
+
const rel = path5.relative(vaultPath, absolutePath).replace(/\\/g, "/");
|
|
1408
|
+
return rel.startsWith(".") ? path5.basename(absolutePath) : rel;
|
|
1409
|
+
}
|
|
1410
|
+
function formatAgeLabel(modifiedAt) {
|
|
1411
|
+
const modified = modifiedAt.getTime();
|
|
1412
|
+
if (!Number.isFinite(modified)) {
|
|
1413
|
+
return "unknown age";
|
|
1414
|
+
}
|
|
1415
|
+
const elapsedMs = Date.now() - modified;
|
|
1416
|
+
if (elapsedMs < ONE_DAY_MS) {
|
|
1417
|
+
return "today";
|
|
1418
|
+
}
|
|
1419
|
+
const days = Math.max(1, Math.floor(elapsedMs / ONE_DAY_MS));
|
|
1420
|
+
if (days < 7) {
|
|
1421
|
+
return `${days}d ago`;
|
|
1422
|
+
}
|
|
1423
|
+
const weeks = Math.floor(days / 7);
|
|
1424
|
+
if (weeks < 5) {
|
|
1425
|
+
return `${weeks}w ago`;
|
|
1426
|
+
}
|
|
1427
|
+
const months = Math.floor(days / 30);
|
|
1428
|
+
if (months < 12) {
|
|
1429
|
+
return `${months}mo ago`;
|
|
1430
|
+
}
|
|
1431
|
+
const years = Math.floor(days / 365);
|
|
1432
|
+
return `${years}y ago`;
|
|
1433
|
+
}
|
|
1434
|
+
function mapSearchResultToContextEntry(vaultPath, result) {
|
|
1435
|
+
const snippet = truncateContextSnippet(result.snippet);
|
|
1436
|
+
if (!snippet) {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
return {
|
|
1440
|
+
title: sanitizeForDisplay(result.document.title || "Untitled"),
|
|
1441
|
+
path: sanitizeForDisplay(toRelativeVaultPath(vaultPath, result.document.path)),
|
|
1442
|
+
age: formatAgeLabel(result.document.modified),
|
|
1443
|
+
snippet,
|
|
1444
|
+
score: Number.isFinite(result.score) ? result.score : 0
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1580
1447
|
async function fetchSessionRecapEntries(options) {
|
|
1581
1448
|
const sessionKey = resolveSessionKey(options.sessionKey);
|
|
1582
1449
|
if (!sessionKey) return [];
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1450
|
+
try {
|
|
1451
|
+
const recap = await buildSessionRecap(sessionKey, {
|
|
1452
|
+
agentId: options.agentId,
|
|
1453
|
+
limit: DEFAULT_MAX_RECAP_RESULTS
|
|
1454
|
+
});
|
|
1455
|
+
return recap.messages.slice(-DEFAULT_MAX_RECAP_RESULTS).map((entry) => {
|
|
1456
|
+
const text = sanitizeForDisplay(entry.text);
|
|
1457
|
+
if (!text) return null;
|
|
1458
|
+
return {
|
|
1459
|
+
role: entry.role === "user" ? "User" : "Assistant",
|
|
1460
|
+
text
|
|
1461
|
+
};
|
|
1462
|
+
}).filter((entry) => Boolean(entry));
|
|
1463
|
+
} catch {
|
|
1589
1464
|
return [];
|
|
1590
1465
|
}
|
|
1591
|
-
return parseSessionRecapJson(recapResult.output, DEFAULT_MAX_RECAP_RESULTS);
|
|
1592
1466
|
}
|
|
1593
1467
|
async function fetchMemoryContextEntries(options) {
|
|
1594
1468
|
const prompt = sanitizePromptForContext(options.prompt);
|
|
@@ -1602,44 +1476,20 @@ async function fetchMemoryContextEntries(options) {
|
|
|
1602
1476
|
if (!vaultPath) {
|
|
1603
1477
|
return { entries: [], vaultPath: null };
|
|
1604
1478
|
}
|
|
1605
|
-
const maxResults = Number.isFinite(options.maxResults) ?
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
prompt,
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
vaultPath
|
|
1618
|
-
];
|
|
1619
|
-
const contextResult = runClawvault(contextArgs, options.pluginConfig, { timeoutMs: 25e3 });
|
|
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) {
|
|
1479
|
+
const maxResults = Number.isFinite(options.maxResults) ? clamp2(Math.floor(Number(options.maxResults)), 1, 20) : DEFAULT_MAX_CONTEXT_RESULTS;
|
|
1480
|
+
try {
|
|
1481
|
+
const vault = new ClawVault(vaultPath);
|
|
1482
|
+
await vault.load();
|
|
1483
|
+
const matches = await vault.find(prompt, {
|
|
1484
|
+
limit: maxResults,
|
|
1485
|
+
minScore: DEFAULT_MIN_SCORE2,
|
|
1486
|
+
temporalBoost: true
|
|
1487
|
+
});
|
|
1488
|
+
const entries = matches.map((match) => mapSearchResultToContextEntry(vaultPath, match)).filter((entry) => Boolean(entry));
|
|
1489
|
+
return { entries, vaultPath };
|
|
1490
|
+
} catch {
|
|
1637
1491
|
return { entries: [], vaultPath };
|
|
1638
1492
|
}
|
|
1639
|
-
return {
|
|
1640
|
-
entries: parseContextJson(fallbackSearchResult.output, maxResults),
|
|
1641
|
-
vaultPath
|
|
1642
|
-
};
|
|
1643
1493
|
}
|
|
1644
1494
|
async function buildVaultContextInjection(options) {
|
|
1645
1495
|
const [recapEntries, memoryResult] = await Promise.all([
|
|
@@ -1761,13 +1611,13 @@ function createMessageSendingHandler(dependencies) {
|
|
|
1761
1611
|
|
|
1762
1612
|
// src/plugin/fact-extractor.ts
|
|
1763
1613
|
import * as fs5 from "fs";
|
|
1764
|
-
import * as
|
|
1614
|
+
import * as path6 from "path";
|
|
1765
1615
|
import { createHash as createHash2 } from "crypto";
|
|
1766
1616
|
var FACTS_FILE = "facts.jsonl";
|
|
1767
1617
|
var ENTITY_GRAPH_FILE = "entity-graph.json";
|
|
1768
1618
|
var MAX_TEXT_LENGTH = 6e3;
|
|
1769
1619
|
function ensureClawVaultDir(vaultPath) {
|
|
1770
|
-
const dir =
|
|
1620
|
+
const dir = path6.join(vaultPath, ".clawvault");
|
|
1771
1621
|
if (!fs5.existsSync(dir)) {
|
|
1772
1622
|
fs5.mkdirSync(dir, { recursive: true });
|
|
1773
1623
|
}
|
|
@@ -1887,7 +1737,7 @@ function buildEntityGraph(facts) {
|
|
|
1887
1737
|
};
|
|
1888
1738
|
}
|
|
1889
1739
|
function ensureFactsLogFile(vaultPath) {
|
|
1890
|
-
const filePath =
|
|
1740
|
+
const filePath = path6.join(ensureClawVaultDir(vaultPath), FACTS_FILE);
|
|
1891
1741
|
if (!fs5.existsSync(filePath)) {
|
|
1892
1742
|
fs5.writeFileSync(filePath, "", "utf-8");
|
|
1893
1743
|
}
|
|
@@ -1900,7 +1750,7 @@ function persistFactsAndGraph(vaultPath, extractedFacts) {
|
|
|
1900
1750
|
store.save();
|
|
1901
1751
|
const allFacts = store.getAllFacts();
|
|
1902
1752
|
const graph = buildEntityGraph(allFacts);
|
|
1903
|
-
const graphPath =
|
|
1753
|
+
const graphPath = path6.join(ensureClawVaultDir(vaultPath), ENTITY_GRAPH_FILE);
|
|
1904
1754
|
fs5.writeFileSync(graphPath, `${JSON.stringify(graph, null, 2)}
|
|
1905
1755
|
`, "utf-8");
|
|
1906
1756
|
return {
|
|
@@ -1957,13 +1807,13 @@ async function runWeeklyReflectionIfNeeded(deps, agentId, workspaceDir) {
|
|
|
1957
1807
|
if (!vaultPath) {
|
|
1958
1808
|
return;
|
|
1959
1809
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1810
|
+
try {
|
|
1811
|
+
const result = await runReflection({ vaultPath });
|
|
1812
|
+
if (result.writtenWeeks > 0) {
|
|
1813
|
+
deps.logger?.info("[clawvault] Weekly reflection complete");
|
|
1814
|
+
}
|
|
1964
1815
|
deps.runtimeState.markWeeklyReflectionRun(weekKey);
|
|
1965
|
-
|
|
1966
|
-
} else if (!result.skipped) {
|
|
1816
|
+
} catch {
|
|
1967
1817
|
deps.logger?.warn("[clawvault] Weekly reflection failed");
|
|
1968
1818
|
}
|
|
1969
1819
|
}
|
|
@@ -1977,19 +1827,16 @@ async function handleGatewayStart(event, ctx, deps) {
|
|
|
1977
1827
|
deps.logger?.warn("[clawvault] No vault found, skipping startup recovery");
|
|
1978
1828
|
return;
|
|
1979
1829
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
return;
|
|
1985
|
-
}
|
|
1986
|
-
if (!result.success) {
|
|
1830
|
+
let recoveryInfo;
|
|
1831
|
+
try {
|
|
1832
|
+
recoveryInfo = await recover(vaultPath, { clearFlag: true });
|
|
1833
|
+
} catch {
|
|
1987
1834
|
deps.logger?.warn("[clawvault] Startup recovery command failed");
|
|
1988
1835
|
return;
|
|
1989
1836
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
const message =
|
|
1837
|
+
if (recoveryInfo.died) {
|
|
1838
|
+
const workingOn = recoveryInfo.checkpoint?.workingOn?.trim();
|
|
1839
|
+
const message = workingOn ? `[ClawVault] Context death detected. Last working on: ${workingOn}. Run \`clawvault wake\` for full recovery context.` : "[ClawVault] Context death detected. Run `clawvault wake` for full recovery context.";
|
|
1993
1840
|
deps.runtimeState.setStartupRecoveryNotice(message);
|
|
1994
1841
|
deps.logger?.warn("[clawvault] Context death detected at startup");
|
|
1995
1842
|
}
|
|
@@ -2036,16 +1883,14 @@ async function handleBeforeReset(event, ctx, deps) {
|
|
|
2036
1883
|
if (autoCheckpointEnabled) {
|
|
2037
1884
|
const safeSessionKey = sanitizeForCheckpoint(sessionKey, 120);
|
|
2038
1885
|
const safeReason = sanitizeForCheckpoint(event.reason ?? "before_reset", 80);
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
], deps.pluginConfig, { timeoutMs: 3e4 });
|
|
2048
|
-
if (!checkpointResult.success && !checkpointResult.skipped) {
|
|
1886
|
+
try {
|
|
1887
|
+
await checkpoint({
|
|
1888
|
+
workingOn: `Session reset via ${safeReason}`,
|
|
1889
|
+
focus: `Pre-reset checkpoint, session: ${safeSessionKey}`,
|
|
1890
|
+
vaultPath
|
|
1891
|
+
});
|
|
1892
|
+
await flush();
|
|
1893
|
+
} catch {
|
|
2049
1894
|
deps.logger?.warn("[clawvault] Auto-checkpoint before reset failed");
|
|
2050
1895
|
}
|
|
2051
1896
|
}
|
|
@@ -2120,7 +1965,7 @@ function isOpenClawPluginApi(value) {
|
|
|
2120
1965
|
const record = value;
|
|
2121
1966
|
return typeof record.on === "function" && typeof record.registerTool === "function" && typeof record.logger === "object";
|
|
2122
1967
|
}
|
|
2123
|
-
|
|
1968
|
+
function registerOpenClawPlugin(api) {
|
|
2124
1969
|
const pluginConfig = readPluginConfig(api);
|
|
2125
1970
|
const runtimeState = new ClawVaultPluginRuntimeState();
|
|
2126
1971
|
const memoryManager = new ClawVaultMemoryManager({
|
|
@@ -2131,10 +1976,8 @@ async function registerOpenClawPlugin(api) {
|
|
|
2131
1976
|
warn: api.logger.warn
|
|
2132
1977
|
}
|
|
2133
1978
|
});
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
api.registerTool(memorySearchTool, { name: "memory_search" });
|
|
2137
|
-
api.registerTool(memoryGetTool, { name: "memory_get" });
|
|
1979
|
+
api.registerTool(createMemorySearchToolFactory(memoryManager), { name: "memory_search" });
|
|
1980
|
+
api.registerTool(createMemoryGetToolFactory(memoryManager), { name: "memory_get" });
|
|
2138
1981
|
api.on("before_prompt_build", createBeforePromptBuildHandler({
|
|
2139
1982
|
pluginConfig,
|
|
2140
1983
|
runtimeState
|
|
@@ -2196,7 +2039,7 @@ var clawvaultPlugin = {
|
|
|
2196
2039
|
name: "ClawVault",
|
|
2197
2040
|
kind: "memory",
|
|
2198
2041
|
description: "Structured memory system for AI agents with proactive recall and protocol-safe messaging",
|
|
2199
|
-
|
|
2042
|
+
register(apiOrRuntime) {
|
|
2200
2043
|
if (isOpenClawPluginApi(apiOrRuntime)) {
|
|
2201
2044
|
return registerOpenClawPlugin(apiOrRuntime);
|
|
2202
2045
|
}
|