clawvault 3.4.0 → 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/README.md +26 -26
- package/SKILL.md +369 -0
- package/dist/{chunk-X3SPPUFG.js → chunk-JI7VUQV7.js} +118 -132
- package/dist/{chunk-QYQAGBTM.js → chunk-QUFQBAHP.js} +148 -125
- 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,49 +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 parseContextJson(output, maxResults) {
|
|
837
|
-
try {
|
|
838
|
-
const parsed = JSON.parse(output);
|
|
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 {
|
|
848
|
-
return [];
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
function parseSessionRecapJson(output, maxResults) {
|
|
852
|
-
try {
|
|
853
|
-
const parsed = JSON.parse(output);
|
|
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 {
|
|
866
|
-
return [];
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
837
|
function formatSessionContextInjection(recapEntries, memoryEntries) {
|
|
870
838
|
const lines = [
|
|
871
839
|
"[ClawVault] Session context restored:",
|
|
@@ -953,22 +921,6 @@ function runClawvault(args, pluginConfig, options = {}) {
|
|
|
953
921
|
};
|
|
954
922
|
}
|
|
955
923
|
}
|
|
956
|
-
function parseRecoveryOutput(output) {
|
|
957
|
-
if (!output || typeof output !== "string") {
|
|
958
|
-
return { hadDeath: false, workingOn: null };
|
|
959
|
-
}
|
|
960
|
-
const hadDeath = output.includes("Context death detected") || output.includes("died") || output.includes("\u26A0\uFE0F");
|
|
961
|
-
if (!hadDeath) {
|
|
962
|
-
return { hadDeath: false, workingOn: null };
|
|
963
|
-
}
|
|
964
|
-
const workingOnLine = output.split("\n").find((line) => line.toLowerCase().includes("working on"));
|
|
965
|
-
if (!workingOnLine) {
|
|
966
|
-
return { hadDeath: true, workingOn: null };
|
|
967
|
-
}
|
|
968
|
-
const parts = workingOnLine.split(":");
|
|
969
|
-
const workingOn = parts.length > 1 ? sanitizeForDisplay(parts.slice(1).join(":").trim()) : null;
|
|
970
|
-
return { hadDeath: true, workingOn: workingOn || null };
|
|
971
|
-
}
|
|
972
924
|
function getObserveCursorPath(vaultPath) {
|
|
973
925
|
return path3.join(vaultPath, ".clawvault", OBSERVE_CURSOR_FILE);
|
|
974
926
|
}
|
|
@@ -1051,12 +1003,43 @@ function shouldObserveActiveSessions(vaultPath, agentId, pluginConfig) {
|
|
|
1051
1003
|
return false;
|
|
1052
1004
|
}
|
|
1053
1005
|
function runObserverCron(vaultPath, agentId, pluginConfig, options = {}) {
|
|
1006
|
+
if (!isOptInEnabled(pluginConfig, "allowClawvaultExec")) {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1054
1009
|
const args = ["observe", "--cron", "--agent", agentId, "-v", vaultPath];
|
|
1055
1010
|
if (Number.isFinite(options.minNewBytes) && Number(options.minNewBytes) > 0) {
|
|
1056
1011
|
args.push("--min-new", String(Math.floor(Number(options.minNewBytes))));
|
|
1057
1012
|
}
|
|
1058
|
-
const
|
|
1059
|
-
|
|
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
|
+
}
|
|
1060
1043
|
}
|
|
1061
1044
|
function resolveSessionKey(input) {
|
|
1062
1045
|
return sanitizeSessionKey(input);
|
|
@@ -1132,8 +1115,8 @@ function toSafeFilePath(vaultPath, relPath) {
|
|
|
1132
1115
|
if (!mapped || mapped.includes("..")) {
|
|
1133
1116
|
throw new Error("Invalid memory path");
|
|
1134
1117
|
}
|
|
1135
|
-
if (
|
|
1136
|
-
throw new Error("memory_get only allows
|
|
1118
|
+
if (!mapped.toLowerCase().endsWith(".md")) {
|
|
1119
|
+
throw new Error("memory_get only allows Markdown note paths inside the vault");
|
|
1137
1120
|
}
|
|
1138
1121
|
const absolute = path4.resolve(vaultPath, mapped);
|
|
1139
1122
|
const vaultRootWithSep = vaultPath.endsWith(path4.sep) ? vaultPath : `${vaultPath}${path4.sep}`;
|
|
@@ -1260,8 +1243,17 @@ function buildToolSchema(properties, required = []) {
|
|
|
1260
1243
|
additionalProperties: false
|
|
1261
1244
|
};
|
|
1262
1245
|
}
|
|
1246
|
+
function resolveToolInput(toolCallIdOrInput, maybeInput) {
|
|
1247
|
+
if (maybeInput && typeof maybeInput === "object" && !Array.isArray(maybeInput)) {
|
|
1248
|
+
return maybeInput;
|
|
1249
|
+
}
|
|
1250
|
+
if (toolCallIdOrInput && typeof toolCallIdOrInput === "object" && !Array.isArray(toolCallIdOrInput)) {
|
|
1251
|
+
return toolCallIdOrInput;
|
|
1252
|
+
}
|
|
1253
|
+
return {};
|
|
1254
|
+
}
|
|
1263
1255
|
function createMemorySearchToolFactory(memoryManager) {
|
|
1264
|
-
return () => {
|
|
1256
|
+
return (_toolContext) => {
|
|
1265
1257
|
const inputSchema = buildToolSchema({
|
|
1266
1258
|
query: {
|
|
1267
1259
|
type: "string",
|
|
@@ -1284,7 +1276,8 @@ function createMemorySearchToolFactory(memoryManager) {
|
|
|
1284
1276
|
description: "Optional OpenClaw session key for scoped recall."
|
|
1285
1277
|
}
|
|
1286
1278
|
}, ["query"]);
|
|
1287
|
-
const execute = async (
|
|
1279
|
+
const execute = async (toolCallIdOrInput, maybeInput) => {
|
|
1280
|
+
const input = resolveToolInput(toolCallIdOrInput, maybeInput);
|
|
1288
1281
|
const query = typeof input.query === "string" ? input.query : "";
|
|
1289
1282
|
if (!query.trim()) {
|
|
1290
1283
|
return { query, count: 0, results: [] };
|
|
@@ -1301,6 +1294,7 @@ function createMemorySearchToolFactory(memoryManager) {
|
|
|
1301
1294
|
};
|
|
1302
1295
|
};
|
|
1303
1296
|
return {
|
|
1297
|
+
label: "Memory Search",
|
|
1304
1298
|
name: "memory_search",
|
|
1305
1299
|
description: "Search ClawVault memory for relevant snippets before answering.",
|
|
1306
1300
|
inputSchema,
|
|
@@ -1313,11 +1307,15 @@ function createMemorySearchToolFactory(memoryManager) {
|
|
|
1313
1307
|
};
|
|
1314
1308
|
}
|
|
1315
1309
|
function createMemoryGetToolFactory(memoryManager) {
|
|
1316
|
-
return () => {
|
|
1310
|
+
return (_toolContext) => {
|
|
1317
1311
|
const inputSchema = buildToolSchema({
|
|
1312
|
+
path: {
|
|
1313
|
+
type: "string",
|
|
1314
|
+
description: "Relative path from memory_search result (for OpenClaw compatibility)."
|
|
1315
|
+
},
|
|
1318
1316
|
relPath: {
|
|
1319
1317
|
type: "string",
|
|
1320
|
-
description: "
|
|
1318
|
+
description: "Alias of path (e.g. memory/2026-01-01.md)."
|
|
1321
1319
|
},
|
|
1322
1320
|
from: {
|
|
1323
1321
|
type: "number",
|
|
@@ -1330,9 +1328,11 @@ function createMemoryGetToolFactory(memoryManager) {
|
|
|
1330
1328
|
maximum: 400,
|
|
1331
1329
|
description: "Optional number of lines to read."
|
|
1332
1330
|
}
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1331
|
+
});
|
|
1332
|
+
inputSchema.anyOf = [{ required: ["path"] }, { required: ["relPath"] }];
|
|
1333
|
+
const execute = async (toolCallIdOrInput, maybeInput) => {
|
|
1334
|
+
const input = resolveToolInput(toolCallIdOrInput, maybeInput);
|
|
1335
|
+
const relPath = typeof input.path === "string" ? input.path : typeof input.relPath === "string" ? input.relPath : "";
|
|
1336
1336
|
if (!relPath.trim()) {
|
|
1337
1337
|
return { path: relPath, text: "" };
|
|
1338
1338
|
}
|
|
@@ -1343,6 +1343,7 @@ function createMemoryGetToolFactory(memoryManager) {
|
|
|
1343
1343
|
});
|
|
1344
1344
|
};
|
|
1345
1345
|
return {
|
|
1346
|
+
label: "Memory Get",
|
|
1346
1347
|
name: "memory_get",
|
|
1347
1348
|
description: "Read a specific memory file or line range from ClawVault.",
|
|
1348
1349
|
inputSchema,
|
|
@@ -1467,20 +1468,51 @@ function rewriteQuestionWithMemoryEvidence(originalContent, memoryHits) {
|
|
|
1467
1468
|
}
|
|
1468
1469
|
|
|
1469
1470
|
// src/plugin/vault-context-injector.ts
|
|
1471
|
+
import * as path5 from "path";
|
|
1470
1472
|
var DEFAULT_MAX_CONTEXT_RESULTS = 4;
|
|
1471
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
|
+
}
|
|
1472
1501
|
async function fetchSessionRecapEntries(options) {
|
|
1473
1502
|
const sessionKey = resolveSessionKey(options.sessionKey);
|
|
1474
1503
|
if (!sessionKey) return [];
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
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 {
|
|
1481
1514
|
return [];
|
|
1482
1515
|
}
|
|
1483
|
-
return parseSessionRecapJson(recapResult.output, DEFAULT_MAX_RECAP_RESULTS);
|
|
1484
1516
|
}
|
|
1485
1517
|
async function fetchMemoryContextEntries(options) {
|
|
1486
1518
|
const prompt = sanitizePromptForContext(options.prompt);
|
|
@@ -1495,27 +1527,21 @@ async function fetchMemoryContextEntries(options) {
|
|
|
1495
1527
|
return { entries: [], vaultPath: null };
|
|
1496
1528
|
}
|
|
1497
1529
|
const maxResults = Number.isFinite(options.maxResults) ? Math.max(1, Math.min(20, Number(options.maxResults))) : DEFAULT_MAX_CONTEXT_RESULTS;
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
prompt,
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
const contextResult = runClawvault(contextArgs, options.pluginConfig, { timeoutMs: 25e3 });
|
|
1512
|
-
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
|
+
});
|
|
1538
|
+
return {
|
|
1539
|
+
entries: results.map((result) => toContextEntry(vaultPath, result)).filter((entry) => entry.snippet.length > 0),
|
|
1540
|
+
vaultPath
|
|
1541
|
+
};
|
|
1542
|
+
} catch {
|
|
1513
1543
|
return { entries: [], vaultPath };
|
|
1514
1544
|
}
|
|
1515
|
-
return {
|
|
1516
|
-
entries: parseContextJson(contextResult.output, maxResults),
|
|
1517
|
-
vaultPath
|
|
1518
|
-
};
|
|
1519
1545
|
}
|
|
1520
1546
|
async function buildVaultContextInjection(options) {
|
|
1521
1547
|
const [recapEntries, memoryResult] = await Promise.all([
|
|
@@ -1637,13 +1663,13 @@ function createMessageSendingHandler(dependencies) {
|
|
|
1637
1663
|
|
|
1638
1664
|
// src/plugin/fact-extractor.ts
|
|
1639
1665
|
import * as fs5 from "fs";
|
|
1640
|
-
import * as
|
|
1666
|
+
import * as path6 from "path";
|
|
1641
1667
|
import { createHash as createHash2 } from "crypto";
|
|
1642
1668
|
var FACTS_FILE = "facts.jsonl";
|
|
1643
1669
|
var ENTITY_GRAPH_FILE = "entity-graph.json";
|
|
1644
1670
|
var MAX_TEXT_LENGTH = 6e3;
|
|
1645
1671
|
function ensureClawVaultDir(vaultPath) {
|
|
1646
|
-
const dir =
|
|
1672
|
+
const dir = path6.join(vaultPath, ".clawvault");
|
|
1647
1673
|
if (!fs5.existsSync(dir)) {
|
|
1648
1674
|
fs5.mkdirSync(dir, { recursive: true });
|
|
1649
1675
|
}
|
|
@@ -1763,7 +1789,7 @@ function buildEntityGraph(facts) {
|
|
|
1763
1789
|
};
|
|
1764
1790
|
}
|
|
1765
1791
|
function ensureFactsLogFile(vaultPath) {
|
|
1766
|
-
const filePath =
|
|
1792
|
+
const filePath = path6.join(ensureClawVaultDir(vaultPath), FACTS_FILE);
|
|
1767
1793
|
if (!fs5.existsSync(filePath)) {
|
|
1768
1794
|
fs5.writeFileSync(filePath, "", "utf-8");
|
|
1769
1795
|
}
|
|
@@ -1776,7 +1802,7 @@ function persistFactsAndGraph(vaultPath, extractedFacts) {
|
|
|
1776
1802
|
store.save();
|
|
1777
1803
|
const allFacts = store.getAllFacts();
|
|
1778
1804
|
const graph = buildEntityGraph(allFacts);
|
|
1779
|
-
const graphPath =
|
|
1805
|
+
const graphPath = path6.join(ensureClawVaultDir(vaultPath), ENTITY_GRAPH_FILE);
|
|
1780
1806
|
fs5.writeFileSync(graphPath, `${JSON.stringify(graph, null, 2)}
|
|
1781
1807
|
`, "utf-8");
|
|
1782
1808
|
return {
|
|
@@ -1853,19 +1879,16 @@ async function handleGatewayStart(event, ctx, deps) {
|
|
|
1853
1879
|
deps.logger?.warn("[clawvault] No vault found, skipping startup recovery");
|
|
1854
1880
|
return;
|
|
1855
1881
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
return;
|
|
1861
|
-
}
|
|
1862
|
-
if (!result.success) {
|
|
1882
|
+
let recoveryResult;
|
|
1883
|
+
try {
|
|
1884
|
+
recoveryResult = await recover(vaultPath, { clearFlag: true });
|
|
1885
|
+
} catch {
|
|
1863
1886
|
deps.logger?.warn("[clawvault] Startup recovery command failed");
|
|
1864
1887
|
return;
|
|
1865
1888
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
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.";
|
|
1869
1892
|
deps.runtimeState.setStartupRecoveryNotice(message);
|
|
1870
1893
|
deps.logger?.warn("[clawvault] Context death detected at startup");
|
|
1871
1894
|
}
|
|
@@ -1912,16 +1935,14 @@ async function handleBeforeReset(event, ctx, deps) {
|
|
|
1912
1935
|
if (autoCheckpointEnabled) {
|
|
1913
1936
|
const safeSessionKey = sanitizeForCheckpoint(sessionKey, 120);
|
|
1914
1937
|
const safeReason = sanitizeForCheckpoint(event.reason ?? "before_reset", 80);
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
], deps.pluginConfig, { timeoutMs: 3e4 });
|
|
1924
|
-
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 {
|
|
1925
1946
|
deps.logger?.warn("[clawvault] Auto-checkpoint before reset failed");
|
|
1926
1947
|
}
|
|
1927
1948
|
}
|
|
@@ -2007,8 +2028,10 @@ async function registerOpenClawPlugin(api) {
|
|
|
2007
2028
|
warn: api.logger.warn
|
|
2008
2029
|
}
|
|
2009
2030
|
});
|
|
2010
|
-
|
|
2011
|
-
|
|
2031
|
+
const memorySearchTool = createMemorySearchToolFactory(memoryManager)();
|
|
2032
|
+
const memoryGetTool = createMemoryGetToolFactory(memoryManager)();
|
|
2033
|
+
api.registerTool(memorySearchTool, { name: "memory_search" });
|
|
2034
|
+
api.registerTool(memoryGetTool, { name: "memory_get" });
|
|
2012
2035
|
api.on("before_prompt_build", createBeforePromptBuildHandler({
|
|
2013
2036
|
pluginConfig,
|
|
2014
2037
|
runtimeState
|
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.
|