hippo-memory 1.24.1 → 1.25.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 (70) hide show
  1. package/bin/hippo.js +2 -2
  2. package/dist/api.d.ts +12 -17
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +25 -55
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +72 -8
  8. package/dist/cli.js.map +1 -1
  9. package/dist/connectors/github/backfill.js +4 -4
  10. package/dist/connectors/github/cli-impl.js +6 -6
  11. package/dist/connectors/github/dlq.js +14 -14
  12. package/dist/connectors/slack/backfill.js +1 -1
  13. package/dist/connectors/slack/dlq.js +10 -10
  14. package/dist/connectors/slack/workspaces.js +4 -4
  15. package/dist/dag.js +6 -6
  16. package/dist/dashboard.js +7 -7
  17. package/dist/hooks.js +24 -24
  18. package/dist/predictions.js +67 -67
  19. package/dist/recall-scope.d.ts +60 -0
  20. package/dist/recall-scope.d.ts.map +1 -0
  21. package/dist/recall-scope.js +84 -0
  22. package/dist/recall-scope.js.map +1 -0
  23. package/dist/refine-llm.js +13 -13
  24. package/dist/shared.d.ts +30 -0
  25. package/dist/shared.d.ts.map +1 -1
  26. package/dist/shared.js +41 -9
  27. package/dist/shared.js.map +1 -1
  28. package/dist/sleep-redact.d.ts +3 -0
  29. package/dist/sleep-redact.d.ts.map +1 -1
  30. package/dist/sleep-redact.js +3 -0
  31. package/dist/sleep-redact.js.map +1 -1
  32. package/dist/src/api.js +25 -55
  33. package/dist/src/api.js.map +1 -1
  34. package/dist/src/cli.js +72 -8
  35. package/dist/src/cli.js.map +1 -1
  36. package/dist/src/connectors/github/backfill.js +4 -4
  37. package/dist/src/connectors/github/cli-impl.js +6 -6
  38. package/dist/src/connectors/github/dlq.js +14 -14
  39. package/dist/src/connectors/slack/backfill.js +1 -1
  40. package/dist/src/connectors/slack/dlq.js +10 -10
  41. package/dist/src/connectors/slack/workspaces.js +4 -4
  42. package/dist/src/dag.js +6 -6
  43. package/dist/src/dashboard.js +7 -7
  44. package/dist/src/hooks.js +24 -24
  45. package/dist/src/predictions.js +67 -67
  46. package/dist/src/recall-scope.js +84 -0
  47. package/dist/src/recall-scope.js.map +1 -0
  48. package/dist/src/refine-llm.js +13 -13
  49. package/dist/src/shared.js +41 -9
  50. package/dist/src/shared.js.map +1 -1
  51. package/dist/src/sleep-redact.js +3 -0
  52. package/dist/src/sleep-redact.js.map +1 -1
  53. package/dist/src/store.js +57 -17
  54. package/dist/src/store.js.map +1 -1
  55. package/dist/src/version.js +1 -1
  56. package/dist/src/working-memory.js +19 -19
  57. package/dist/store.d.ts +30 -9
  58. package/dist/store.d.ts.map +1 -1
  59. package/dist/store.js +57 -17
  60. package/dist/store.js.map +1 -1
  61. package/dist/version.d.ts +1 -1
  62. package/dist/version.js +1 -1
  63. package/dist/working-memory.js +19 -19
  64. package/dist-ui/index.html +12 -12
  65. package/extensions/openclaw-plugin/index.ts +650 -650
  66. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  67. package/extensions/openclaw-plugin/package.json +1 -1
  68. package/openclaw.plugin.json +1 -1
  69. package/package.json +1 -1
  70. package/dist/benchmarks/e1.3/scenarios.json +0 -2587
package/dist/cli.js CHANGED
@@ -35,7 +35,7 @@ import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInsta
35
35
  import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, computeSchemaFit, Layer, } from './memory.js';
36
36
  import { resolveProjectIdentity } from './project-identity.js';
37
37
  import { detectSecret } from './secret-detect.js';
38
- import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, RECALL_DEFAULT_DENY_SCOPES, } from './store.js';
38
+ import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, RECALL_DEFAULT_DENY_SCOPES, } from './store.js';
39
39
  import { markRetrieved, hybridSearch, physicsSearch, explainMatch, textOverlap } from './search.js';
40
40
  import { renderTraceContent, parseSteps } from './trace.js';
41
41
  import { deduplicateStore } from './dedupe.js';
