hippo-memory 1.52.6 → 1.52.8

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 (55) hide show
  1. package/README.md +57 -34
  2. package/dist/api.d.ts +1 -0
  3. package/dist/api.js +36 -11
  4. package/dist/autolearn.d.ts +1 -4
  5. package/dist/autolearn.js +6 -7
  6. package/dist/capture.d.ts +3 -0
  7. package/dist/capture.js +51 -56
  8. package/dist/churn-git.js +2 -2
  9. package/dist/cli.d.ts +1 -4
  10. package/dist/cli.js +240 -150
  11. package/dist/config.js +8 -1
  12. package/dist/consolidate.d.ts +2 -0
  13. package/dist/consolidate.js +13 -6
  14. package/dist/customer-notes.js +3 -2
  15. package/dist/dag.js +5 -0
  16. package/dist/dashboard.js +4 -0
  17. package/dist/decisions.d.ts +1 -1
  18. package/dist/decisions.js +4 -3
  19. package/dist/extensions/openclaw-plugin/index.js +1 -0
  20. package/dist/extract.js +3 -0
  21. package/dist/graph-recall.js +8 -7
  22. package/dist/half-life-migration.d.ts +14 -5
  23. package/dist/half-life-migration.js +89 -21
  24. package/dist/handoff.d.ts +2 -0
  25. package/dist/hooks.d.ts +4 -4
  26. package/dist/hooks.js +10 -4
  27. package/dist/importers.d.ts +2 -1
  28. package/dist/importers.js +19 -7
  29. package/dist/incidents.d.ts +1 -1
  30. package/dist/incidents.js +4 -3
  31. package/dist/index.d.ts +4 -1
  32. package/dist/index.js +6 -1
  33. package/dist/mcp/server.js +3 -3
  34. package/dist/memory.d.ts +6 -13
  35. package/dist/memory.js +0 -10
  36. package/dist/policies.js +3 -2
  37. package/dist/predictions.js +2 -0
  38. package/dist/processes.js +3 -2
  39. package/dist/project-briefs.js +3 -2
  40. package/dist/secret-detect.js +3 -3
  41. package/dist/shared.d.ts +1 -1
  42. package/dist/shared.js +6 -1
  43. package/dist/skills.js +3 -2
  44. package/dist/store.d.ts +5 -5
  45. package/dist/store.js +25 -13
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/dist-ui/assets/index-BhT8RvO6.js +61 -0
  49. package/dist-ui/index.html +1 -1
  50. package/extensions/openclaw-plugin/index.ts +1 -0
  51. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  52. package/extensions/openclaw-plugin/package.json +1 -1
  53. package/openclaw.plugin.json +1 -1
  54. package/package.json +1 -1
  55. package/dist-ui/assets/index-BgmA7Hwe.js +0 -61
package/dist/capture.js CHANGED
@@ -18,9 +18,10 @@ import { embedMemory } from './embeddings.js';
18
18
  import { isEmbeddingConfigured } from './embedding-provider.js';
19
19
  import { resolveTenantId } from './tenant.js';
20
20
  import { defaultPreCompactLogPath } from './hooks.js';
21
- import { redactSecrets } from './secret-detect.js';
21
+ import { redactSecretsStrict } from './secret-detect.js';
22
22
  import { RejectedValueError, checkRejectionGuard } from './rejection.js';
23
23
  import { openHippoDb, closeHippoDb } from './db.js';
24
+ import { loadConfig } from './config.js';
24
25
  // Sentence-level patterns
25
26
  //
26
27
  // T1 (DF2): each pattern now carries TWO capture groups — group 1 is the
@@ -790,6 +791,7 @@ function cmdCaptureCore(hippoRoot, options) {
790
791
  let captured = 0;
791
792
  let skipped = 0;
792
793
  let rejected = 0;
794
+ const baseHalfLifeDays = loadConfig(targetRoot).defaultHalfLifeDays;
793
795
  // AT1 P2 fix (dry-run parity, docs/plans/2026-08-15-at1-rejected-value-tombstone.md):
794
796
  // dry-run used to skip the guarded write branch ENTIRELY, so a tombstoned
795
797
  // extraction printed as `[capture]` and counted toward `captured` — the
@@ -823,6 +825,7 @@ function cmdCaptureCore(hippoRoot, options) {
823
825
  source: 'capture',
824
826
  confidence: 'observed',
825
827
  tenantId: useGlobal ? undefined : options.tenantId,
828
+ baseHalfLifeDays,
826
829
  });
