hippo-memory 1.65.0 → 1.67.0

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 (71) hide show
  1. package/README.md +2 -0
  2. package/dist/api/auth.d.ts +11 -2
  3. package/dist/api/auth.js +25 -2
  4. package/dist/api/recall-types.d.ts +1 -0
  5. package/dist/api/recall.js +5 -5
  6. package/dist/auth.d.ts +3 -1
  7. package/dist/auth.js +6 -2
  8. package/dist/capture/compact.d.ts +2 -0
  9. package/dist/capture/compact.js +12 -6
  10. package/dist/capture/copilot-transcript.d.ts +15 -0
  11. package/dist/capture/copilot-transcript.js +40 -0
  12. package/dist/capture/failure-reading.js +5 -2
  13. package/dist/capture/transcript.js +7 -1
  14. package/dist/capture/working-state.js +14 -3
  15. package/dist/capture-contract.d.ts +4 -2
  16. package/dist/capture-contract.js +3 -3
  17. package/dist/cli/context.js +11 -8
  18. package/dist/cli/hook-blocks.d.ts +2 -0
  19. package/dist/cli/hook-blocks.js +6 -0
  20. package/dist/cli/session-hooks.d.ts +1 -1
  21. package/dist/cli/session-hooks.js +28 -16
  22. package/dist/cli/setup.js +66 -7
  23. package/dist/cli/shared.d.ts +5 -0
  24. package/dist/cli/shared.js +22 -3
  25. package/dist/cli/usage.js +20 -9
  26. package/dist/db/open.d.ts +5 -1
  27. package/dist/db/open.js +28 -7
  28. package/dist/db/sqlite-blocked.d.ts +11 -1
  29. package/dist/db/sqlite-blocked.js +20 -2
  30. package/dist/db.d.ts +2 -2
  31. package/dist/db.js +2 -2
  32. package/dist/delivery-recorder.d.ts +3 -1
  33. package/dist/delivery-recorder.js +1 -1
  34. package/dist/embeddings.d.ts +11 -2
  35. package/dist/embeddings.js +20 -11
  36. package/dist/hooks/copilot.d.ts +45 -0
  37. package/dist/hooks/copilot.js +187 -0
  38. package/dist/hooks/json-hooks.d.ts +1 -1
  39. package/dist/hooks/json-hooks.js +41 -2
  40. package/dist/hooks/shared.d.ts +5 -1
  41. package/dist/hooks/shared.js +9 -0
  42. package/dist/mcp/request.d.ts +0 -2
  43. package/dist/mcp/request.js +16 -11
  44. package/dist/prompt-hook.d.ts +6 -0
  45. package/dist/prompt-hook.js +15 -7
  46. package/dist/quality-repair.js +3 -0
  47. package/dist/search/hybrid.d.ts +3 -0
  48. package/dist/search/physics-search.d.ts +3 -0
  49. package/dist/search/physics-search.js +18 -19
  50. package/dist/search/vector.d.ts +7 -2
  51. package/dist/search/vector.js +24 -10
  52. package/dist/server/routes/admin.js +1 -1
  53. package/dist/server/routes/recall.js +6 -9
  54. package/dist/server/types.d.ts +5 -4
  55. package/dist/server.d.ts +11 -6
  56. package/dist/server.js +18 -14
  57. package/dist/stdin.d.ts +5 -0
  58. package/dist/stdin.js +62 -0
  59. package/dist/store/key-audit.d.ts +6 -0
  60. package/dist/store/key-audit.js +25 -0
  61. package/dist/store/open.js +2 -1
  62. package/dist/store-port.d.ts +42 -4
  63. package/dist/store-port.js +46 -2
  64. package/dist/vector-store.d.ts +8 -0
  65. package/dist/vector-store.js +31 -19
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  69. package/extensions/openclaw-plugin/package.json +1 -1
  70. package/openclaw.plugin.json +1 -1
  71. package/package.json +1 -1