@@ -722,15 +722,43 @@ async function cmdRecall(hippoRoot, query, flags) {
722
722
  // A5 stub auth: resolve the active tenant once and thread it through every
723
723
  // recall-time SELECT against `memories`. Cross-tenant rows must never surface.
724
724
  const tenantId = resolveTenantId({});
725
- let localEntries = loadSearchEntries(hippoRoot, query, undefined, tenantId);
726
- let globalEntries = isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query, undefined, tenantId) : [];
725
+ // v1.25.0 (v39 follow-up #1): the direct CLI recall path applies the recall
726
+ // scope rule. SQL half via loadRecallSearchEntries in 'additive' mode
727
+ // default-deny (`unknown:legacy` quarantine) always, and an explicit
728
+ // `--scope X` UNLOCKS envelope scope X on top of the default-admitted set
729
+ // (the CLI flag is historically a tag-boost hint over scope-NULL rows, so
730
+ // api.recall's narrowing exact-match would empty every tag-scoped recall —
731
+ // see passesCliRecallScopeFilter in recall-scope.ts). The regex-only
732
+ // `<source>:private:*` half is the JS post-filter below. Hoisted here
733
+ // (it previously lived with the boost flags): recallExplicitScope is the
734
+ // FILTER input; recallActiveScope (which falls back to detectScope()) stays
735
+ // boost-only — auto-detection must never become a filter input or
736
+ // detected-project recalls would change shape.
737
+ const recallExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
738
+ const requestedScopeForFilter = recallExplicitScope || undefined;
739
+ let localEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive');
740
+ let globalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive') : [];
727
741
  // v1.12.13 / C5 — WYSIATI counters. Track filter activity per the plan v3
728
742
  // Task 3 mapping table. dropped_pre_rank is the SUM of all non-budget
729
743
  // filter drops (pre-rank AND post-rank). Search-engine internal drops
730
744
  // (scored-to-zero rows that hybridSearch/physicsSearch returns fewer of)
731
745
  // are NOT counted in v1 — they are part of the rank step, not a filter.
746
+ // totalCandidates = post-SQL-predicate count (api.recall parity: measured
747
+ // after loadRecallSearchEntries, before the JS scope filter). NOTE the
748
+ // v1.12.13 accounting convention: SQL-excluded rows (quarantine + the
749
+ // v1.25.0 pre-window ':private:' exclusion) are pre-candidate and are NOT
750
+ // counted as drops; the JS half below normally drops 0 and exists as
751
+ // defense-in-depth (LIKE/regex divergence, exact-mode mismatch).
732
752
  const totalCandidatesCountCmd = localEntries.length + globalEntries.length;
733
753
  let droppedPreRankCountCmd = 0;
754
+ // v1.25.0: JS half of the recall scope rule (private-scope regex deny with
755
+ // explicit-request unlock), via the canonical helper — do not inline a
756
+ // fourth copy of this predicate.
757
+ const passesRecallScope = (e) => api.passesCliRecallScopeFilter(e.scope ?? null, requestedScopeForFilter);
758
+ const beforeScopeFilterCmd = localEntries.length + globalEntries.length;
759
+ localEntries = localEntries.filter(passesRecallScope);
760
+ globalEntries = globalEntries.filter(passesRecallScope);
761
+ droppedPreRankCountCmd += beforeScopeFilterCmd - (localEntries.length + globalEntries.length);
734
762
  // Bi-temporal filtering for physics path (hybridSearch handles it internally)
