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.
Files changed (76) hide show
  1. package/README.md +47 -13
  2. package/dist/agent-memories/apply.d.ts +47 -0
  3. package/dist/agent-memories/apply.js +253 -0
  4. package/dist/agent-memories/claude-code.d.ts +11 -0
  5. package/dist/agent-memories/claude-code.js +113 -0
  6. package/dist/agent-memories/codex.d.ts +3 -0
  7. package/dist/agent-memories/codex.js +47 -0
  8. package/dist/agent-memories/copilot.d.ts +3 -0
  9. package/dist/agent-memories/copilot.js +125 -0
  10. package/dist/agent-memories/files.d.ts +37 -0
  11. package/dist/agent-memories/files.js +77 -0
  12. package/dist/agent-memories/folder-store.d.ts +17 -0
  13. package/dist/agent-memories/folder-store.js +44 -0
  14. package/dist/agent-memories/gemini.d.ts +3 -0
  15. package/dist/agent-memories/gemini.js +103 -0
  16. package/dist/agent-memories/git.d.ts +8 -0
  17. package/dist/agent-memories/git.js +11 -0
  18. package/dist/agent-memories/keys.d.ts +9 -0
  19. package/dist/agent-memories/keys.js +20 -0
  20. package/dist/agent-memories/legacy.d.ts +17 -0
  21. package/dist/agent-memories/legacy.js +45 -0
  22. package/dist/agent-memories/markdown.d.ts +13 -0
  23. package/dist/agent-memories/markdown.js +123 -0
  24. package/dist/agent-memories/openclaw.d.ts +3 -0
  25. package/dist/agent-memories/openclaw.js +42 -0
  26. package/dist/agent-memories/plan.d.ts +78 -0
  27. package/dist/agent-memories/plan.js +123 -0
  28. package/dist/agent-memories/qwen-code.d.ts +5 -0
  29. package/dist/agent-memories/qwen-code.js +50 -0
  30. package/dist/agent-memories/report.d.ts +52 -0
  31. package/dist/agent-memories/report.js +88 -0
  32. package/dist/agent-memories/source.d.ts +16 -0
  33. package/dist/agent-memories/source.js +32 -0
  34. package/dist/agent-memories/sync.d.ts +33 -0
  35. package/dist/agent-memories/sync.js +336 -0
  36. package/dist/agent-memories/tools.d.ts +33 -0
  37. package/dist/agent-memories/tools.js +19 -0
  38. package/dist/agent-memories/types.d.ts +42 -0
  39. package/dist/agent-memories/types.js +2 -0
  40. package/dist/api.d.ts +2 -2
  41. package/dist/api.js +28 -29
  42. package/dist/audit.d.ts +4 -3
  43. package/dist/audit.js +10 -6
  44. package/dist/capture.d.ts +11 -22
  45. package/dist/capture.js +76 -81
  46. package/dist/cli.d.ts +0 -2
  47. package/dist/cli.js +133 -170
  48. package/dist/compaction-items.d.ts +18 -0
  49. package/dist/compaction-items.js +60 -0
  50. package/dist/compaction-record.d.ts +94 -0
  51. package/dist/compaction-record.js +573 -0
  52. package/dist/config.d.ts +4 -0
  53. package/dist/config.js +13 -0
  54. package/dist/consolidate.d.ts +0 -2
  55. package/dist/consolidate.js +3 -36
  56. package/dist/db.d.ts +5 -1
  57. package/dist/db.js +40 -8
  58. package/dist/dedupe.js +3 -2
  59. package/dist/doctor.js +34 -2
  60. package/dist/dormant.d.ts +5 -3
  61. package/dist/dormant.js +9 -0
  62. package/dist/gated-write.d.ts +9 -0
  63. package/dist/gated-write.js +24 -0
  64. package/dist/hooks.d.ts +4 -2
  65. package/dist/hooks.js +8 -7
  66. package/dist/memory.d.ts +19 -2
  67. package/dist/memory.js +32 -3
  68. package/dist/shared.js +10 -7
  69. package/dist/store.d.ts +14 -2
  70. package/dist/store.js +75 -19
  71. package/dist/version.d.ts +1 -1
  72. package/dist/version.js +1 -1
  73. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  74. package/extensions/openclaw-plugin/package.json +1 -1
  75. package/openclaw.plugin.json +1 -1
  76. package/package.json +1 -1