827
830
  if (options.dryRun) {
828
831
  if (dryRunDb) {
@@ -1077,6 +1080,46 @@ export function preCompactMessage(report) {
1077
1080
  export function preCompactReportPath(logFile) {
1078
1081
  return path.join(path.dirname(logFile), 'pre-compact-last.json');
1079
1082
  }
1083
+ /** A session's task, summary and next step from its transcript tail, secrets scrubbed and capped, '' where none; null with a logged reason when nothing is derivable. */
1084
+ export function transcriptWorkingState(transcriptPath, log) {
1085
+ let tail;
1086
+ try {
1087
+ // CX7 (codex round 2): a final record bigger than the window leaves an empty tail, as when a huge tool_result
1088
+ // triggered compaction, so grow the window a bounded number of times until one complete line survives.
1089
+ tail = readTranscriptTail(transcriptPath, PRE_COMPACT_TAIL_BYTES);
1090
+ let prevCap = PRE_COMPACT_TAIL_BYTES;
1091
+ for (const grownCap of [PRE_COMPACT_TAIL_BYTES * 4, PRE_COMPACT_TAIL_BYTES * 16]) {
1092
+ if (tail.trim() !== '')
1093
+ break;
1094
+ if (fs.statSync(transcriptPath).size <= prevCap)
1095
+ break; // already read the whole file
1096
+ log(`tail window grown to ${grownCap} bytes (oversized final record)`);
1097
+ tail = readTranscriptTail(transcriptPath, grownCap);
1098
+ prevCap = grownCap;
1099
+ }
1100
+ }
1101
+ catch (err) {
1102
+ log(`skip: could not read transcript tail: ${errorMessage(err)}`);
1103
+ return null;
1104
+ }
1105
+ const rawSummary = summariseTranscript(tail);
1106
+ const rawTask = lastPlainUserMessage(tail);
1107
+ const rawNextStep = lastAssistantTextBlock(tail);
1108
+ if (!rawTask.trim() && !rawSummary.trim() && !rawNextStep.trim()) {
1109
+ log('skip: empty summary');
1110
+ return null;
1111
+ }
1112
+ // X9: these fields skip the capture content gate and reach a prompt, so the strict scrub runs. The caps protect the
1113
+ // re-injection token budget and never split a surrogate pair (X2); `hippo snapshot save` stays uncapped.
1114
+ const task = redactSecretsStrict(rawTask);
1115
+ const summary = redactSecretsStrict(rawSummary);
1116
+ const nextStep = redactSecretsStrict(rawNextStep);
1117
+ return {
1118
+ task: task.trim() ? truncateCodePointSafe(task, PRE_COMPACT_TASK_CAP) : '',
1119
+ summary: summary.trim() ? truncateKeepNewest(summary, PRE_COMPACT_SUMMARY_CAP) : '',
1120
+ next_step: nextStep.trim() ? truncateCodePointSafe(nextStep, PRE_COMPACT_NEXT_STEP_CAP) : '',
1121
+ };
1122
+ }
1080
1123
  /** Runs the PreCompact producer: saves a working-state snapshot into `report`. Never extracts memories; SessionEnd capture owns that. */
1081
1124
  function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
1082
1125
  // X3: the PreCompact hook fires in every Claude Code project, including
@@ -1157,40 +1200,10 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
1157
1200
  appendPreCompactLog(logFile, 'skip: no transcript resolved');
1158
1201
  return;
1159
1202
  }
1160
- let tail;
1161
- try {
1162
- // CX7 (codex round 2): a final JSONL record larger than the window
1163
- // swallows the whole tail — the seek lands inside it and alignment
1164
- // drops everything up to its terminator, which is exactly the shape of
1165
- // a huge tool_result that itself triggered compaction. Grow the window
1166
- // a bounded number of times until at least one complete line survives.
1167
- tail = readTranscriptTail(transcriptPath, PRE_COMPACT_TAIL_BYTES);
1168
- let prevCap = PRE_COMPACT_TAIL_BYTES;
1169
- for (const grownCap of [PRE_COMPACT_TAIL_BYTES * 4, PRE_COMPACT_TAIL_BYTES * 16]) {
1170
- if (tail.trim() !== '')
1171
- break;
1172
- if (fs.statSync(transcriptPath).size <= prevCap)
1173
- break; // already read the whole file
1174
- appendPreCompactLog(logFile, `tail window grown to ${grownCap} bytes (oversized final record)`);
1175
- tail = readTranscriptTail(transcriptPath, grownCap);
1176
- prevCap = grownCap;
1177
- }
1178
- }
1179
- catch (err) {
1180
- appendPreCompactLog(logFile, `skip: could not read transcript tail: ${errorMessage(err)}`);
1181
- return;
1182
- }
1183
- const summaryFull = summariseTranscript(tail);
1184
- // Snapshot fields must never carry raw secrets, same rule X9 applies below to task/next_step.
1185
- const scrubbedSummary = redactSecrets(summaryFull);
1186
- const rawTask = lastPlainUserMessage(tail);
1187
- const rawNextStep = lastAssistantTextBlock(tail);
1188
- // Full skip only when every derived field is empty: never clobber a
1189
- // user-authored active snapshot with junk.
1190
- if (!rawTask.trim() && !summaryFull.trim() && !rawNextStep.trim()) {
1191
- appendPreCompactLog(logFile, 'skip: empty summary');
1203
+ // Nothing derivable skips the write, so a user-authored active snapshot is never clobbered with junk.
1204
+ const derived = transcriptWorkingState(transcriptPath, (message) => appendPreCompactLog(logFile, message));
1205
+ if (!derived)
1192
1206
  return;
1193
- }
1194
1207
  const tenantId = resolveTenantId({});
1195
1208
  // Per-field merge (X1): a tool-heavy tail whose only user turns are
1196
1209
  // tool_result arrays derives an empty task even though the summary is
@@ -1204,15 +1217,6 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
1204
1217
  catch {
1205
1218
  // No existing snapshot to merge against — proceed with derived-only.
1206
1219
  }
1207
- // X9: scrub secret-shaped substrings out of freshly-derived text before it
1208
- // is capped/stored. These fields bypass the normal capture content gate
1209
- // (they're not extracted items), so this producer is the only place that
1210
- // ever sees them before they land in task_snapshots. Carried-over
1211
- // existing field values are NOT re-scrubbed here — they already passed
1212
- // through this same gate (or were set via `hippo snapshot save`, which is
1213
- // deliberately untouched, same as the caps below).
1214
- const scrubbedTask = redactSecrets(rawTask);
1215
- const scrubbedNextStep = redactSecrets(rawNextStep);
1216
1220
  // CX6 (codex round 2): field fallback must never move content across
1217
1221
  // sessions — session A's task carried into a snapshot saved under session
1218
1222
  // B's id would pass compact-resume's session gate wearing the wrong
@@ -1222,19 +1226,10 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
1222
1226
  (existing.session_id === null || sessionId === null || existing.session_id === sessionId)
1223
1227
  ? existing
1224
1228
  : null;
1225
- // Field caps are enforced HERE ONLY — saveActiveTaskSnapshot and the
1226
- // `hippo snapshot save` CLI path stay uncapped (AGENTS.md public-API
1227
- // preservation). Caps protect the re-injection token budget. Code-point
1228
- // safe (X2): never split a surrogate pair at the cut.
1229
- const task = scrubbedTask.trim()
1230
- ? truncateCodePointSafe(scrubbedTask, PRE_COMPACT_TASK_CAP)
1231
- : (fallback?.task ?? '');
1232
- const summary = scrubbedSummary.trim()
1233
- ? truncateKeepNewest(scrubbedSummary, PRE_COMPACT_SUMMARY_CAP)
1234
- : (fallback?.summary ?? '');
1235
- const nextStep = scrubbedNextStep.trim()
1236
- ? truncateCodePointSafe(scrubbedNextStep, PRE_COMPACT_NEXT_STEP_CAP)
1237
- : (fallback?.next_step ?? '');
1229
+ // Carried-over fields are not re-capped, as `hippo snapshot save` stays uncapped; saveActiveTaskSnapshot scrubs every field.
1230
+ const task = derived.task || (fallback?.task ?? '');
1231
+ const summary = derived.summary || (fallback?.summary ?? '');
1232
+ const nextStep = derived.next_step || (fallback?.next_step ?? '');
1238
1233
  // All-empty fields (cross-session tail with nothing derivable) skip the
1239
1234
  // write so a foreign session's junk never displaces the owning snapshot.
1240
1235
  if (!task && !summary && !nextStep) {
package/dist/churn-git.js CHANGED
@@ -10,7 +10,7 @@ export class GitReadError extends Error {
10
10
  }
11
11
  function runGit(args, repoRoot) {
12
12
  try {
13
- return execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8', maxBuffer: GIT_MAX_BUFFER });
13
+ return execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8', maxBuffer: GIT_MAX_BUFFER, windowsHide: true });
14
14
  }
15
15
  catch (err) {
16
16
  throw new GitReadError(`git ${args.join(' ')} failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -19,7 +19,7 @@ function runGit(args, repoRoot) {
19
19
  // git grep exits 1 for "no match" -- a normal empty result, not a failure.
20
20
  function runGitGrepOrEmpty(args, repoRoot) {
21
21
  try {
22
- return execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8', maxBuffer: GIT_MAX_BUFFER });
22
+ return execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8', maxBuffer: GIT_MAX_BUFFER, windowsHide: true });
23
23
  }
24
24
  catch (err) {
25
25
  // SAFETY: execFileSync attaches `status` to the thrown Error on a non-zero child exit.
package/dist/cli.d.ts CHANGED
@@ -43,10 +43,7 @@ export declare function parseArgs(argv: string[]): {
43
43
  args: string[];
44
44
  flags: Record<string, string | boolean | string[]>;
45
45
  };
46
- /**
47
- * Scan for Claude Code MEMORY.md files and import new entries into hippo.
48
- * Looks in ~/.claude/projects/<project>/memory/ for .md files with YAML frontmatter.
49
- */
46
+ /** Import new entries from the Claude Code auto memory of the store's project, the frontmatter .md files in ~/.claude/projects/<project>/memory/. */
50
47
  export declare function learnFromMemoryMd(hippoRoot: string, homeDir?: string): number;
51
48
  /**
52
49
  * Render an api.sleep result as console output, byte-identical to the