clawvault 3.4.1 → 3.5.0
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/CHANGELOG.md +543 -0
- package/LICENSE +21 -0
- package/SKILL.md +369 -0
- package/dist/{chunk-X3SPPUFG.js → chunk-JI7VUQV7.js} +118 -132
- package/dist/{chunk-PLNK37JD.js → chunk-QUFQBAHP.js} +114 -217
- package/dist/cli/index.js +1 -1
- package/dist/commands/compat.js +1 -1
- package/dist/commands/observe.js +1 -1
- package/dist/commands/status.js +4 -4
- package/dist/index.js +11 -8
- package/dist/openclaw-plugin.js +6 -1
- package/docs/clawhub-security-release-playbook.md +75 -0
- package/docs/getting-started/installation.md +99 -0
- package/docs/openclaw-plugin-usage.md +152 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +26 -8
- package/bin/command-registration.test.js +0 -179
- package/bin/command-runtime.test.js +0 -154
- package/bin/help-contract.test.js +0 -55
- package/bin/register-config-route-commands.test.js +0 -121
- package/bin/register-core-commands.test.js +0 -80
- package/bin/register-kanban-commands.test.js +0 -83
- package/bin/register-project-commands.test.js +0 -206
- package/bin/register-query-commands.test.js +0 -80
- package/bin/register-resilience-commands.test.js +0 -81
- package/bin/register-task-commands.test.js +0 -69
- package/bin/register-template-commands.test.js +0 -87
- package/bin/test-helpers/cli-command-fixtures.js +0 -120
- package/dashboard/lib/graph-diff.test.js +0 -75
- package/dashboard/lib/vault-parser.test.js +0 -254
- 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
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildRecallResult
|
|
3
3
|
} from "./chunk-RL2L6I6K.js";
|
|
4
|
+
import {
|
|
5
|
+
recover
|
|
6
|
+
} from "./chunk-OIWVQYQF.js";
|
|
7
|
+
import {
|
|
8
|
+
formatAge
|
|
9
|
+
} from "./chunk-7ZRP733D.js";
|
|
10
|
+
import {
|
|
11
|
+
buildSessionRecap
|
|
12
|
+
} from "./chunk-ZKGY7WTT.js";
|
|
13
|
+
import {
|
|
14
|
+
checkpoint,
|
|
15
|
+
flush
|
|
16
|
+
} from "./chunk-F55HGNU4.js";
|
|
4
17
|
import {
|
|
5
18
|
synthesizeEntityProfiles
|
|
6
19
|
} from "./chunk-NSXYM6EZ.js";
|
|
@@ -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:",
|
|
@@ -1043,22 +921,6 @@ function runClawvault(args, pluginConfig, options = {}) {
|
|
|
1043
921
|
};
|
|
1044
922
|
}
|
|
1045
923
|
}
|
|
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
924
|
function getObserveCursorPath(vaultPath) {
|
|
1063
925
|
return path3.join(vaultPath, ".clawvault", OBSERVE_CURSOR_FILE);
|
|
1064
926
|
}
|
|
@@ -1141,12 +1003,43 @@ function shouldObserveActiveSessions(vaultPath, agentId, pluginConfig) {
|
|
|
1141
1003
|
return false;
|
|
1142
1004
|
}
|
|
1143
1005
|
function runObserverCron(vaultPath, agentId, pluginConfig, options = {}) {
|
|
1006
|
+
if (!isOptInEnabled(pluginConfig, "allowClawvaultExec")) {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1144
1009
|
const args = ["observe", "--cron", "--agent", agentId, "-v", vaultPath];
|
|
1145
1010
|
if (Number.isFinite(options.minNewBytes) && Number(options.minNewBytes) > 0) {
|
|
1146
1011
|
args.push("--min-new", String(Math.floor(Number(options.minNewBytes))));
|
|
1147
1012
|
}
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1013
|
+
const executablePath = resolveExecutablePath(CLAWVAULT_EXECUTABLE, {
|
|
1014
|
+
explicitPath: getConfiguredExecutablePath(pluginConfig)
|
|
1015
|
+
});
|
|
1016
|
+
if (!executablePath) {
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
const expectedSha256 = getConfiguredExecutableSha256(pluginConfig);
|
|
1020
|
+
const integrityResult = verifyExecutableIntegrity(executablePath, expectedSha256);
|
|
1021
|
+
if (!integrityResult.ok) {
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
let sanitizedArgs;
|
|
1025
|
+
try {
|
|
1026
|
+
sanitizedArgs = sanitizeExecArgs(args);
|
|
1027
|
+
} catch {
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
try {
|
|
1031
|
+
const child = spawn(executablePath, sanitizedArgs, {
|
|
1032
|
+
detached: process.platform !== "win32",
|
|
1033
|
+
stdio: "ignore",
|
|
1034
|
+
shell: false
|
|
1035
|
+
});
|
|
1036
|
+
child.on("error", () => {
|
|
1037
|
+
});
|
|
1038
|
+
child.unref();
|
|
1039
|
+
return true;
|
|
1040
|
+
} catch {
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1150
1043
|
}
|
|
1151
1044
|
function resolveSessionKey(input) {
|
|
1152
1045
|
return sanitizeSessionKey(input);
|
|
@@ -1575,20 +1468,51 @@ function rewriteQuestionWithMemoryEvidence(originalContent, memoryHits) {
|
|
|
1575
1468
|
}
|
|
1576
1469
|
|
|
1577
1470
|
// src/plugin/vault-context-injector.ts
|
|
1471
|
+
import * as path5 from "path";
|
|
1578
1472
|
var DEFAULT_MAX_CONTEXT_RESULTS = 4;
|
|
1579
1473
|
var DEFAULT_MAX_RECAP_RESULTS = 6;
|
|
1474
|
+
var DEFAULT_MIN_CONTEXT_SCORE = 0.2;
|
|
1475
|
+
function normalizeRelativePath(vaultPath, absolutePath) {
|
|
1476
|
+
const relativePath = path5.relative(vaultPath, absolutePath).replace(/\\/g, "/");
|
|
1477
|
+
if (!relativePath || relativePath.startsWith("..")) {
|
|
1478
|
+
return path5.basename(absolutePath);
|
|
1479
|
+
}
|
|
1480
|
+
return relativePath;
|
|
1481
|
+
}
|
|
1482
|
+
function formatContextAge(modified) {
|
|
1483
|
+
const ageMs = Date.now() - modified.getTime();
|
|
1484
|
+
return `${formatAge(ageMs)} ago`;
|
|
1485
|
+
}
|
|
1486
|
+
function toContextEntry(vaultPath, result) {
|
|
1487
|
+
const relativePath = normalizeRelativePath(vaultPath, result.document.path);
|
|
1488
|
+
return {
|
|
1489
|
+
title: sanitizeForDisplay(result.document.title || "Untitled"),
|
|
1490
|
+
path: sanitizeForDisplay(relativePath),
|
|
1491
|
+
age: sanitizeForDisplay(formatContextAge(result.document.modified)),
|
|
1492
|
+
snippet: sanitizeForDisplay(result.snippet).replace(/\s+/g, " ").trim().slice(0, 220),
|
|
1493
|
+
score: Number.isFinite(Number(result.score)) ? Number(result.score) : 0
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
function truncateRecapText(text) {
|
|
1497
|
+
const cleaned = sanitizeForDisplay(text).replace(/\s+/g, " ").trim();
|
|
1498
|
+
if (cleaned.length <= 220) return cleaned;
|
|
1499
|
+
return `${cleaned.slice(0, 217).trimEnd()}...`;
|
|
1500
|
+
}
|
|
1580
1501
|
async function fetchSessionRecapEntries(options) {
|
|
1581
1502
|
const sessionKey = resolveSessionKey(options.sessionKey);
|
|
1582
1503
|
if (!sessionKey) return [];
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1504
|
+
try {
|
|
1505
|
+
const recap = await buildSessionRecap(sessionKey, {
|
|
1506
|
+
limit: DEFAULT_MAX_RECAP_RESULTS,
|
|
1507
|
+
agentId: options.agentId
|
|
1508
|
+
});
|
|
1509
|
+
return recap.messages.slice(-DEFAULT_MAX_RECAP_RESULTS).map((message) => ({
|
|
1510
|
+
role: message.role === "user" ? "User" : "Assistant",
|
|
1511
|
+
text: truncateRecapText(message.text)
|
|
1512
|
+
})).filter((message) => message.text.length > 0);
|
|
1513
|
+
} catch {
|
|
1589
1514
|
return [];
|
|
1590
1515
|
}
|
|
1591
|
-
return parseSessionRecapJson(recapResult.output, DEFAULT_MAX_RECAP_RESULTS);
|
|
1592
1516
|
}
|
|
1593
1517
|
async function fetchMemoryContextEntries(options) {
|
|
1594
1518
|
const prompt = sanitizePromptForContext(options.prompt);
|
|
@@ -1603,43 +1527,21 @@ async function fetchMemoryContextEntries(options) {
|
|
|
1603
1527
|
return { entries: [], vaultPath: null };
|
|
1604
1528
|
}
|
|
1605
1529
|
const maxResults = Number.isFinite(options.maxResults) ? Math.max(1, Math.min(20, Number(options.maxResults))) : DEFAULT_MAX_CONTEXT_RESULTS;
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
prompt,
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
"--limit",
|
|
1615
|
-
String(maxResults),
|
|
1616
|
-
"-v",
|
|
1617
|
-
vaultPath
|
|
1618
|
-
];
|
|
1619
|
-
const contextResult = runClawvault(contextArgs, options.pluginConfig, { timeoutMs: 25e3 });
|
|
1620
|
-
if (contextResult.success) {
|
|
1530
|
+
try {
|
|
1531
|
+
const vault = new ClawVault(vaultPath);
|
|
1532
|
+
await vault.load();
|
|
1533
|
+
const results = await vault.find(prompt, {
|
|
1534
|
+
limit: maxResults,
|
|
1535
|
+
minScore: DEFAULT_MIN_CONTEXT_SCORE,
|
|
1536
|
+
temporalBoost: true
|
|
1537
|
+
});
|
|
1621
1538
|
return {
|
|
1622
|
-
entries:
|
|
1539
|
+
entries: results.map((result) => toContextEntry(vaultPath, result)).filter((entry) => entry.snippet.length > 0),
|
|
1623
1540
|
vaultPath
|
|
1624
1541
|
};
|
|
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) {
|
|
1542
|
+
} catch {
|
|
1637
1543
|
return { entries: [], vaultPath };
|
|
1638
1544
|
}
|
|
1639
|
-
return {
|
|
1640
|
-
entries: parseContextJson(fallbackSearchResult.output, maxResults),
|
|
1641
|
-
vaultPath
|
|
1642
|
-
};
|
|
1643
1545
|
}
|
|
1644
1546
|
async function buildVaultContextInjection(options) {
|
|
1645
1547
|
const [recapEntries, memoryResult] = await Promise.all([
|
|
@@ -1761,13 +1663,13 @@ function createMessageSendingHandler(dependencies) {
|
|
|
1761
1663
|
|
|
1762
1664
|
// src/plugin/fact-extractor.ts
|
|
1763
1665
|
import * as fs5 from "fs";
|
|
1764
|
-
import * as
|
|
1666
|
+
import * as path6 from "path";
|
|
1765
1667
|
import { createHash as createHash2 } from "crypto";
|
|
1766
1668
|
var FACTS_FILE = "facts.jsonl";
|
|
1767
1669
|
var ENTITY_GRAPH_FILE = "entity-graph.json";
|
|
1768
1670
|
var MAX_TEXT_LENGTH = 6e3;
|
|
1769
1671
|
function ensureClawVaultDir(vaultPath) {
|
|
1770
|
-
const dir =
|
|
1672
|
+
const dir = path6.join(vaultPath, ".clawvault");
|
|
1771
1673
|
if (!fs5.existsSync(dir)) {
|
|
1772
1674
|
fs5.mkdirSync(dir, { recursive: true });
|
|
1773
1675
|
}
|
|
@@ -1887,7 +1789,7 @@ function buildEntityGraph(facts) {
|
|
|
1887
1789
|
};
|
|
1888
1790
|
}
|
|
1889
1791
|
function ensureFactsLogFile(vaultPath) {
|
|
1890
|
-
const filePath =
|
|
1792
|
+
const filePath = path6.join(ensureClawVaultDir(vaultPath), FACTS_FILE);
|
|
1891
1793
|
if (!fs5.existsSync(filePath)) {
|
|
1892
1794
|
fs5.writeFileSync(filePath, "", "utf-8");
|
|
1893
1795
|
}
|
|
@@ -1900,7 +1802,7 @@ function persistFactsAndGraph(vaultPath, extractedFacts) {
|
|
|
1900
1802
|
store.save();
|
|
1901
1803
|
const allFacts = store.getAllFacts();
|
|
1902
1804
|
const graph = buildEntityGraph(allFacts);
|
|
1903
|
-
const graphPath =
|
|
1805
|
+
const graphPath = path6.join(ensureClawVaultDir(vaultPath), ENTITY_GRAPH_FILE);
|
|
1904
1806
|
fs5.writeFileSync(graphPath, `${JSON.stringify(graph, null, 2)}
|
|
1905
1807
|
`, "utf-8");
|
|
1906
1808
|
return {
|
|
@@ -1977,19 +1879,16 @@ async function handleGatewayStart(event, ctx, deps) {
|
|
|
1977
1879
|
deps.logger?.warn("[clawvault] No vault found, skipping startup recovery");
|
|
1978
1880
|
return;
|
|
1979
1881
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
return;
|
|
1985
|
-
}
|
|
1986
|
-
if (!result.success) {
|
|
1882
|
+
let recoveryResult;
|
|
1883
|
+
try {
|
|
1884
|
+
recoveryResult = await recover(vaultPath, { clearFlag: true });
|
|
1885
|
+
} catch {
|
|
1987
1886
|
deps.logger?.warn("[clawvault] Startup recovery command failed");
|
|
1988
1887
|
return;
|
|
1989
1888
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
const message =
|
|
1889
|
+
if (recoveryResult.died) {
|
|
1890
|
+
const lastWorkingOn = typeof recoveryResult.checkpoint?.workingOn === "string" ? recoveryResult.checkpoint.workingOn.trim() : "";
|
|
1891
|
+
const message = lastWorkingOn ? `[ClawVault] Context death detected. Last working on: ${lastWorkingOn}. Run \`clawvault wake\` for full recovery context.` : "[ClawVault] Context death detected. Run `clawvault wake` for full recovery context.";
|
|
1993
1892
|
deps.runtimeState.setStartupRecoveryNotice(message);
|
|
1994
1893
|
deps.logger?.warn("[clawvault] Context death detected at startup");
|
|
1995
1894
|
}
|
|
@@ -2036,16 +1935,14 @@ async function handleBeforeReset(event, ctx, deps) {
|
|
|
2036
1935
|
if (autoCheckpointEnabled) {
|
|
2037
1936
|
const safeSessionKey = sanitizeForCheckpoint(sessionKey, 120);
|
|
2038
1937
|
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) {
|
|
1938
|
+
try {
|
|
1939
|
+
await checkpoint({
|
|
1940
|
+
workingOn: `Session reset via ${safeReason}`,
|
|
1941
|
+
focus: `Pre-reset checkpoint, session: ${safeSessionKey}`,
|
|
1942
|
+
vaultPath
|
|
1943
|
+
});
|
|
1944
|
+
await flush();
|
|
1945
|
+
} catch {
|
|
2049
1946
|
deps.logger?.warn("[clawvault] Auto-checkpoint before reset failed");
|
|
2050
1947
|
}
|
|
2051
1948
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -11,7 +11,6 @@ import "../chunk-D5U3Q4N5.js";
|
|
|
11
11
|
import "../chunk-BLQXXX7Q.js";
|
|
12
12
|
import "../chunk-VXAGOLDP.js";
|
|
13
13
|
import "../chunk-7SWP5FKU.js";
|
|
14
|
-
import "../chunk-HRLWZGMA.js";
|
|
15
14
|
import "../chunk-DVOUSOR3.js";
|
|
16
15
|
import "../chunk-4PY655YM.js";
|
|
17
16
|
import "../chunk-AXSJIFOJ.js";
|
|
@@ -20,6 +19,7 @@ import "../chunk-T7E764W3.js";
|
|
|
20
19
|
import "../chunk-HEHO7SMV.js";
|
|
21
20
|
import "../chunk-2PKBIKDH.js";
|
|
22
21
|
import "../chunk-35JCYSRR.js";
|
|
22
|
+
import "../chunk-HRLWZGMA.js";
|
|
23
23
|
import "../chunk-BSJ6RIT7.js";
|
|
24
24
|
import "../chunk-ECGJYWNA.js";
|
|
25
25
|
import "../chunk-2CDEETQN.js";
|
package/dist/commands/compat.js
CHANGED
package/dist/commands/observe.js
CHANGED
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
} from "../chunk-BLQXXX7Q.js";
|
|
5
5
|
import "../chunk-VXAGOLDP.js";
|
|
6
6
|
import "../chunk-7SWP5FKU.js";
|
|
7
|
-
import "../chunk-HRLWZGMA.js";
|
|
8
7
|
import "../chunk-DVOUSOR3.js";
|
|
9
8
|
import "../chunk-4PY655YM.js";
|
|
10
9
|
import "../chunk-AXSJIFOJ.js";
|
|
10
|
+
import "../chunk-HRLWZGMA.js";
|
|
11
11
|
import "../chunk-BSJ6RIT7.js";
|
|
12
12
|
import "../chunk-2CDEETQN.js";
|
|
13
13
|
import "../chunk-GJO3CFUN.js";
|
package/dist/commands/status.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
formatAge
|
|
3
|
-
} from "../chunk-7ZRP733D.js";
|
|
4
1
|
import {
|
|
5
2
|
scanVaultLinks
|
|
6
3
|
} from "../chunk-7YZWHM36.js";
|
|
7
4
|
import "../chunk-J7ZWCI2C.js";
|
|
5
|
+
import {
|
|
6
|
+
formatAge
|
|
7
|
+
} from "../chunk-7ZRP733D.js";
|
|
8
8
|
import {
|
|
9
9
|
getObserverStaleness
|
|
10
10
|
} from "../chunk-VXAGOLDP.js";
|
|
11
11
|
import "../chunk-7SWP5FKU.js";
|
|
12
|
-
import "../chunk-HRLWZGMA.js";
|
|
13
12
|
import "../chunk-DVOUSOR3.js";
|
|
14
13
|
import "../chunk-4PY655YM.js";
|
|
15
14
|
import "../chunk-AXSJIFOJ.js";
|
|
15
|
+
import "../chunk-HRLWZGMA.js";
|
|
16
16
|
import "../chunk-BSJ6RIT7.js";
|
|
17
17
|
import {
|
|
18
18
|
ClawVault
|
package/dist/index.js
CHANGED
|
@@ -13,11 +13,6 @@ import {
|
|
|
13
13
|
registerReplayCommand,
|
|
14
14
|
replayCommand
|
|
15
15
|
} from "./chunk-HGDDW24U.js";
|
|
16
|
-
import {
|
|
17
|
-
buildSessionRecap,
|
|
18
|
-
formatSessionRecapMarkdown,
|
|
19
|
-
sessionRecapCommand
|
|
20
|
-
} from "./chunk-ZKGY7WTT.js";
|
|
21
16
|
import {
|
|
22
17
|
setupCommand
|
|
23
18
|
} from "./chunk-EL6UBSX5.js";
|
|
@@ -40,7 +35,7 @@ import {
|
|
|
40
35
|
checkOpenClawCompatibility,
|
|
41
36
|
compatCommand,
|
|
42
37
|
compatibilityExitCode
|
|
43
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-JI7VUQV7.js";
|
|
44
39
|
import {
|
|
45
40
|
doctor
|
|
46
41
|
} from "./chunk-CSHO3PJB.js";
|
|
@@ -60,11 +55,19 @@ import {
|
|
|
60
55
|
openclaw_plugin_default,
|
|
61
56
|
plausibilityScore,
|
|
62
57
|
registerMemorySlot
|
|
63
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-QUFQBAHP.js";
|
|
64
59
|
import {
|
|
65
60
|
buildRecallResult,
|
|
66
61
|
classifyRecallQuery
|
|
67
62
|
} from "./chunk-RL2L6I6K.js";
|
|
63
|
+
import "./chunk-OIWVQYQF.js";
|
|
64
|
+
import "./chunk-7ZRP733D.js";
|
|
65
|
+
import {
|
|
66
|
+
buildSessionRecap,
|
|
67
|
+
formatSessionRecapMarkdown,
|
|
68
|
+
sessionRecapCommand
|
|
69
|
+
} from "./chunk-ZKGY7WTT.js";
|
|
70
|
+
import "./chunk-F55HGNU4.js";
|
|
68
71
|
import {
|
|
69
72
|
ensureEntityProfiles,
|
|
70
73
|
readEntityProfile,
|
|
@@ -146,7 +149,6 @@ import {
|
|
|
146
149
|
createLlmFunction,
|
|
147
150
|
resolveFactExtractionMode
|
|
148
151
|
} from "./chunk-7SWP5FKU.js";
|
|
149
|
-
import "./chunk-HRLWZGMA.js";
|
|
150
152
|
import {
|
|
151
153
|
requestLlmCompletion,
|
|
152
154
|
resolveLlmProvider
|
|
@@ -192,6 +194,7 @@ import {
|
|
|
192
194
|
} from "./chunk-HEHO7SMV.js";
|
|
193
195
|
import "./chunk-2PKBIKDH.js";
|
|
194
196
|
import "./chunk-35JCYSRR.js";
|
|
197
|
+
import "./chunk-HRLWZGMA.js";
|
|
195
198
|
import {
|
|
196
199
|
FactStore,
|
|
197
200
|
extractFactsLlm,
|
package/dist/openclaw-plugin.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMemorySlotPlugin,
|
|
3
3
|
openclaw_plugin_default
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QUFQBAHP.js";
|
|
5
5
|
import "./chunk-RL2L6I6K.js";
|
|
6
|
+
import "./chunk-OIWVQYQF.js";
|
|
7
|
+
import "./chunk-7ZRP733D.js";
|
|
8
|
+
import "./chunk-ZKGY7WTT.js";
|
|
9
|
+
import "./chunk-F55HGNU4.js";
|
|
6
10
|
import "./chunk-NSXYM6EZ.js";
|
|
7
11
|
import "./chunk-35JCYSRR.js";
|
|
12
|
+
import "./chunk-HRLWZGMA.js";
|
|
8
13
|
import "./chunk-BSJ6RIT7.js";
|
|
9
14
|
import "./chunk-ECGJYWNA.js";
|
|
10
15
|
import "./chunk-2CDEETQN.js";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ClawHub Security Release Playbook
|
|
2
|
+
|
|
3
|
+
This playbook captures what kept the ClawHub/OpenClaw security review stable for `clawvault` and what repeatedly caused "suspicious" regressions.
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Keep ClawHub scanner classification at least `Benign` by ensuring bundle metadata, SKILL frontmatter, and shipped files stay consistent.
|
|
8
|
+
|
|
9
|
+
## Known-good frontmatter contract
|
|
10
|
+
|
|
11
|
+
Use compact, parser-safe frontmatter with documented keys only:
|
|
12
|
+
|
|
13
|
+
- `name`, `description`, `author`, `source`, `repository`, `homepage`
|
|
14
|
+
- `user-invocable`
|
|
15
|
+
- `openclaw` (single-line JSON object)
|
|
16
|
+
- `metadata` (single-line JSON object with `openclaw`)
|
|
17
|
+
|
|
18
|
+
For `openclaw` and `metadata.openclaw`, use only documented fields:
|
|
19
|
+
|
|
20
|
+
- `emoji`
|
|
21
|
+
- `requires.bins`
|
|
22
|
+
- `requires.env` (can be `[]` if no required env vars)
|
|
23
|
+
- `install` (installer spec array)
|
|
24
|
+
- `homepage`
|
|
25
|
+
|
|
26
|
+
Avoid non-spec keys inside `openclaw` metadata (for example ad-hoc fields such as `env_optional`), because strict scanners may treat the metadata block as invalid and fall back to "no requirements/install spec".
|
|
27
|
+
|
|
28
|
+
## Bundle composition
|
|
29
|
+
|
|
30
|
+
Always publish a minimal auditable bundle:
|
|
31
|
+
|
|
32
|
+
- `SKILL.md`
|
|
33
|
+
- `openclaw.plugin.json`
|
|
34
|
+
- `dist/openclaw-plugin.js`
|
|
35
|
+
|
|
36
|
+
If plugin manifest/runtime files are missing from the published bundle, scanners flag visibility/supply-chain concerns.
|
|
37
|
+
|
|
38
|
+
## Required pre-publish checks
|
|
39
|
+
|
|
40
|
+
1. Validate SKILL frontmatter is single-line JSON for `openclaw` and `metadata`.
|
|
41
|
+
2. Confirm runtime dependencies are declared in both:
|
|
42
|
+
- frontmatter metadata (`requires.bins`, `install`)
|
|
43
|
+
- human docs in SKILL (`Install (Canonical)`, safe install flow)
|
|
44
|
+
3. Confirm `source` and `homepage` fields are present and accurate.
|
|
45
|
+
4. Confirm plugin manifest/runtime paths referenced in SKILL exist in the bundle.
|
|
46
|
+
|
|
47
|
+
## Publish + verify workflow
|
|
48
|
+
|
|
49
|
+
1. Publish skill patch version to ClawHub.
|
|
50
|
+
2. Wait for propagation (`clawhub inspect` can temporarily return `Skill not found`).
|
|
51
|
+
3. Verify metadata and files:
|
|
52
|
+
- `npx clawhub inspect clawvault --file SKILL.md`
|
|
53
|
+
- `npx clawhub inspect clawvault --files`
|
|
54
|
+
4. Verify page classification in browser snapshot (not just CLI):
|
|
55
|
+
- Open `https://clawhub.ai/G9Pedro/clawvault`
|
|
56
|
+
- Confirm status badge is `Benign` (or better) and review details.
|
|
57
|
+
|
|
58
|
+
## If scanner regresses
|
|
59
|
+
|
|
60
|
+
If warning text mentions mismatch between registry metadata and SKILL/docs:
|
|
61
|
+
|
|
62
|
+
1. Compare scanner claim to frontmatter values first.
|
|
63
|
+
2. Remove unsupported keys from metadata block.
|
|
64
|
+
3. Re-publish patch version with normalized metadata.
|
|
65
|
+
4. Re-check in browser after propagation.
|
|
66
|
+
|
|
67
|
+
## Security posture notes
|
|
68
|
+
|
|
69
|
+
Even with clean metadata, this skill can still receive cautionary language because it:
|
|
70
|
+
|
|
71
|
+
- runs plugin lifecycle handlers,
|
|
72
|
+
- reads/modifies OpenClaw session files,
|
|
73
|
+
- and relies on external CLI packages (`clawvault`, `qmd`).
|
|
74
|
+
|
|
75
|
+
That caution is expected and should be addressed with transparent docs, explicit safe-install guidance, and auditable shipped plugin code.
|