@@ -1,17 +1,32 @@
1
1
  // The async seam the server reaches its store through, so an add-on can serve from a database other than hippo.db.
2
2
  import { readApiKeyRecord } from './auth.js';
3
- import { appendAuditEvent } from './audit.js';
3
+ import { appendAuditEvent, listAuditEventsAfter } from './audit.js';
4
4
  import { closeHippoDb, openHippoDb, withWriteScope } from './db.js';
5
+ import { StoreNotPortedError } from './db/sqlite-blocked.js';
6
+ import { embeddingIndexStateAt, loadStoredVectors } from './embeddings.js';
5
7
  import { activeGoalsWithPolicies, localGoalRecallRows, writeGoalRecallLog, } from './goals.js';
8
+ import { loadPhysicsState } from './physics-state.js';
6
9
  import { planningFallacyEvidenceAt } from './predictions/planning-fallacy.js';
7
10
  import { writeRecallTrace } from './recall-trace.js';
8
11
  import { loadEntriesByIds, loadFreshRawMemories } from './store/entry-reads.js';
12
+ import { auditHighIdAt, revokeKeyAt } from './store/key-audit.js';
9
13
  import { strengthenRetrievedInOwnTx } from './store/entry-writes.js';
10
14
  import { loadLatestHandoff } from './store/handoffs.js';
11
15
  import { updateStats } from './store/index-and-stats.js';
12
- import { loadRecallSearchEntries } from './store/search-rows.js';
16
+ import { loadRecallSearchEntries, loadVectorCandidateEntries } from './store/search-rows.js';
13
17
  import { listSessionEvents, loadActiveTaskSnapshot } from './store/sessions.js';
14
18
  import { recordTokenUse } from './token-ledger.js';
