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.
- package/README.md +57 -34
- package/dist/api.d.ts +1 -0
- package/dist/api.js +36 -11
- package/dist/autolearn.d.ts +1 -4
- package/dist/autolearn.js +6 -7
- package/dist/capture.d.ts +3 -0
- package/dist/capture.js +51 -56
- package/dist/churn-git.js +2 -2
- package/dist/cli.d.ts +1 -4
- package/dist/cli.js +240 -150
- package/dist/config.js +8 -1
- package/dist/consolidate.d.ts +2 -0
- package/dist/consolidate.js +13 -6
- package/dist/customer-notes.js +3 -2
- package/dist/dag.js +5 -0
- package/dist/dashboard.js +4 -0
- package/dist/decisions.d.ts +1 -1
- package/dist/decisions.js +4 -3
- package/dist/extensions/openclaw-plugin/index.js +1 -0
- package/dist/extract.js +3 -0
- package/dist/graph-recall.js +8 -7
- package/dist/half-life-migration.d.ts +14 -5
- package/dist/half-life-migration.js +89 -21
- package/dist/handoff.d.ts +2 -0
- package/dist/hooks.d.ts +4 -4
- package/dist/hooks.js +10 -4
- package/dist/importers.d.ts +2 -1
- package/dist/importers.js +19 -7
- package/dist/incidents.d.ts +1 -1
- package/dist/incidents.js +4 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +6 -1
- package/dist/mcp/server.js +3 -3
- package/dist/memory.d.ts +6 -13
- package/dist/memory.js +0 -10
- package/dist/policies.js +3 -2
- package/dist/predictions.js +2 -0
- package/dist/processes.js +3 -2
- package/dist/project-briefs.js +3 -2
- package/dist/secret-detect.js +3 -3
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +6 -1
- package/dist/skills.js +3 -2
- package/dist/store.d.ts +5 -5
- package/dist/store.js +25 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-ui/assets/index-BhT8RvO6.js +61 -0
- package/dist-ui/index.html +1 -1
- package/extensions/openclaw-plugin/index.ts +1 -0
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- 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 {
|
|
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
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
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
|
-
//
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
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
|