735
763
  if (asOf) {
736
764
  const filterAsOf = (entries) => {
@@ -782,7 +810,8 @@ async function cmdRecall(hippoRoot, query, flags) {
782
810
  const minResults = flags['min-results'] !== undefined
783
811
  ? parseInt(String(flags['min-results']), 10)
784
812
  : undefined;
785
- const recallExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
813
+ // recallExplicitScope hoisted above the candidate loads (v1.25.0) see the
814
+ // scope-filter block near the top of cmdRecall.
786
815
  const recallActiveScope = recallExplicitScope || detectScope();
787
816
  const useMultihop = flags['multihop'] === true || config.multihop.enabled;
788
817
  // L1 — graph-retrieval stream. `--graph-stream` forces rrf fusion + the graph stream
@@ -859,10 +888,16 @@ async function cmdRecall(hippoRoot, query, flags) {
859
888
  });
860
889
  }
861
890
  else if (hasGlobal) {
862
- // Use searchBothHybrid for merged results with embedding support
891
+ // Use searchBothHybrid for merged results with embedding support.
892
+ // recallScope (v1.25.0): searchBothHybrid re-loads candidates internally,
893
+ // so the scope rule must be plumbed in — the filtered localEntries /
894
+ // globalEntries above are NOT what this path ranks.
863
895
  results = await searchBothHybrid(query, hippoRoot, globalRoot, {
864
896
  budget, mmr: mmrEnabled, mmrLambda, localBump, minResults, scope: recallActiveScope, tenantId,
865
897
  includeSuperseded, asOf,
898
+ recallScope: recallExplicitScope
899
+ ? { requested: recallExplicitScope, additive: true }
900
+ : {},
866
901
  });
867
902
  }
868
903
  else {
@@ -1695,8 +1730,30 @@ async function cmdExplain(hippoRoot, query, flags) {
1695
1730
  const globalRoot = getGlobalRoot();
1696
1731
  // A5: scope explain results to the active tenant.
1697
1732
  const tenantId = resolveTenantId({});
1698
- let explainLocalEntries = loadSearchEntries(hippoRoot, query, undefined, tenantId);
1699
- let explainGlobalEntries = isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query, undefined, tenantId) : [];
1733
+ // v1.25.0 (v39 follow-up #1): cmdExplain shares cmdRecall's leak class —
1734
+ // same unscoped loads, same fix, same semantics (explain explains what
1735
+ // recall would see). Flag hoisted above the loads; the note below keeps the
1736
+ // command honest for an operator debugging a hidden row.
1737
+ const explainExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
1738
+ const explainRequestedScope = explainExplicitScope || undefined;
1739
+ let explainLocalEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, explainRequestedScope, 'additive');
1740
+ let explainGlobalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, explainRequestedScope, 'additive') : [];
1741
+ const passesExplainScope = (e) => api.passesCliRecallScopeFilter(e.scope ?? null, explainRequestedScope);
1742
+ explainLocalEntries = explainLocalEntries.filter(passesExplainScope);
1743
+ explainGlobalEntries = explainGlobalEntries.filter(passesExplainScope);
1744
+ // Honesty note (grill finding #2): the SQL predicate excludes denied rows
1745
+ // BEFORE the candidate window (codex P2 fix), so the pipeline never sees
1746
+ // them. For the diagnostic note only, probe the unscoped window and count
1747
+ // what the scope policy hides — window-capped, so the count is a floor on
1748
+ // large stores, which is fine for a "why is my row missing" hint.
1749
+ const explainUnscopedProbe = [
1750
+ ...loadSearchEntries(hippoRoot, query, undefined, tenantId),
1751
+ ...(isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query, undefined, tenantId) : []),
1752
+ ];
1753
+ const explainScopeDropped = explainUnscopedProbe.filter((e) => !passesExplainScope(e)).length;
1754
+ if (explainScopeDropped > 0) {
1755
+ console.error(`[note] ${explainScopeDropped} candidate${explainScopeDropped === 1 ? '' : 's'} hidden by recall scope policy (pass an explicit --scope to inspect).`);
1756
+ }
1700
1757
  // Bi-temporal filtering
1701
1758
  if (explainAsOf) {
1702
1759
  const filterAsOfExplain = (entries) => {
@@ -1739,7 +1796,7 @@ async function cmdExplain(hippoRoot, query, flags) {
1739
1796
  : flags['local-bump'] !== undefined
1740
1797
  ? parseFloat(String(flags['local-bump']))
1741
1798
  : config.search.localBump;
1742
- const explainExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
1799
+ // explainExplicitScope hoisted above the candidate loads (v1.25.0).
1743
1800
  const explainActiveScope = explainExplicitScope || detectScope();
1744
1801
  let results;
1745
1802
  let modeUsed;
@@ -1757,6 +1814,9 @@ async function cmdExplain(hippoRoot, query, flags) {
1757
1814
  results = await searchBothHybrid(query, hippoRoot, globalRoot, {
1758
1815
  budget, explain: true, mmr: mmrEnabled, mmrLambda, localBump, scope: explainActiveScope,
1759
1816
  includeSuperseded: explainIncludeSuperseded, asOf: explainAsOf, tenantId,
1817
+ recallScope: explainExplicitScope
1818
+ ? { requested: explainExplicitScope, additive: true }
1819
+ : {},
1760
1820
  });
1761
1821
  modeUsed = 'searchBothHybrid';
1762
1822
  }
@@ -2429,6 +2489,10 @@ export function renderSleepResult(result) {
2429
2489
  if (result.shared !== undefined && result.shared > 0) {
2430
2490
  console.log(`\nAuto-shared ${result.shared} high-value memories to global store.`);
2431
2491
  }
2492
+ if (result.secretSkipped !== undefined && result.secretSkipped > 0) {
2493
+ // v1.25.0 (v39 follow-up #2): the secret veto is no longer silent.
2494
+ console.log(`\nAuto-share: withheld ${result.secretSkipped} secret-flagged ${result.secretSkipped === 1 ? 'memory' : 'memories'} (secret veto).`);
2495
+ }
2432
2496
  if (result.ambient) {
2433
2497
  console.log(`\n${renderAmbientSummary(result.ambient)}`);
2434
2498
  }