19
+ export function hasGroup(store, group) {
20
+ return group === 'base' || store[group] !== undefined;
21
+ }
22
+ /** The group's methods; a store without them throws StoreNotPortedError, which answers 501 as any unported path does. */
23
+ export function requireGroup(store, group) {
24
+ const groups = store;
25
+ const methods = groups[group];
26
+ if (methods === undefined)
27
+ throw new StoreNotPortedError(store.kind, group);
28
+ return methods;
29
+ }
15
30
  function onHandle(hippoRoot, fn) {
16
31
  const db = openHippoDb(hippoRoot);
17
32
  try {
@@ -67,9 +82,38 @@ export function sqliteStore(hippoRoot) {
67
82
  async recordTokens(use) {
68
83
  onHandle(hippoRoot, (db) => recordTokenUse(db, use));
69
84
  },
85
+ vectors: {
86
+ async embeddingIndexState() {
87
+ return embeddingIndexStateAt(hippoRoot);
88
+ },
89
+ async storedVectors(ids) {
90
+ return loadStoredVectors(hippoRoot, ids);
91
+ },
92
+ async nearestEntries(queryVector, spec) {
93
+ return loadVectorCandidateEntries(hippoRoot, queryVector, spec);
94
+ },
95
+ async physicsParticles(ids) {
96
+ // loadPhysicsState reads every row for an empty list.
97
+ return ids.length === 0 ? new Map() : onHandle(hippoRoot, (db) => loadPhysicsState(db, [...ids]));
98
+ },
99
+ },
100
+ keyAudit: sqliteKeyAudit(hippoRoot),
70
101
  async close() { },
71
102
  };
72
103
  }
104
+ function sqliteKeyAudit(hippoRoot) {
105
+ return {
106
+ async revokeApiKey(revoke) {
107
+ return onHandle(hippoRoot, (db) => revokeKeyAt(db, revoke));
108
+ },
109
+ async auditEventsAfter(opts) {
110
+ return onHandle(hippoRoot, (db) => listAuditEventsAfter(db, opts));
111
+ },
112
+ async auditHighId() {
113
+ return onHandle(hippoRoot, auditHighIdAt);
114
+ },
115
+ };
116
+ }
73
117
  /** sqliteStore's continuity read, for the synchronous recall that cannot await the port. */
74
118
  export function continuityAt(hippoRoot, tenantId, eventLimit, key) {
75
119
  const activeSnapshot = loadActiveTaskSnapshot(hippoRoot, tenantId, key ?? undefined);
@@ -18,6 +18,14 @@ export interface VectorMatch {
18
18
  id: string;
19
19
  score: number;
20
20
  }
21
+ /** A stored vector, decoded. */
22
+ export interface VectorRow {
23
+ readonly id: string;
24
+ readonly vector: Float32Array;
25
+ }
26
+ /** The `k` rows closest to `query` by cosine, best first; ties go to the smaller id, so row order never changes the top k.
27
+ * The query rounds to Float32 as stored vectors are, and scores add up in float64, so every store that feeds it the same bytes ranks alike. */
28
+ export declare function rankVectorRows(query: readonly number[], rows: Iterable<VectorRow>, k: number): VectorMatch[];
21
29
  /** The `k` stored vectors closest to `query` among `memories m` rows passing `where` (SQL starting with ` AND`).
22
30
  * Filters run before the cut, so rows a caller may not see can never push admitted rows out of the top k. */
23
31
  export declare function topVectorMatches(db: DatabaseSyncLike, query: readonly number[], k: number, where: string, params: readonly (string | number)[]): VectorMatch[];
@@ -24,8 +24,8 @@ export function encodeVector(vector) {
24
24
  return new Uint8Array(Float32Array.from(vector).buffer);
25
25
  }
26
26
  export function decodeVector(blob) {
27
- // A view needs 4-byte alignment; copy when SQLite hands back an unaligned slice.
28
- const bytes = blob.byteOffset % 4 === 0 ? blob : blob.slice();
27
+ // A view needs 4-byte alignment; Buffer#slice is a view too, so copy with the constructor.
28
+ const bytes = blob.byteOffset % 4 === 0 ? blob : new Uint8Array(blob);
29
29
  return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
30
30
  }
31
31
  /** Insert or replace vectors, skipping empty or non-finite ones; the caller owns any surrounding transaction. */
@@ -92,28 +92,22 @@ export function storedVectorIds(db) {
92
92
  export function hasStoredVectors(db) {
93
93
  return db.prepare('SELECT 1 FROM memory_vectors LIMIT 1').get() !== undefined;
94
94
  }
95
- /** The `k` stored vectors closest to `query` among `memories m` rows passing `where` (SQL starting with ` AND`).
96
- * Filters run before the cut, so rows a caller may not see can never push admitted rows out of the top k. */
97
- export function topVectorMatches(db, query, k, where, params) {
95
+ /** The `k` rows closest to `query` by cosine, best first; ties go to the smaller id, so row order never changes the top k.
96
+ * The query rounds to Float32 as stored vectors are, and scores add up in float64, so every store that feeds it the same bytes ranks alike. */
97
+ export function rankVectorRows(query, rows, k) {
98
+ const top = [];
98
99
  if (k <= 0 || query.length === 0)
99
- return [];
100
+ return top;
100
101
  const q = Float32Array.from(query);
101
102
  let qNorm = 0;
102
103
  for (let i = 0; i < q.length; i++)
103
104
  qNorm += q[i] * q[i];
104
105
  qNorm = Math.sqrt(qNorm);
105
106
  if (qNorm < 1e-10)
106
- return [];
107
- // SHORTCUT: brute-force cosine over every vector of matching dim, fine to ~100k rows; an ANN index (sqlite-vec, HNSW) is the upgrade.
108
- const rows = db.prepare(`
109
- SELECT v.memory_id AS id, v.vector AS vector
110
- FROM memory_vectors v JOIN memories m ON m.id = v.memory_id
111
- WHERE v.dim = ?${where}
112
- `).iterate(q.length, ...params);
113
- const top = [];
114
- // SAFETY: the SELECT above names exactly these two columns; node:sqlite returns BLOBs as Uint8Array.
115
- for (const row of rows) {
116
- const v = decodeVector(row.vector);
107
+ return top;
108
+ for (const { id, vector: v } of rows) {
109
+ if (v.length !== q.length)
110
+ continue;
117
111
  let dot = 0;
118
112
  let norm = 0;
119
113
  for (let i = 0; i < v.length; i++) {
@@ -123,12 +117,30 @@ export function topVectorMatches(db, query, k, where, params) {
123
117
  if (norm < 1e-20)
124
118
  continue;
125
119
  const score = dot / (qNorm * Math.sqrt(norm));
126
- if (top.length === k && !beats(score, row.id, top[k - 1]))
120
+ if (top.length === k && !beats(score, id, top[k - 1]))
127
121
  continue;
128
- insertSorted(top, { id: row.id, score }, k);
122
+ insertSorted(top, { id, score }, k);
129
123
  }
130
124
  return top;
131
125
  }
126
+ function* decodedRows(rows) {
127
+ for (const row of rows)
128
+ yield { id: row.id, vector: decodeVector(row.vector) };
129
+ }
130
+ /** The `k` stored vectors closest to `query` among `memories m` rows passing `where` (SQL starting with ` AND`).
131
+ * Filters run before the cut, so rows a caller may not see can never push admitted rows out of the top k. */
132
+ export function topVectorMatches(db, query, k, where, params) {
133
+ if (k <= 0 || query.length === 0)
134
+ return [];
135
+ // SHORTCUT: brute-force cosine over every vector of matching dim, fine to ~100k rows; an ANN index (sqlite-vec, HNSW) is the upgrade.
136
+ const rows = db.prepare(`
137
+ SELECT v.memory_id AS id, v.vector AS vector
138
+ FROM memory_vectors v JOIN memories m ON m.id = v.memory_id
139
+ WHERE v.dim = ?${where}
140
+ `).iterate(query.length, ...params);
141
+ // SAFETY: the SELECT above names exactly these two columns; node:sqlite returns BLOBs as Uint8Array.
142
+ return rankVectorRows(query, decodedRows(rows), k);
143
+ }
132
144
  // Ties break on id so the cut is the same on every run.
133
145
  function beats(score, id, other) {
134
146
  return score > other.score || (score === other.score && id < other.id);
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.65.0";
19
+ export declare const PACKAGE_VERSION = "1.67.0";
20
20
  /** The floor a store takes on its first expiring key: the first release with schema v53, set by hand in that release; scripts/check-expiring-keys-floor.mjs gates it. */
21
21
  export declare const EXPIRING_KEYS_MIN_BINARY = "1.64.0";
22
22
  /** The floor a store takes on its first session bind or owner snapshot: first release with v54; set by hand in that release. */
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.65.0';
19
+ export const PACKAGE_VERSION = '1.67.0';
20
20
  /** The floor a store takes on its first expiring key: the first release with schema v53, set by hand in that release; scripts/check-expiring-keys-floor.mjs gates it. */
21
21
  export const EXPIRING_KEYS_MIN_BINARY = '1.64.0';
22
22
  /** The floor a store takes on its first session bind or owner snapshot: first release with v54; set by hand in that release. */
@@ -2,7 +2,7 @@
2
2
  "id": "hippo-memory",
3
3
  "name": "Hippo Memory",
4
4
  "description": "Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.",
5
- "version": "1.65.0",
5
+ "version": "1.67.0",
6
6
 
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hippo-memory",
3
- "version": "1.65.0",
3
+ "version": "1.67.0",
4
4
  "type": "module",
5
5
  "description": "Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.",
6
6
  "main": "index.ts",
@@ -2,7 +2,7 @@
2
2
  "id": "hippo-memory",
3
3
  "name": "Hippo Memory",
4
4
  "description": "Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.",
5
- "version": "1.65.0",
5
+ "version": "1.67.0",
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.65.0",
3
+ "version": "1.67.0",
4
4
  "description": "Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.",
5
5
  "mcpName": "io.github.kitfunso/hippo-memory",
6
6
  "type": "module",