package/dist/store.d.ts CHANGED
@@ -289,6 +289,8 @@ export declare function loadEntriesByIds(hippoRoot: string, ids: readonly string
289
289
  /** Strengthen what a read returned: update only the four retrieval columns on the live row, never a stale copy.
290
290
  * Best effort: a failure logs and never fails the read. Returns the ids found in this store. */
291
291
  export declare function strengthenRetrieved(hippoRoot: string, ids: readonly string[], tenantId?: string): Set<string>;
292
+ /** strengthenRetrieved on the caller's handle, inside the caller's transaction. Throws; the caller decides. */
293
+ export declare function strengthenRetrievedOn(db: DatabaseSyncLike, ids: readonly string[], tenantId?: string): Set<string>;
292
294
  /**
293
295
  * All `kind='raw'` rows for a given session, tenant-scoped, returned
294
296
  * oldest-first. Used by `api.assemble` to walk a session's chronological
@@ -350,6 +352,10 @@ export declare function loadFreshRawMemories(hippoRoot: string, count: number, t
350
352
  * Used by `drillDown` (Task 3).
351
353
  */
352
354
  export declare function loadChildrenOf(hippoRoot: string, parentId: string, tenantId?: string): MemoryEntry[];
355
+ /** Tables whose rows keep a first-class object's backing memory in `memory_id` (ON DELETE SET NULL); tests/dormant-memories.test.ts pins it to the schema. */
356
+ export declare const MEMORY_BACKED_TABLES: readonly ["predictions", "decisions", "incidents", "processes", "policies", "skills", "project_briefs", "customer_notes"];
357
+ /** Ids of memories that back a first-class object, for passes that plan deletes before making them. A table missing from an older schema is skipped. */
358
+ export declare function memoriesBackingObjects(hippoRoot: string): Set<string>;
353
359
  /**
354
360
  * AT1 (plan §4, round-2 fix, designed from source): db-scoped delete core.
355
361
  * `deleteEntry` used to open+close its OWN connection, which meant it could
@@ -367,7 +373,7 @@ export declare function loadChildrenOf(hippoRoot: string, parentId: string, tena
367
373
  * Default keeps `deleteEntry` byte-identical to its pre-split behavior.
368
374
  *
369
375
  * Returns `{tenantId, dagParentId}` for the removed row, or `null` if no row with `id`
370
- * existed or `automatic` refused it (pinned or raw at DELETE time, so a late pin wins).
376
+ * existed or `automatic` refused it (pinned, raw, kept for good or backing an object at DELETE time, so a late pin wins).
371
377
  */
372
378
  export declare function deleteEntryCore(db: ReturnType<typeof openHippoDb>, id: string, opts?: {
373
379
  actor?: string;
@@ -400,7 +406,7 @@ export declare function deleteEntry(hippoRoot: string, id: string, opts?: {
400
406
  * `dormant` (src/dormant.ts): each move's snapshot is inserted into `dormant_memories` and its `memories` row
401
407
  * leaves exactly like a delete (FTS row, DAG parent dirty-mark, mirrors), in the same transaction, so a memory
402
408
  * is never in both places or in neither. Deletes and moves both skip rows that are no longer auto-deletable
403
- * (pinned or raw since the caller decided). Returns the ids that left `memories`, deleted or moved. */
409
+ * (pinned, raw, kept for good or backing an object since the caller decided). Returns the ids that left `memories`, deleted or moved. */
404
410
  export declare function batchWriteAndDelete(hippoRoot: string, toWrite: MemoryEntry[], toDeleteIds: string[], opts?: {
405
411
  snapshot?: ReadonlyMap<string, MemoryEntry>;
406
412
  dormant?: DormantMove[];
@@ -415,6 +421,12 @@ export declare function batchWriteAndDelete(hippoRoot: string, toWrite: MemoryEn
415
421
  export declare function loadAllEntries(hippoRoot: string, tenantId?: string): MemoryEntry[];
416
422
  /** Every memory row on an open connection, so a caller can read inside its own transaction. */
417
423
  export declare function selectAllEntries(db: DatabaseSyncLike, tenantId?: string): MemoryEntry[];
424
+ /** Live rows whose source starts with `prefix`, on the caller's handle; LIKE folds case, so the prefix is checked again exactly. */
425
+ export declare function selectLiveEntriesBySourcePrefix(db: DatabaseSyncLike, tenantId: string, prefix: string): MemoryEntry[];
426
+ /** Rewrites a live row's tags and its full-text row on the caller's transaction, with no audit row. */
427
+ export declare function setEntryTagsInTx(db: DatabaseSyncLike, entry: MemoryEntry): void;
428
+ /** Removes a row from `memories` and full-text search on the caller's transaction, as sleep's dormant move does, and marks its summary parent dirty. */
429
+ export declare function deleteEntryRowInTx(db: DatabaseSyncLike, entry: MemoryEntry, actor: string): void;
418
430
  export declare function loadContentsWithTag(hippoRoot: string, tenantId: string, tag: string): string[];
419
431
  export interface AmbientRecallRequest {
420
432
  terms: string[];
package/dist/store.js CHANGED
@@ -1349,20 +1349,8 @@ export function strengthenRetrieved(hippoRoot, ids, tenantId) {
1349
1349
  try {
1350
1350
  db = openHippoDb(hippoRoot);
1351
1351
  db.exec('BEGIN IMMEDIATE');
1352
- const tenantClause = tenantId !== undefined ? ' AND tenant_id = ?' : '';
1353
- const select = db.prepare(`SELECT ${MEMORY_SELECT_COLUMNS} FROM memories WHERE id = ?${tenantClause}`);
1354
- const live = [];
1355
- for (const id of ids) {
1356
- // SAFETY: the SELECT names exactly MEMORY_SELECT_COLUMNS, matching MemoryRow's field set.
1357
- const row = (tenantId !== undefined ? select.get(id, tenantId) : select.get(id));
1358
- if (row)
1359
- live.push(rowToEntry(row));
1360
- }
1361
- const update = db.prepare('UPDATE memories SET retrieval_count = ?, last_retrieved = ?, half_life_days = ?, strength = ? WHERE id = ?');
1362
- for (const e of markRetrieved(live)) {
1363
- update.run(e.retrieval_count, e.last_retrieved, e.half_life_days, e.strength, e.id);
1364
- found.add(e.id);
1365
- }
1352
+ for (const id of strengthenRetrievedOn(db, ids, tenantId))
1353
+ found.add(id);
1366
1354
  db.exec('COMMIT');
1367
1355
  }
1368
1356
  catch (error) {
@@ -1379,6 +1367,27 @@ export function strengthenRetrieved(hippoRoot, ids, tenantId) {
1379
1367
  }
1380
1368
  return found;
1381
1369
  }
1370
+ /** strengthenRetrieved on the caller's handle, inside the caller's transaction. Throws; the caller decides. */
1371
+ export function strengthenRetrievedOn(db, ids, tenantId) {
1372
+ const found = new Set();
1373
+ if (ids.length === 0 || isRecallBoostAblated())
1374
+ return found;
1375
+ const tenantClause = tenantId !== undefined ? ' AND tenant_id = ?' : '';
1376
+ const select = db.prepare(`SELECT ${MEMORY_SELECT_COLUMNS} FROM memories WHERE id = ?${tenantClause}`);
1377
+ const live = [];
1378
+ for (const id of ids) {
1379
+ // SAFETY: the SELECT names exactly MEMORY_SELECT_COLUMNS, matching MemoryRow's field set.
1380
+ const row = (tenantId !== undefined ? select.get(id, tenantId) : select.get(id));
1381
+ if (row)
1382
+ live.push(rowToEntry(row));
1383
+ }
1384
+ const update = db.prepare('UPDATE memories SET retrieval_count = ?, last_retrieved = ?, half_life_days = ?, strength = ? WHERE id = ?');
1385
+ for (const e of markRetrieved(live)) {
1386
+ update.run(e.retrieval_count, e.last_retrieved, e.half_life_days, e.strength, e.id);
1387
+ found.add(e.id);
1388
+ }
1389
+ return found;
1390
+ }
1382
1391
  /**
1383
1392
  * All `kind='raw'` rows for a given session, tenant-scoped, returned
1384
1393
  * oldest-first. Used by `api.assemble` to walk a session's chronological
@@ -1538,6 +1547,34 @@ export function loadChildrenOf(hippoRoot, parentId, tenantId) {
1538
1547
  closeHippoDb(db);
1539
1548
  }
1540
1549
  }
1550
+ /** Tables whose rows keep a first-class object's backing memory in `memory_id` (ON DELETE SET NULL); tests/dormant-memories.test.ts pins it to the schema. */
1551
+ export const MEMORY_BACKED_TABLES = ['predictions', 'decisions', 'incidents', 'processes', 'policies', 'skills', 'project_briefs', 'customer_notes'];
1552
+ /** Deleting a memory that backs an object nulls the object's link, and no restore can repair it, so no automatic pass may. */
1553
+ const AUTOMATIC_DELETE_SQL = `${AUTO_DELETABLE_SQL}${MEMORY_BACKED_TABLES.map((t) => ` AND NOT EXISTS (SELECT 1 FROM ${t} WHERE ${t}.memory_id = memories.id)`).join('')}`;
1554
+ /** Ids of memories that back a first-class object, for passes that plan deletes before making them. A table missing from an older schema is skipped. */
1555
+ export function memoriesBackingObjects(hippoRoot) {
1556
+ const ids = new Set();
1557
+ const db = openHippoDb(hippoRoot);
1558
+ try {
1559
+ for (const table of MEMORY_BACKED_TABLES) {
1560
+ try {
1561
+ // SAFETY: SELECT of one nullable TEXT column, filtered to non-null.
1562
+ const rows = db.prepare(`SELECT memory_id FROM ${table} WHERE memory_id IS NOT NULL`).all();
1563
+ for (const r of rows)
1564
+ ids.add(r.memory_id);
1565
+ }
1566
+ catch (err) {
1567
+ // A missing table is an older schema; any other error could hide a backing memory, so the caller stops.
1568
+ if (!(err instanceof Error && err.message.includes('no such table')))
1569
+ throw err;
1570
+ }
1571
+ }
1572
+ }
1573
+ finally {
1574
+ closeHippoDb(db);
1575
+ }
1576
+ return ids;
1577
+ }
1541
1578
  /**
1542
1579
  * AT1 (plan §4, round-2 fix, designed from source): db-scoped delete core.
1543
1580
  * `deleteEntry` used to open+close its OWN connection, which meant it could
@@ -1555,7 +1592,7 @@ export function loadChildrenOf(hippoRoot, parentId, tenantId) {
1555
1592
  * Default keeps `deleteEntry` byte-identical to its pre-split behavior.
1556
1593
  *
1557
1594
  * Returns `{tenantId, dagParentId}` for the removed row, or `null` if no row with `id`
1558
- * existed or `automatic` refused it (pinned or raw at DELETE time, so a late pin wins).
1595
+ * existed or `automatic` refused it (pinned, raw, kept for good or backing an object at DELETE time, so a late pin wins).
1559
1596
  */
1560
1597
  export function deleteEntryCore(db, id, opts) {
1561
1598
  // SAFETY: row's shape matches the three columns named in the SELECT above.
@@ -1564,7 +1601,7 @@ export function deleteEntryCore(db, id, opts) {
1564
1601
  .get(id);
1565
1602
  if (!row?.id)
1566
1603
  return null;
1567
- const guard = opts?.automatic ? ` AND ${AUTO_DELETABLE_SQL}` : '';
1604
+ const guard = opts?.automatic ? ` AND ${AUTOMATIC_DELETE_SQL}` : '';
1568
1605
  if (Number(db.prepare(`DELETE FROM memories WHERE id = ?${guard}`).run(id).changes ?? 0) === 0)
1569
1606
  return null;
1570
1607
  deleteFtsRow(db, id);
@@ -1622,7 +1659,7 @@ function mergeOwnChanges(base, ours, live) {
1622
1659
  * `dormant` (src/dormant.ts): each move's snapshot is inserted into `dormant_memories` and its `memories` row
1623
1660
  * leaves exactly like a delete (FTS row, DAG parent dirty-mark, mirrors), in the same transaction, so a memory
1624
1661
  * is never in both places or in neither. Deletes and moves both skip rows that are no longer auto-deletable
1625
- * (pinned or raw since the caller decided). Returns the ids that left `memories`, deleted or moved. */
1662
+ * (pinned, raw, kept for good or backing an object since the caller decided). Returns the ids that left `memories`, deleted or moved. */
1626
1663
  export function batchWriteAndDelete(hippoRoot, toWrite, toDeleteIds, opts) {
1627
1664
  const dormantMoves = opts?.dormant ?? [];
1628
1665
  if (toWrite.length === 0 && toDeleteIds.length === 0 && dormantMoves.length === 0)
@@ -1650,7 +1687,7 @@ export function batchWriteAndDelete(hippoRoot, toWrite, toDeleteIds, opts) {
1650
1687
  const placeholders = toDeleteIds.map(() => '?').join(',');
1651
1688
  // A row pinned after the caller decided to delete it survives.
1652
1689
  // SAFETY: rows' shape matches the three columns named in the SELECT.
1653
- const rows = db.prepare(`SELECT id, dag_parent_id, tenant_id FROM memories WHERE id IN (${placeholders}) AND ${AUTO_DELETABLE_SQL}`).all(...toDeleteIds);
1690
+ const rows = db.prepare(`SELECT id, dag_parent_id, tenant_id FROM memories WHERE id IN (${placeholders}) AND ${AUTOMATIC_DELETE_SQL}`).all(...toDeleteIds);
1654
1691
  for (const row of rows) {
1655
1692
  deletableIds.push(row.id);
1656
1693
  if (row.dag_parent_id) {
@@ -1737,7 +1774,7 @@ export function batchWriteAndDelete(hippoRoot, toWrite, toDeleteIds, opts) {
1737
1774
  const byId = new Map(dormantMoves.map((m) => [m.entry.id, m]));
1738
1775
  const placeholders = dormantMoves.map(() => '?').join(',');
1739
1776
  // SAFETY: rows' shape matches the three columns named in the SELECT.
1740
- const rows = db.prepare(`SELECT id, dag_parent_id, tenant_id FROM memories WHERE id IN (${placeholders}) AND ${AUTO_DELETABLE_SQL}`).all(...byId.keys());
1777
+ const rows = db.prepare(`SELECT id, dag_parent_id, tenant_id FROM memories WHERE id IN (${placeholders}) AND ${AUTOMATIC_DELETE_SQL}`).all(...byId.keys());
1741
1778
  for (const row of rows) {
1742
1779
  movable.push(byId.get(row.id));
1743
1780
  if (row.dag_parent_id) {
@@ -1810,6 +1847,25 @@ export function selectAllEntries(db, tenantId) {
1810
1847
  : db.prepare(`SELECT ${MEMORY_SELECT_COLUMNS} FROM memories ORDER BY created ASC, id ASC`).all();
1811
1848
  return rows.map(rowToEntry);
1812
1849
  }
1850
+ /** Live rows whose source starts with `prefix`, on the caller's handle; LIKE folds case, so the prefix is checked again exactly. */
1851
+ export function selectLiveEntriesBySourcePrefix(db, tenantId, prefix) {
1852
+ // SAFETY: selects exactly MEMORY_SELECT_COLUMNS, matching MemoryRow's field set.
1853
+ const rows = db.prepare(`SELECT ${MEMORY_SELECT_COLUMNS} FROM memories WHERE tenant_id = ? AND superseded_by IS NULL AND source LIKE ? ESCAPE '\\'`).all(tenantId, `${prefix.replace(/[%_\\]/g, '\\$&')}%`);
1854
+ return rows.map(rowToEntry).filter((entry) => entry.source.startsWith(prefix));
1855
+ }
1856
+ /** Rewrites a live row's tags and its full-text row on the caller's transaction, with no audit row. */
1857
+ export function setEntryTagsInTx(db, entry) {
1858
+ db.prepare(`UPDATE memories SET tags_json = ?, updated_at = datetime('now') WHERE id = ? AND tenant_id = ?`)
1859
+ .run(JSON.stringify(entry.tags), entry.id, entry.tenantId);
1860
+ syncFtsRow(db, entry);
1861
+ }
1862
+ /** Removes a row from `memories` and full-text search on the caller's transaction, as sleep's dormant move does, and marks its summary parent dirty. */
1863
+ export function deleteEntryRowInTx(db, entry, actor) {
1864
+ db.prepare('DELETE FROM memories WHERE id = ? AND tenant_id = ?').run(entry.id, entry.tenantId);
1865
+ deleteFtsRow(db, entry.id);
1866
+ if (entry.dag_parent_id)
1867
+ markSummaryDirtyInTx(db, entry.dag_parent_id, entry.tenantId, actor);
1868
+ }
1813
1869
  // Content of every tenant row tagged `tag`, without reading the rest of the store.
1814
1870
  // `instr` is a substring prefilter over the raw JSON; `includes` below re-checks exactly.
1815
1871
  export function loadContentsWithTag(hippoRoot, tenantId, tag) {
package/dist/version.d.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  * an ESM `import` can resolve cleanly, and a hardcoded constant survives
17
17
  * any packager that drops .json files.
18
18
  */
19
- export declare const PACKAGE_VERSION = "1.52.9";
19
+ export declare const PACKAGE_VERSION = "1.53.1";
20
20
  /** Compares plain x.y.z versions, positive if a > b; tags throw so the rollback guard never misfires silently. */
21
21
  export declare function compareSemver(a: string, b: string): number;
22
22
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * an ESM `import` can resolve cleanly, and a hardcoded constant survives
17
17
  * any packager that drops .json files.
18
18
  */
19
- export const PACKAGE_VERSION = '1.52.9';
19
+ export const PACKAGE_VERSION = '1.53.1';
20
20
  /** Compares plain x.y.z versions, positive if a > b; tags throw so the rollback guard never misfires silently. */
21
21
  export function compareSemver(a, b) {
22
22
  const parse = (v) => {
@@ -2,7 +2,7 @@
2
2
  "id": "hippo-memory",
3
3
  "name": "Hippo Memory",
4
4
  "description": "Memory for AI agents that learns what is wrong and ranks it down. Injects context at session start and captures errors.",
5
- "version": "1.52.9",
5
+ "version": "1.53.1",
6
6
 
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hippo-memory",
3
- "version": "1.52.9",
3
+ "version": "1.53.1",
4
4
  "type": "module",
5
5
  "description": "Hippo Memory plugin for OpenClaw - biologically-inspired agent memory",
6
6
  "main": "index.ts",
@@ -2,7 +2,7 @@
2
2
  "id": "hippo-memory",
3
3
  "name": "Hippo Memory",
4
4
  "description": "Memory for AI agents that learns what is wrong and ranks it down. Injects context at session start and captures errors.",
5
- "version": "1.52.9",
5
+ "version": "1.53.1",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hippo-memory",
3
- "version": "1.52.9",
3
+ "version": "1.53.1",
4
4
  "description": "Memory for AI agents that learns what is wrong and ranks it down. MCP server, hooks for Claude Code, OpenCode and Codex, AGENTS.md instructions for Codex, Cursor, OpenClaw and Pi. SQLite, zero runtime deps.",
5
5
  "mcpName": "io.github.kitfunso/hippo-memory",
6
6
  "type": "module",