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.
- package/README.md +2 -0
- package/dist/api/auth.d.ts +11 -2
- package/dist/api/auth.js +25 -2
- package/dist/api/recall-types.d.ts +1 -0
- package/dist/api/recall.js +5 -5
- package/dist/auth.d.ts +3 -1
- package/dist/auth.js +6 -2
- package/dist/capture/compact.d.ts +2 -0
- package/dist/capture/compact.js +12 -6
- package/dist/capture/copilot-transcript.d.ts +15 -0
- package/dist/capture/copilot-transcript.js +40 -0
- package/dist/capture/failure-reading.js +5 -2
- package/dist/capture/transcript.js +7 -1
- package/dist/capture/working-state.js +14 -3
- package/dist/capture-contract.d.ts +4 -2
- package/dist/capture-contract.js +3 -3
- package/dist/cli/context.js +11 -8
- package/dist/cli/hook-blocks.d.ts +2 -0
- package/dist/cli/hook-blocks.js +6 -0
- package/dist/cli/session-hooks.d.ts +1 -1
- package/dist/cli/session-hooks.js +28 -16
- package/dist/cli/setup.js +66 -7
- package/dist/cli/shared.d.ts +5 -0
- package/dist/cli/shared.js +22 -3
- package/dist/cli/usage.js +20 -9
- package/dist/db/open.d.ts +5 -1
- package/dist/db/open.js +28 -7
- package/dist/db/sqlite-blocked.d.ts +11 -1
- package/dist/db/sqlite-blocked.js +20 -2
- package/dist/db.d.ts +2 -2
- package/dist/db.js +2 -2
- package/dist/delivery-recorder.d.ts +3 -1
- package/dist/delivery-recorder.js +1 -1
- package/dist/embeddings.d.ts +11 -2
- package/dist/embeddings.js +20 -11
- package/dist/hooks/copilot.d.ts +45 -0
- package/dist/hooks/copilot.js +187 -0
- package/dist/hooks/json-hooks.d.ts +1 -1
- package/dist/hooks/json-hooks.js +41 -2
- package/dist/hooks/shared.d.ts +5 -1
- package/dist/hooks/shared.js +9 -0
- package/dist/mcp/request.d.ts +0 -2
- package/dist/mcp/request.js +16 -11
- package/dist/prompt-hook.d.ts +6 -0
- package/dist/prompt-hook.js +15 -7
- package/dist/quality-repair.js +3 -0
- package/dist/search/hybrid.d.ts +3 -0
- package/dist/search/physics-search.d.ts +3 -0
- package/dist/search/physics-search.js +18 -19
- package/dist/search/vector.d.ts +7 -2
- package/dist/search/vector.js +24 -10
- package/dist/server/routes/admin.js +1 -1
- package/dist/server/routes/recall.js +6 -9
- package/dist/server/types.d.ts +5 -4
- package/dist/server.d.ts +11 -6
- package/dist/server.js +18 -14
- package/dist/stdin.d.ts +5 -0
- package/dist/stdin.js +62 -0
- package/dist/store/key-audit.d.ts +6 -0
- package/dist/store/key-audit.js +25 -0
- package/dist/store/open.js +2 -1
- package/dist/store-port.d.ts +42 -4
- package/dist/store-port.js +46 -2
- package/dist/vector-store.d.ts +8 -0
- package/dist/vector-store.js +31 -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/store-port.js
CHANGED
|
@@ -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);
|
package/dist/vector-store.d.ts
CHANGED
|
@@ -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[];
|
package/dist/vector-store.js
CHANGED
|
@@ -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;
|
|
28
|
-
const bytes = blob.byteOffset % 4 === 0 ? blob : blob
|
|
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`
|
|
96
|
-
*
|
|
97
|
-
export function
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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,
|
|
120
|
+
if (top.length === k && !beats(score, id, top[k - 1]))
|
|
127
121
|
continue;
|
|
128
|
-
insertSorted(top, { id
|
|
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.
|
|
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.
|
|
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. */
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "1.67.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED