hippo-memory 1.38.3 → 1.38.9

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 (50) hide show
  1. package/dist/api.d.ts +4 -14
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +22 -2
  4. package/dist/api.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +62 -62
  7. package/dist/cli.js.map +1 -1
  8. package/dist/client.d.ts +7 -0
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +15 -1
  11. package/dist/client.js.map +1 -1
  12. package/dist/consolidate.d.ts.map +1 -1
  13. package/dist/consolidate.js +11 -22
  14. package/dist/consolidate.js.map +1 -1
  15. package/dist/db.d.ts.map +1 -1
  16. package/dist/db.js +18 -3
  17. package/dist/db.js.map +1 -1
  18. package/dist/replay.d.ts +1 -1
  19. package/dist/replay.d.ts.map +1 -1
  20. package/dist/replay.js +5 -3
  21. package/dist/replay.js.map +1 -1
  22. package/dist/search.d.ts.map +1 -1
  23. package/dist/search.js +3 -1
  24. package/dist/search.js.map +1 -1
  25. package/dist/src/api.js +22 -2
  26. package/dist/src/api.js.map +1 -1
  27. package/dist/src/cli.js +62 -62
  28. package/dist/src/cli.js.map +1 -1
  29. package/dist/src/client.js +15 -1
  30. package/dist/src/client.js.map +1 -1
  31. package/dist/src/consolidate.js +11 -22
  32. package/dist/src/consolidate.js.map +1 -1
  33. package/dist/src/db.js +18 -3
  34. package/dist/src/db.js.map +1 -1
  35. package/dist/src/replay.js +5 -3
  36. package/dist/src/replay.js.map +1 -1
  37. package/dist/src/search.js +3 -1
  38. package/dist/src/search.js.map +1 -1
  39. package/dist/src/store.js +18 -6
  40. package/dist/src/store.js.map +1 -1
  41. package/dist/src/version.js +1 -1
  42. package/dist/store.d.ts.map +1 -1
  43. package/dist/store.js +18 -6
  44. package/dist/store.js.map +1 -1
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  48. package/extensions/openclaw-plugin/package.json +1 -1
  49. package/openclaw.plugin.json +1 -1
  50. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -36,7 +36,6 @@ import { fileURLToPath } from 'node:url';
36
36
  import { execFileSync, execSync, spawn } from 'child_process';
37
37
  import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInstalledTools, defaultSleepLogPath, ensureCodexWrapperInstalled, installCodexWrapper, isCodexWrapperInstalled, repairCodexWrapperIfInstalled, uninstallCodexWrapper, resolveCodexSessionTranscript, resolveCodexWrapperPaths, installOpencodePlugin, uninstallOpencodePlugin, resolveOpencodePluginPath, } from './hooks.js';
38
38
  import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, computeSchemaFit, Layer, } from './memory.js';
39
- import { resolveProjectIdentity } from './project-identity.js';
40
39
  import { detectSecret } from './secret-detect.js';
41
40
  import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, RECALL_DEFAULT_DENY_SCOPES, } from './store.js';
42
41
  import { rejectValue, unrejectValue, listRejectionsForTenant } from './reject-flow.js';
@@ -115,7 +114,7 @@ import { graphExpandRecall, MAX_HOPS, DEFAULT_MAX_NEIGHBORS } from './graph-reca
115
114
  import { DEFAULT_GRAPH_STREAM_WEIGHT } from './graph-stream.js';
116
115
  import { getReranker } from './rerankers/index.js';
117
116
  import { computeSalience } from './salience.js';
118
- import { computeAmbientState, renderAmbientSummary } from './ambient.js';
117
+ import { renderAmbientSummary } from './ambient.js';
119
118
  import { validateOwner, isStrictOwnerEnv } from './owner-validation.js';
120
119
  import { pruneAuditLog, parseOlderThanFlag } from './audit-prune.js';
121
120
  import { listDlq, replayDlqEntry } from './connectors/slack/dlq.js';
