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.
Files changed (37) hide show
  1. package/CHANGELOG.md +543 -0
  2. package/LICENSE +21 -0
  3. package/README.md +26 -26
  4. package/SKILL.md +369 -0
  5. package/dist/{chunk-X3SPPUFG.js → chunk-JI7VUQV7.js} +118 -132
  6. package/dist/{chunk-QYQAGBTM.js → chunk-QUFQBAHP.js} +148 -125
  7. package/dist/cli/index.js +1 -1
  8. package/dist/commands/compat.js +1 -1
  9. package/dist/commands/observe.js +1 -1
  10. package/dist/commands/status.js +4 -4
  11. package/dist/index.js +11 -8
  12. package/dist/openclaw-plugin.js +6 -1
  13. package/docs/clawhub-security-release-playbook.md +75 -0
  14. package/docs/getting-started/installation.md +99 -0
  15. package/docs/openclaw-plugin-usage.md +152 -0
  16. package/openclaw.plugin.json +1 -1
  17. package/package.json +26 -8
  18. package/bin/command-registration.test.js +0 -179
  19. package/bin/command-runtime.test.js +0 -154
  20. package/bin/help-contract.test.js +0 -55
  21. package/bin/register-config-route-commands.test.js +0 -121
  22. package/bin/register-core-commands.test.js +0 -80
  23. package/bin/register-kanban-commands.test.js +0 -83
  24. package/bin/register-project-commands.test.js +0 -206
  25. package/bin/register-query-commands.test.js +0 -80
  26. package/bin/register-resilience-commands.test.js +0 -81
  27. package/bin/register-task-commands.test.js +0 -69
  28. package/bin/register-template-commands.test.js +0 -87
  29. package/bin/test-helpers/cli-command-fixtures.js +0 -120
  30. package/dashboard/lib/graph-diff.test.js +0 -75
  31. package/dashboard/lib/vault-parser.test.js +0 -254
  32. package/hooks/clawvault/HOOK.md +0 -130
  33. package/hooks/clawvault/handler.js +0 -1696
  34. package/hooks/clawvault/handler.test.js +0 -576
  35. package/hooks/clawvault/integrity.js +0 -112
  36. package/hooks/clawvault/integrity.test.js +0 -32
  37. 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 result = runClawvault(args, pluginConfig, { timeoutMs: 12e4 });
1059
- return !result.skipped && result.success;
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 (mapped !== "MEMORY.md" && !mapped.startsWith("memory/")) {
1136
- throw new Error("memory_get only allows MEMORY.md or memory/* paths");
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 (input) => {
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: "Relative path from memory_search result (e.g. memory/2026-01-01.md)."
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
- }, ["relPath"]);
1334
- const execute = async (input) => {
1335
- const relPath = typeof input.relPath === "string" ? input.relPath : "";
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
- const recapArgs = ["session-recap", sessionKey, "--format", "json"];
1476
- if (options.agentId) {
1477
- recapArgs.push("--agent", options.agentId);
1478
- }
1479
- const recapResult = runClawvault(recapArgs, options.pluginConfig, { timeoutMs: 2e4 });
1480
- if (!recapResult.success) {
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
- const profile = options.contextProfile ?? options.pluginConfig.contextProfile ?? "auto";
1499
- const contextArgs = [
1500
- "context",
1501
- prompt,
1502
- "--format",
1503
- "json",
1504
- "--profile",
1505
- profile,
1506
- "--limit",
1507
- String(maxResults),
1508
- "-v",
1509
- vaultPath
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 path5 from "path";
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 = path5.join(vaultPath, ".clawvault");
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 = path5.join(ensureClawVaultDir(vaultPath), FACTS_FILE);
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 = path5.join(ensureClawVaultDir(vaultPath), ENTITY_GRAPH_FILE);
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
- const result = runClawvault(["recover", "--clear", "-v", vaultPath], deps.pluginConfig, {
1857
- timeoutMs: 2e4
1858
- });
1859
- if (result.skipped) {
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
- const parsed = parseRecoveryOutput(result.output);
1867
- if (parsed.hadDeath) {
1868
- const message = parsed.workingOn ? `[ClawVault] Context death detected. Last working on: ${parsed.workingOn}. Run \`clawvault wake\` for full recovery context.` : "[ClawVault] Context death detected. Run `clawvault wake` for full recovery context.";
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
- const checkpointResult = runClawvault([
1916
- "checkpoint",
1917
- "--working-on",
1918
- `Session reset via ${safeReason}`,
1919
- "--focus",
1920
- `Pre-reset checkpoint, session: ${safeSessionKey}`,
1921
- "-v",
1922
- vaultPath
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
- api.registerTool(createMemorySearchToolFactory(memoryManager), { name: "memory_search" });
2011
- api.registerTool(createMemoryGetToolFactory(memoryManager), { name: "memory_get" });
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";
@@ -2,7 +2,7 @@ import {
2
2
  checkOpenClawCompatibility,
3
3
  compatCommand,
4
4
  compatibilityExitCode
5
- } from "../chunk-X3SPPUFG.js";
5
+ } from "../chunk-JI7VUQV7.js";
6
6
  import "../chunk-2ZDO52B4.js";
7
7
  export {
8
8
  checkOpenClawCompatibility,
@@ -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";
@@ -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-X3SPPUFG.js";
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-QYQAGBTM.js";
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,
@@ -1,10 +1,15 @@
1
1
  import {
2
2
  createMemorySlotPlugin,
3
3
  openclaw_plugin_default
4
- } from "./chunk-QYQAGBTM.js";
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.