hippo-memory 1.52.9 → 1.53.1
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 +47 -13
- package/dist/agent-memories/apply.d.ts +47 -0
- package/dist/agent-memories/apply.js +253 -0
- package/dist/agent-memories/claude-code.d.ts +11 -0
- package/dist/agent-memories/claude-code.js +113 -0
- package/dist/agent-memories/codex.d.ts +3 -0
- package/dist/agent-memories/codex.js +47 -0
- package/dist/agent-memories/copilot.d.ts +3 -0
- package/dist/agent-memories/copilot.js +125 -0
- package/dist/agent-memories/files.d.ts +37 -0
- package/dist/agent-memories/files.js +77 -0
- package/dist/agent-memories/folder-store.d.ts +17 -0
- package/dist/agent-memories/folder-store.js +44 -0
- package/dist/agent-memories/gemini.d.ts +3 -0
- package/dist/agent-memories/gemini.js +103 -0
- package/dist/agent-memories/git.d.ts +8 -0
- package/dist/agent-memories/git.js +11 -0
- package/dist/agent-memories/keys.d.ts +9 -0
- package/dist/agent-memories/keys.js +20 -0
- package/dist/agent-memories/legacy.d.ts +17 -0
- package/dist/agent-memories/legacy.js +45 -0
- package/dist/agent-memories/markdown.d.ts +13 -0
- package/dist/agent-memories/markdown.js +123 -0
- package/dist/agent-memories/openclaw.d.ts +3 -0
- package/dist/agent-memories/openclaw.js +42 -0
- package/dist/agent-memories/plan.d.ts +78 -0
- package/dist/agent-memories/plan.js +123 -0
- package/dist/agent-memories/qwen-code.d.ts +5 -0
- package/dist/agent-memories/qwen-code.js +50 -0
- package/dist/agent-memories/report.d.ts +52 -0
- package/dist/agent-memories/report.js +88 -0
- package/dist/agent-memories/source.d.ts +16 -0
- package/dist/agent-memories/source.js +32 -0
- package/dist/agent-memories/sync.d.ts +33 -0
- package/dist/agent-memories/sync.js +336 -0
- package/dist/agent-memories/tools.d.ts +33 -0
- package/dist/agent-memories/tools.js +19 -0
- package/dist/agent-memories/types.d.ts +42 -0
- package/dist/agent-memories/types.js +2 -0
- package/dist/api.d.ts +2 -2
- package/dist/api.js +28 -29
- package/dist/audit.d.ts +4 -3
- package/dist/audit.js +10 -6
- package/dist/capture.d.ts +11 -22
- package/dist/capture.js +76 -81
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +133 -170
- package/dist/compaction-items.d.ts +18 -0
- package/dist/compaction-items.js +60 -0
- package/dist/compaction-record.d.ts +94 -0
- package/dist/compaction-record.js +573 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +13 -0
- package/dist/consolidate.d.ts +0 -2
- package/dist/consolidate.js +3 -36
- package/dist/db.d.ts +5 -1
- package/dist/db.js +40 -8
- package/dist/dedupe.js +3 -2
- package/dist/doctor.js +34 -2
- package/dist/dormant.d.ts +5 -3
- package/dist/dormant.js +9 -0
- package/dist/gated-write.d.ts +9 -0
- package/dist/gated-write.js +24 -0
- package/dist/hooks.d.ts +4 -2
- package/dist/hooks.js +8 -7
- package/dist/memory.d.ts +19 -2
- package/dist/memory.js +32 -3
- package/dist/shared.js +10 -7
- package/dist/store.d.ts +14 -2
- package/dist/store.js +75 -19
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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/audit.js
CHANGED
|
@@ -109,11 +109,12 @@ function hasNoSpecificity(text) {
|
|
|
109
109
|
return false;
|
|
110
110
|
return words.length < 8 && VAGUE_ONLY.test(text);
|
|
111
111
|
}
|
|
112
|
-
export function auditMemory(entry) {
|
|
112
|
+
export function auditMemory(entry, backsObject = false) {
|
|
113
113
|
const issue = classifyMemory(entry);
|
|
114
|
-
// Error means "auto-remove"; a pinned or
|
|
115
|
-
if (issue?.severity === 'error' && !canAutoDelete(entry)) {
|
|
116
|
-
|
|
114
|
+
// Error means "auto-remove"; a pinned, raw, kept-for-good or object-backing row is never auto-removed, so it can only warn.
|
|
115
|
+
if (issue?.severity === 'error' && (!canAutoDelete(entry) || backsObject)) {
|
|
116
|
+
const why = entry.pinned ? 'pinned' : entry.kind === 'raw' ? 'raw' : backsObject ? 'backs an object' : 'keep rule';
|
|
117
|
+
return { ...issue, severity: 'warning', reason: `${issue.reason} (${why}, kept)` };
|
|
117
118
|
}
|
|
118
119
|
return issue;
|
|
119
120
|
}
|
|
@@ -140,10 +141,11 @@ function classifyMemory(entry) {
|
|
|
140
141
|
}
|
|
141
142
|
return null;
|
|
142
143
|
}
|
|
143
|
-
|
|
144
|
+
/** `backing`: ids of memories that back an object (store.memoriesBackingObjects). */
|
|
145
|
+
export function auditMemories(entries, backing) {
|
|
144
146
|
const issues = [];
|
|
145
147
|
for (const entry of entries) {
|
|
146
|
-
const issue = auditMemory(entry);
|
|
148
|
+
const issue = auditMemory(entry, backing.has(entry.id));
|
|
147
149
|
if (issue)
|
|
148
150
|
issues.push(issue);
|
|
149
151
|
}
|
|
@@ -229,6 +231,8 @@ export const AUDIT_OPS = [
|
|
|
229
231
|
'quarantine', // emitted by recordQuarantine inside remember's write transaction
|
|
230
232
|
'quarantine_approve', // emitted by api.quarantineApprove
|
|
231
233
|
'quarantine_reject', // emitted by api.quarantineReject
|
|
234
|
+
'agent_memory_restore', // emitted by the agent memory sync when a deleted note comes back
|
|
235
|
+
'agent_memory_set_aside', // emitted by the agent memory sync when a note is deleted or refused
|
|
232
236
|
];
|
|
233
237
|
function isBigIntValue(value) {
|
|
234
238
|
return typeof value === 'bigint';
|
package/dist/capture.d.ts
CHANGED
|
@@ -89,11 +89,13 @@ export declare function summariseTranscript(jsonl: string): string;
|
|
|
89
89
|
* Priority, where the first source present is the only one tried:
|
|
90
90
|
* 1. Explicit `transcriptPath` option (from `--transcript <path>`)
|
|
91
91
|
* 2. Stdin JSON payload (Claude Code / OpenCode SessionEnd hook shape)
|
|
92
|
-
* 3. Most recent `.jsonl` under `~/.claude/projects/<any>/`, only
|
|
92
|
+
* 3. Most recent `.jsonl` under `~/.claude/projects/<any>/`, only when the caller passes `mayScan` (only the caller knows it is not a hook) and there is no path and no stdin text, because this scan spans every project on the box
|
|
93
93
|
*
|
|
94
94
|
* Returns null when nothing resolves, a named transcript or payload whose file is missing included. Never throws.
|
|
95
95
|
*/
|
|
96
|
-
export declare function resolveLastSessionTranscript(explicit: string | undefined, stdinText: string | undefined,
|
|
96
|
+
export declare function resolveLastSessionTranscript(explicit: string | undefined, stdinText: string | undefined, opts: {
|
|
97
|
+
mayScan: boolean;
|
|
98
|
+
}): string | null;
|
|
97
99
|
export declare function cmdCapture(hippoRoot: string, options: CaptureOptions): void;
|
|
98
100
|
/** Never read the whole transcript — PreCompact fires exactly when it's largest. */
|
|
99
101
|
export declare const PRE_COMPACT_TAIL_BYTES: number;
|
|
@@ -140,19 +142,6 @@ export declare function readTranscriptTail(transcriptPath: string, capBytes?: nu
|
|
|
140
142
|
* guard, not per-file copies.
|
|
141
143
|
*/
|
|
142
144
|
export declare function sanitizeLogMessage(message: string): string;
|
|
143
|
-
/** What one pre-compact run saved, reported to the user after compaction. */
|
|
144
|
-
export interface PreCompactReport {
|
|
145
|
-
snapshotSaved: boolean;
|
|
146
|
-
/** Claude Code session the run belonged to, when the payload named one. */
|
|
147
|
-
sessionId: string | null;
|
|
148
|
-
}
|
|
149
|
-
/** Line shown after compaction, or null when nothing was saved (Claude Code's own generic message is the fallback). */
|
|
150
|
-
export declare function preCompactMessage(report: Pick<PreCompactReport, 'snapshotSaved'>): string | null;
|
|
151
|
-
/**
|
|
152
|
-
* Where pre-compact leaves its report for `hippo post-compact`: next to the
|
|
153
|
-
* pre-compact log, so both hooks find it from the same `--log-file`.
|
|
154
|
-
*/
|
|
155
|
-
export declare function preCompactReportPath(logFile: string): string;
|
|
156
145
|
/** 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. */
|
|
157
146
|
export declare function transcriptWorkingState(transcriptPath: string, log: (message: string) => void): Pick<TaskSnapshot, 'task' | 'summary' | 'next_step'> | null;
|
|
158
147
|
export interface PreCompactOptions {
|
|
@@ -168,12 +157,12 @@ export interface PreCompactOptions {
|
|
|
168
157
|
* could turn a caught-and-logged failure back into a non-zero exit.
|
|
169
158
|
*/
|
|
170
159
|
export declare function cmdPreCompact(hippoRoot: string, options: PreCompactOptions): Promise<void>;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
*/
|
|
177
|
-
export declare function
|
|
160
|
+
export interface PostCompactOptions {
|
|
161
|
+
stdinText?: string;
|
|
162
|
+
logFile?: string;
|
|
163
|
+
afterSave?: (transcriptPath: string, originProject: string, log: (message: string) => void) => void;
|
|
164
|
+
}
|
|
165
|
+
/** PostCompact entry point: saves the summary and its items, replays earlier leftovers, returns the one line to show. Never throws, so the hook exits 0. */
|
|
166
|
+
export declare function cmdPostCompact(hippoRoot: string, options: PostCompactOptions): string | null;
|
|
178
167
|
export {};
|
|
179
168
|
//# sourceMappingURL=capture.d.ts.map
|
package/dist/capture.js
CHANGED
|
@@ -13,7 +13,9 @@ import * as path from 'path';
|
|
|
13
13
|
import { createMemory, Layer } from './memory.js';
|
|
14
14
|
import { duplicateKey, storedTextKeys } from './same-text.js';
|
|
15
15
|
import { isContentWorthStoring } from './audit.js';
|
|
16
|
-
import { isInitialized,
|
|
16
|
+
import { isInitialized, openStore, stampOriginProject, writeEntryMirrors, loadAllEntries, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, } from './store.js';
|
|
17
|
+
import { gatedWrite } from './gated-write.js';
|
|
18
|
+
import { PRE_COMPACT_INSTRUCTION, compactionOrigin, parsePostCompactPayload, postCompactLine, recordCompactionStart, recordSnapshotSaved, saveCompaction, replayCompactionsAt, COMPACTION_DB_WAIT_MS, } from './compaction-record.js';
|
|
17
19
|
import { getGlobalRoot, initGlobal } from './shared.js';
|
|
18
20
|
import { embedMemory } from './embeddings.js';
|
|
19
21
|
import { resolveTenantId } from './tenant.js';
|
|
@@ -627,11 +629,11 @@ function summariseSessionTurns(turns) {
|
|
|
627
629
|
* Priority, where the first source present is the only one tried:
|
|
628
630
|
* 1. Explicit `transcriptPath` option (from `--transcript <path>`)
|
|
629
631
|
* 2. Stdin JSON payload (Claude Code / OpenCode SessionEnd hook shape)
|
|
630
|
-
* 3. Most recent `.jsonl` under `~/.claude/projects/<any>/`, only
|
|
632
|
+
* 3. Most recent `.jsonl` under `~/.claude/projects/<any>/`, only when the caller passes `mayScan` (only the caller knows it is not a hook) and there is no path and no stdin text, because this scan spans every project on the box
|
|
631
633
|
*
|
|
632
634
|
* Returns null when nothing resolves, a named transcript or payload whose file is missing included. Never throws.
|
|
633
635
|
*/
|
|
634
|
-
export function resolveLastSessionTranscript(explicit, stdinText,
|
|
636
|
+
export function resolveLastSessionTranscript(explicit, stdinText, opts) {
|
|
635
637
|
if (explicit)
|
|
636
638
|
return fs.existsSync(explicit) ? explicit : null;
|
|
637
639
|
if (stdinText && stdinText.trim() !== '') {
|
|
@@ -648,7 +650,7 @@ export function resolveLastSessionTranscript(explicit, stdinText, stdinTimedOut
|
|
|
648
650
|
}
|
|
649
651
|
return null;
|
|
650
652
|
}
|
|
651
|
-
if (
|
|
653
|
+
if (!opts.mayScan)
|
|
652
654
|
return null;
|
|
653
655
|
const home = process.env.HOME || process.env.USERPROFILE;
|
|
654
656
|
if (!home)
|
|
@@ -788,7 +790,7 @@ function cmdCaptureCore(hippoRoot, options) {
|
|
|
788
790
|
case 'last-session': {
|
|
789
791
|
let turns = options.sessionTurns;
|
|
790
792
|
if (!turns) {
|
|
791
|
-
const resolved = resolveLastSessionTranscript(options.transcriptPath, options.stdinText, options.stdinTimedOut);
|
|
793
|
+
const resolved = resolveLastSessionTranscript(options.transcriptPath, options.stdinText, { mayScan: !options.stdinTimedOut });
|
|
792
794
|
if (!resolved) {
|
|
793
795
|
console.log('No transcript found. Pass --transcript <path> or run from a SessionEnd hook.');
|
|
794
796
|
return;
|
|
@@ -832,6 +834,7 @@ function cmdCaptureCore(hippoRoot, options) {
|
|
|
832
834
|
// once, run the same checkRejectionGuard the real write path uses via
|
|
833
835
|
// writeEntry, never write anything.
|
|
834
836
|
const dryRunDb = options.dryRun ? openHippoDb(targetRoot) : null;
|
|
837
|
+
const writeDb = options.dryRun ? null : openStore(targetRoot);
|
|
835
838
|
try {
|
|
836
839
|
for (const item of extracted) {
|
|
837
840
|
if (keys.has(duplicateKey(item.content))) {
|
|
@@ -876,19 +879,20 @@ function cmdCaptureCore(hippoRoot, options) {
|
|
|
876
879
|
}
|
|
877
880
|
console.log(` [capture] (${item.category}) ${item.content}`);
|
|
878
881
|
}
|
|
879
|
-
else {
|
|
882
|
+
else if (writeDb !== null) {
|
|
880
883
|
// AT1 (plan §3 containment): one rejected item must not abort the
|
|
881
884
|
// rest of this capture's items.
|
|
882
|
-
|
|
883
|
-
|
|
885
|
+
const stamped = stampOriginProject(targetRoot, entry);
|
|
886
|
+
const outcome = gatedWrite(writeDb, targetRoot, stamped);
|
|
887
|
+
if (outcome === 'skipped:rejected') {
|
|
888
|
+
rejected++;
|
|
889
|
+
continue;
|
|
884
890
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
continue;
|
|
889
|
-
}
|
|
890
|
-
throw err;
|
|
891
|
+
if (outcome !== 'written') {
|
|
892
|
+
skipped++;
|
|
893
|
+
continue;
|
|
891
894
|
}
|
|
895
|
+
writeEntryMirrors(targetRoot, stamped);
|
|
892
896
|
updateStats(targetRoot, { remembered: 1 });
|
|
893
897
|
keys.add(duplicateKey(item.content)); // within-batch dedup
|
|
894
898
|
void embedMemory(targetRoot, entry);
|
|
@@ -899,6 +903,8 @@ function cmdCaptureCore(hippoRoot, options) {
|
|
|
899
903
|
finally {
|
|
900
904
|
if (dryRunDb)
|
|
901
905
|
closeHippoDb(dryRunDb);
|
|
906
|
+
if (writeDb)
|
|
907
|
+
closeHippoDb(writeDb);
|
|
902
908
|
}
|
|
903
909
|
const prefix = options.dryRun ? '[dry-run] ' : '';
|
|
904
910
|
const globalPrefix = useGlobal ? '[global] ' : '';
|
|
@@ -1098,18 +1104,14 @@ function isReadableFile(filePath) {
|
|
|
1098
1104
|
return false;
|
|
1099
1105
|
}
|
|
1100
1106
|
}
|
|
1101
|
-
/**
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
* pre-compact log, so both hooks find it from the same `--log-file`.
|
|
1110
|
-
*/
|
|
1111
|
-
export function preCompactReportPath(logFile) {
|
|
1112
|
-
return path.join(path.dirname(logFile), 'pre-compact-last.json');
|
|
1107
|
+
/** PreCompact stdout is the summariser's instructions; sent before the snapshot work because a locked store can run the hook past its 30 s limit, and via writeSync because process.exit drops buffered pipe output. */
|
|
1108
|
+
function printPreCompactInstruction(logFile) {
|
|
1109
|
+
try {
|
|
1110
|
+
fs.writeSync(1, `${PRE_COMPACT_INSTRUCTION}\n`);
|
|
1111
|
+
}
|
|
1112
|
+
catch (err) {
|
|
1113
|
+
appendPreCompactLog(logFile, `instruction not printed: ${errorMessage(err)}`);
|
|
1114
|
+
}
|
|
1113
1115
|
}
|
|
1114
1116
|
/** 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. */
|
|
1115
1117
|
export function transcriptWorkingState(transcriptPath, log) {
|
|
@@ -1150,8 +1152,8 @@ export function transcriptWorkingState(transcriptPath, log) {
|
|
|
1150
1152
|
next_step: nextStep.trim() ? truncateCodePointSafe(nextStep, PRE_COMPACT_NEXT_STEP_CAP) : '',
|
|
1151
1153
|
};
|
|
1152
1154
|
}
|
|
1153
|
-
/** Runs the PreCompact producer: saves a working-state snapshot
|
|
1154
|
-
function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile
|
|
1155
|
+
/** Runs the PreCompact producer: records the compaction, asks the summariser for memories, saves a working-state snapshot. Never extracts memories itself; SessionEnd capture owns that. */
|
|
1156
|
+
function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile) {
|
|
1155
1157
|
// X3: the PreCompact hook fires in every Claude Code project, including
|
|
1156
1158
|
// ones that never ran `hippo init`, so gate before any store-opening call
|
|
1157
1159
|
// (saveActiveTaskSnapshot etc. call initStore, which would create one).
|
|
@@ -1175,6 +1177,8 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
|
|
|
1175
1177
|
const manualInvocation = !stdinText || stdinText.trim() === '';
|
|
1176
1178
|
let sessionId = null;
|
|
1177
1179
|
let payloadTranscriptPath = null;
|
|
1180
|
+
let payloadCwd = null;
|
|
1181
|
+
let payloadTrigger = null;
|
|
1178
1182
|
if (!manualInvocation) {
|
|
1179
1183
|
let payload;
|
|
1180
1184
|
try {
|
|
@@ -1193,8 +1197,9 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
|
|
|
1193
1197
|
}
|
|
1194
1198
|
if ('session_id' in payload && isStringValue(payload.session_id))
|
|
1195
1199
|
sessionId = payload.session_id;
|
|
1196
|
-
report.sessionId = sessionId;
|
|
1197
1200
|
payloadTranscriptPath = payload.transcript_path;
|
|
1201
|
+
payloadCwd = 'cwd' in payload && isStringValue(payload.cwd) ? payload.cwd : null;
|
|
1202
|
+
payloadTrigger = 'trigger' in payload && isStringValue(payload.trigger) ? payload.trigger : null;
|
|
1198
1203
|
}
|
|
1199
1204
|
// X11: payload transcript_path must end .jsonl. No directory-containment
|
|
1200
1205
|
// check is applied on top of this — CLAUDE_CONFIG_DIR can relocate the
|
|
@@ -1207,6 +1212,12 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
|
|
|
1207
1212
|
appendPreCompactLog(logFile, `skip: payload transcript_path is not a .jsonl file: ${payloadTranscriptPath}`);
|
|
1208
1213
|
return;
|
|
1209
1214
|
}
|
|
1215
|
+
// The record is the "something saved before every compaction", so it lands even when no snapshot is derivable below.
|
|
1216
|
+
let recordId = null;
|
|
1217
|
+
if (sessionId !== null && sessionId !== '') {
|
|
1218
|
+
recordId = recordCompactionStart(hippoRoot, { sessionId, trigger: payloadTrigger, cwd: payloadCwd, transcriptPath: payloadTranscriptPath }, (message) => appendPreCompactLog(logFile, message));
|
|
1219
|
+
printPreCompactInstruction(logFile);
|
|
1220
|
+
}
|
|
1210
1221
|
// A payload transcript_path is EXCLUSIVE: never fall back to
|
|
1211
1222
|
// newest-transcript auto-discovery when it's missing/unreadable. That
|
|
1212
1223
|
// fallback would snapshot a DIFFERENT session's transcript under THIS
|
|
@@ -1224,7 +1235,7 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
|
|
|
1224
1235
|
}
|
|
1225
1236
|
}
|
|
1226
1237
|
else {
|
|
1227
|
-
transcriptPath = resolveLastSessionTranscript(undefined, stdinText);
|
|
1238
|
+
transcriptPath = resolveLastSessionTranscript(undefined, stdinText, { mayScan: true });
|
|
1228
1239
|
}
|
|
1229
1240
|
if (!transcriptPath) {
|
|
1230
1241
|
appendPreCompactLog(logFile, 'skip: no transcript resolved');
|
|
@@ -1275,7 +1286,8 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
|
|
|
1275
1286
|
session_id: sessionId,
|
|
1276
1287
|
});
|
|
1277
1288
|
appendPreCompactLog(logFile, 'snapshot saved');
|
|
1278
|
-
|
|
1289
|
+
if (recordId !== null)
|
|
1290
|
+
recordSnapshotSaved(hippoRoot, recordId, (message) => appendPreCompactLog(logFile, message));
|
|
1279
1291
|
}
|
|
1280
1292
|
catch (err) {
|
|
1281
1293
|
appendPreCompactLog(logFile, `snapshot save failed: ${errorMessage(err)}`);
|
|
@@ -1291,68 +1303,51 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile, report) {
|
|
|
1291
1303
|
*/
|
|
1292
1304
|
export async function cmdPreCompact(hippoRoot, options) {
|
|
1293
1305
|
const logFile = options.logFile ?? defaultPreCompactLogPath();
|
|
1294
|
-
const report = { snapshotSaved: false, sessionId: null };
|
|
1295
1306
|
try {
|
|
1296
|
-
runPreCompact(hippoRoot, options.stdinText, options.stdinTimedOut ?? false, logFile
|
|
1307
|
+
runPreCompact(hippoRoot, options.stdinText, options.stdinTimedOut ?? false, logFile);
|
|
1297
1308
|
}
|
|
1298
1309
|
catch (err) {
|
|
1299
1310
|
appendPreCompactLog(logFile, `pre-compact failed: ${errorMessage(err)}`);
|
|
1300
1311
|
}
|
|
1301
|
-
// Nothing goes to stdout: Claude Code passes PreCompact stdout to the
|
|
1302
|
-
// summarising model as extra instructions. The PostCompact hook
|
|
1303
|
-
// (`hippo post-compact`) tells the user instead, from this report.
|
|
1304
|
-
if (report.snapshotSaved) {
|
|
1305
|
-
try {
|
|
1306
|
-
fs.writeFileSync(preCompactReportPath(logFile), JSON.stringify({ ...report, at: new Date().toISOString() }));
|
|
1307
|
-
}
|
|
1308
|
-
catch {
|
|
1309
|
-
// Losing the message must never fail the hook.
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
1312
|
process.exit(0);
|
|
1313
1313
|
}
|
|
1314
|
-
/**
|
|
1315
|
-
const
|
|
1316
|
-
/**
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
*/
|
|
1322
|
-
export function postCompactMessage(stdinText, logFile = defaultPreCompactLogPath(), now = new Date()) {
|
|
1323
|
-
const reportFile = preCompactReportPath(logFile);
|
|
1324
|
-
let raw;
|
|
1325
|
-
try {
|
|
1326
|
-
raw = fs.readFileSync(reportFile, 'utf8');
|
|
1327
|
-
fs.rmSync(reportFile, { force: true });
|
|
1328
|
-
}
|
|
1329
|
-
catch {
|
|
1330
|
-
return null;
|
|
1331
|
-
}
|
|
1314
|
+
/** A PostCompact hook has 10 s in all; replay stops starting new records after this. */
|
|
1315
|
+
const POST_COMPACT_REPLAY_BUDGET_MS = 6000;
|
|
1316
|
+
/** PostCompact entry point: saves the summary and its items, replays earlier leftovers, returns the one line to show. Never throws, so the hook exits 0. */
|
|
1317
|
+
export function cmdPostCompact(hippoRoot, options) {
|
|
1318
|
+
const logFile = options.logFile ?? defaultPreCompactLogPath();
|
|
1319
|
+
const log = (message) => appendPreCompactLog(logFile, `post-compact: ${message}`);
|
|
1320
|
+
const deadline = Date.now() + POST_COMPACT_REPLAY_BUDGET_MS;
|
|
1332
1321
|
try {
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
return null;
|
|
1336
|
-
const at = 'at' in report && isStringValue(report.at) ? Date.parse(report.at) : Number.NaN;
|
|
1337
|
-
if (Number.isNaN(at) || now.getTime() - at > POST_COMPACT_REPORT_MAX_AGE_MS)
|
|
1322
|
+
if (!isInitialized(hippoRoot)) {
|
|
1323
|
+
log('skip: no hippo store');
|
|
1338
1324
|
return null;
|
|
1339
|
-
let payloadSession = null;
|
|
1340
|
-
try {
|
|
1341
|
-
const payload = JSON.parse((stdinText ?? '').trim() || 'null');
|
|
1342
|
-
if (isObjectLike(payload) && 'session_id' in payload && isStringValue(payload.session_id))
|
|
1343
|
-
payloadSession = payload.session_id;
|
|
1344
1325
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1326
|
+
const payload = parsePostCompactPayload(options.stdinText);
|
|
1327
|
+
let line = null;
|
|
1328
|
+
let storeBusy = false;
|
|
1329
|
+
if (payload === null) {
|
|
1330
|
+
log('skip: no PostCompact payload naming a session');
|
|
1347
1331
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1332
|
+
else {
|
|
1333
|
+
const saved = saveCompaction(hippoRoot, payload, log);
|
|
1334
|
+
line = postCompactLine(saved);
|
|
1335
|
+
storeBusy = saved.deferred;
|
|
1336
|
+
if (!storeBusy && payload.transcriptPath !== null && options.afterSave) {
|
|
1337
|
+
try {
|
|
1338
|
+
options.afterSave(payload.transcriptPath, compactionOrigin(hippoRoot, payload.cwd), log);
|
|
1339
|
+
}
|
|
1340
|
+
catch (err) {
|
|
1341
|
+
log(`agent memory import failed: ${errorMessage(err)}`);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
if (!storeBusy)
|
|
1346
|
+
replayCompactionsAt(hippoRoot, log, { busyWaitMs: COMPACTION_DB_WAIT_MS, deadline });
|
|
1347
|
+
return line;
|
|
1354
1348
|
}
|
|
1355
|
-
catch {
|
|
1349
|
+
catch (err) {
|
|
1350
|
+
log(`failed: ${errorMessage(err)}`);
|
|
1356
1351
|
return null;
|
|
1357
1352
|
}
|
|
1358
1353
|
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -43,8 +43,6 @@ export declare function parseArgs(argv: string[]): {
|
|
|
43
43
|
args: string[];
|
|
44
44
|
flags: Record<string, string | boolean | string[]>;
|
|
45
45
|
};
|
|
46
|
-
/** Import new entries from the Claude Code auto memory of the store's project, the frontmatter .md files in ~/.claude/projects/<project>/memory/. */
|
|
47
|
-
export declare function learnFromMemoryMd(hippoRoot: string, homeDir?: string): number;
|
|
48
46
|
/**
|
|
49
47
|
* Render an api.sleep result as console output, byte-identical to the
|
|
50
48
|
* pre-extraction inline implementation in cmdSleepCore.
|