@@ -2868,7 +2867,7 @@ function cmdCompactResume(hippoRoot, stdinText) {
2868
2867
  * immediately. The child writes to the log file and survives TUI teardown;
2869
2868
  * the next SessionStart reads the log via `hippo last-sleep`.
2870
2869
  */
2871
- function cmdSessionEnd(hippoRoot, flags) {
2870
+ async function cmdSessionEnd(hippoRoot, flags) {
2872
2871
  const logFile = typeof flags['log-file'] === 'string' ? flags['log-file'] : null;
2873
2872
  // Read stdin synchronously. The SessionEnd hook payload carries
2874
2873
  // `transcript_path` as JSON; we extract it here and pass it to the worker
@@ -2915,7 +2914,7 @@ function cmdSessionEnd(hippoRoot, flags) {
2915
2914
  // --session-id (both are stdin-derived, argv-only for the child), so in
2916
2915
  // this fallback capture auto-discovers the transcript and the DF1
2917
2916
  // snapshot close no-ops — the ambient freshness bound is the backstop.
2918
- cmdSessionEndWorker(hippoRoot, flags);
2917
+ await cmdSessionEndWorker(hippoRoot, flags);
2919
2918
  return;
2920
2919
  }
2921
2920
  }
@@ -2924,9 +2923,9 @@ function cmdSessionEnd(hippoRoot, flags) {
2924
2923
  * `__session-end-worker` subcommand (not user-facing). Failures in one stage
2925
2924
  * do not block the other.
2926
2925
  */
2927
- function cmdSessionEndWorker(hippoRoot, flags) {
2926
+ async function cmdSessionEndWorker(hippoRoot, flags) {
2928
2927
  try {
2929
- cmdSleep(hippoRoot, flags);
2928
+ await cmdSleep(hippoRoot, flags);
2930
2929
  }
2931
2930
  catch {
2932
2931
  // sleep errors are already tee'd to the log file via cmdSleep's
@@ -3035,7 +3034,7 @@ function cmdCodexRun(hippoRoot, args) {
3035
3034
  console.error(`Failed to launch Codex: ${err.message}`);
3036
3035
  process.exit(1);
3037
3036
  });
3038
- child.on('exit', (code, signal) => {
3037
+ child.on('exit', async (code, signal) => {
3039
3038
  const workerArgs = [
3040
3039
  process.argv[1],
3041
3040
  '__codex-session-end-worker',
@@ -3060,13 +3059,19 @@ function cmdCodexRun(hippoRoot, args) {
3060
3059
  }
3061
3060
  catch {
3062
3061
  // Fall back to the inline path if the detached worker cannot be created.
3063
- cmdCodexSessionEndWorker(hippoRoot, {
3064
- 'codex-home': path.dirname(historyPath),
3065
- 'history-path': historyPath,
3066
- 'start-offset': String(startOffsetBytes),
3067
- 'started-at': String(startedAtMs),
3068
- 'log-file': metadata.logFile,
3069
- });
3062
+ // Awaited so the sleep write can't be killed by the exit calls below.
3063
+ try {
3064
+ await cmdCodexSessionEndWorker(hippoRoot, {
3065
+ 'codex-home': path.dirname(historyPath),
3066
+ 'history-path': historyPath,
3067
+ 'start-offset': String(startOffsetBytes),
3068
+ 'started-at': String(startedAtMs),
3069
+ 'log-file': metadata.logFile,
3070
+ });
3071
+ }
3072
+ catch {
3073
+ // cmdCodexSessionEndWorker already fail-softs internally; this is belt-and-braces.
3074
+ }
3070
3075
  }
3071
3076
  if (signal) {
3072
3077
  try {
@@ -3080,10 +3085,10 @@ function cmdCodexRun(hippoRoot, args) {
3080
3085
  process.exit(code ?? 0);
3081
3086
  });
3082
3087
  }
3083
- function cmdCodexSessionEndWorker(hippoRoot, flags) {
3088
+ async function cmdCodexSessionEndWorker(hippoRoot, flags) {
3084
3089
  const logFile = typeof flags['log-file'] === 'string' ? flags['log-file'] : undefined;
3085
3090
  try {
3086
- cmdSleep(hippoRoot, logFile ? { 'log-file': logFile } : {});
3091
+ await cmdSleep(hippoRoot, logFile ? { 'log-file': logFile } : {});
3087
3092
  }
3088
3093
  catch {
3089
3094
  // sleep errors are already written via cmdSleep
@@ -3314,6 +3319,8 @@ function cmdOutcome(hippoRoot, flags) {
3314
3319
  }
3315
3320
  console.log(`Applied ${good ? 'positive' : 'negative'} outcome to ${updated} memor${updated === 1 ? 'y' : 'ies'}`);
3316
3321
  }
3322
+ // Shared between the forget dispatch and cmdForget so the message can't drift.
3323
+ const ARCHIVE_REASON_REQUIRED = 'hippo forget --archive requires --reason "<why>" (recorded on the archive).';
3317
3324
  function cmdForget(hippoRoot, id, flags) {
3318
3325
  requireInit(hippoRoot);
3319
3326
  const ctx = {
@@ -3327,12 +3334,11 @@ function cmdForget(hippoRoot, id, flags) {
3327
3334
  if (flags['archive'] === true) {
3328
3335
  const reason = typeof flags['reason'] === 'string' ? flags['reason'] : null;
3329
3336
  if (!reason) {
3330
- console.error('hippo forget --archive requires --reason "<why>" (recorded on the archive).');
3337
+ console.error(ARCHIVE_REASON_REQUIRED);
3331
3338
  process.exit(1);
3332
3339
  }
3333
3340
  try {
3334
3341
  api.archiveRaw(ctx, id, reason);
3335
- updateStats(hippoRoot, { forgotten: 1 });
3336
3342
  console.log(`Archived ${id}`);
3337
3343
  }
3338
3344
  catch (err) {
@@ -3343,7 +3349,6 @@ function cmdForget(hippoRoot, id, flags) {
3343
3349
  }
3344
3350
  try {
3345
3351
  api.forget(ctx, id);
3346
- updateStats(hippoRoot, { forgotten: 1 });
3347
3352
  console.log(`Forgot ${id}`);
3348
3353
  }
3349
3354
  catch (err) {
@@ -5590,30 +5595,8 @@ async function cmdContext(hippoRoot, args, flags, stdinText) {
5590
5595
  printContextMarkdown(renderItems, result.tokens, framing);
5591
5596
  }
5592
5597
  printCrossProjectSection(crossEntries);
5593
- // Ambient state summary (CLI-side: api.getContext doesn't load all entries
5594
- // post-selection, so we re-load here for the landscape summary).
5595
- const ambientConfig = loadConfig(hippoRoot);
5596
- if (ambientConfig.ambient.enabled && !pinnedOnly) {
5597
- const tenantId = ctx.tenantId;
5598
- const globalRoot = getGlobalRoot();
5599
- const hasGlobalForAmbient = isInitialized(globalRoot);
5600
- // v39: the landscape summary must reflect the same admission policy as
5601
- // the injected entries - otherwise excluded rows leak back in as
5602
- // aggregate signal (codex P2-13).
5603
- const isolationOff = ambientConfig.contextProjectIsolation === false;
5604
- const ambientCurrentName = resolveProjectIdentity(process.cwd()).name;
5605
- const ambientAdmit = (e) => api.ambientAdmitEntry(e, ambientCurrentName, crossProject || isolationOff);
5606
- const localForAmbient = isInitialized(hippoRoot)
5607
- ? loadAllEntries(hippoRoot, tenantId).filter(e => !e.superseded_by).filter(ambientAdmit)
5608
- : [];
5609
- const globalForAmbient = hasGlobalForAmbient
5610
- ? loadAllEntries(globalRoot, tenantId).filter(e => !e.superseded_by).filter(ambientAdmit)
5611
- : [];
5612
- const allForAmbient = [...localForAmbient, ...globalForAmbient];
5613
- if (allForAmbient.length > 0) {
5614
- const ambientState = computeAmbientState(allForAmbient);
5615
- console.log(`\n${renderAmbientSummary(ambientState)}`);
5616
- }
5598
+ if (result.ambientState) {
5599
+ console.log(`\n${renderAmbientSummary(result.ambientState)}`);
5617
5600
  }
5618
5601
  }
5619
5602
  }
@@ -8130,8 +8113,11 @@ async function main() {
8130
8113
  }
8131
8114
  // Thin-client routing. When a server is up, simple `remember` calls go
8132
8115
  // over HTTP so the daemon stays single-writer (footgun #2). Rich CLI
8133
- // flags (--pin, --layer, --extract, --global, salience gates) still
8134
- // need the direct path; we only intercept the minimal envelope.
8116
+ // flags (--pin, --layer, --extract, --global) still need the direct
8117
+ // path; we only intercept the minimal envelope. The salience gate is
8118
+ // NOT in richFlag and the route does not apply it, so a routed remember
8119
+ // stores what a direct one would skip. Measured 2026-09-07, tracked in
8120
+ // TODOS.md; do not read this list as covering salience.
8135
8121
  const richFlag = flags['pin'] || flags['global'] || flags['extract'] || flags['force'] ||
8136
8122
  flags['observed'] || flags['inferred'] || flags['verified'] ||
8137
8123
  flags['layer'] !== undefined;
@@ -8247,10 +8233,10 @@ async function main() {
8247
8233
  cmdLastSleep(flags);
8248
8234
  break;
8249
8235
  case 'session-end':
8250
- cmdSessionEnd(hippoRoot, flags);
8236
+ await cmdSessionEnd(hippoRoot, flags);
8251
8237
  break;
8252
8238
  case '__session-end-worker':
8253
- cmdSessionEndWorker(hippoRoot, flags);
8239
+ await cmdSessionEndWorker(hippoRoot, flags);
8254
8240
  break;
8255
8241
  case 'pre-compact': {
8256
8242
  // Same TTY guard as `capture --last-session`: skip reading stdin when
@@ -8287,7 +8273,7 @@ async function main() {
8287
8273
  cmdCodexRun(hippoRoot, args);
8288
8274
  break;
8289
8275
  case '__codex-session-end-worker':
8290
- cmdCodexSessionEndWorker(hippoRoot, flags);
8276
+ await cmdCodexSessionEndWorker(hippoRoot, flags);
8291
8277
  break;
8292
8278
  case 'dedup':
8293
8279
  cmdDedup(hippoRoot, flags);
@@ -8446,23 +8432,37 @@ async function main() {
8446
8432
  console.error('Please provide a memory ID.');
8447
8433
  process.exit(1);
8448
8434
  }
8449
- // A3: --archive is a direct-DB operation (raw archival via archiveRaw);
8450
- // the HTTP forget route does not carry it, so archive requests always
8451
- // take the direct path rather than silently no-op'ing over a server.
8452
- if (flags['archive'] !== true) {
8453
- const routed = await runViaServerIfAvailable(hippoRoot, async (info, apiKey) => {
8454
- try {
8435
+ // Archive has its own HTTP route (POST /v1/memories/:id/archive); route
8436
+ // both branches the same way the direct path does.
8437
+ const archive = flags['archive'] === true;
8438
+ const reason = typeof flags['reason'] === 'string' ? flags['reason'] : null;
8439
+ if (archive && !reason) {
8440
+ console.error(ARCHIVE_REASON_REQUIRED);
8441
+ process.exit(1);
8442
+ }
8443
+ const routed = await runViaServerIfAvailable(hippoRoot, async (info, apiKey) => {
8444
+ try {
8445
+ if (archive) {
8446
+ await client.archiveRaw(info.url, apiKey, id, reason);
8447
+ console.log(`Archived ${id}`);
8448
+ }
8449
+ else {
8455
8450
  await client.forget(info.url, apiKey, id);
8456
8451
  console.log(`Forgot ${id}`);
8457
8452
  }
8458
- catch (err) {
8459
- console.error(err.message);
8460
- process.exit(1);
8461
- }
8462
- });
8463
- if (routed)
8464
- break;
8465
- }
8453
+ }
8454
+ catch (err) {
8455
+ // A server that died after the health probe is the caller's stale
8456
+ // pidfile fallback to handle, not an error to report to the user.
8457
+ if (client.isConnectionRefused(err))
8458
+ throw err;
8459
+ const msg = err instanceof Error ? err.message : String(err);
8460
+ console.error(archive ? `Could not archive ${id}: ${msg}` : msg);
8461
+ process.exit(1);
8462
+ }
8463
+ });
8464
+ if (routed)
8465
+ break;
8466
8466
  cmdForget(hippoRoot, id, flags);
8467
8467
  break;
8468
8468
  }