hippo-memory 1.66.0 → 1.68.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 +16 -11
- package/dist/api/auth.js +117 -61
- package/dist/api/types.d.ts +9 -0
- package/dist/auth.d.ts +22 -1
- package/dist/auth.js +20 -8
- 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/sqlite-blocked.js +1 -1
- package/dist/delivery-recorder.d.ts +3 -1
- package/dist/delivery-recorder.js +1 -1
- package/dist/embeddings.d.ts +10 -10
- package/dist/embeddings.js +103 -18
- 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/search/vector.js +2 -5
- package/dist/server/routes/admin.js +3 -3
- package/dist/server/types.d.ts +5 -4
- package/dist/server.d.ts +11 -8
- package/dist/server.js +20 -19
- 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/key-writes.d.ts +9 -0
- package/dist/store/key-writes.js +30 -0
- package/dist/store/vector-writes.d.ts +6 -0
- package/dist/store/vector-writes.js +59 -0
- package/dist/store-port.d.ts +101 -9
- package/dist/store-port.js +53 -2
- 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.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type ApiKeyRecord } from './auth.js';
|
|
2
|
-
import { type AppendAuditOpts } from './audit.js';
|
|
1
|
+
import { type ApiKeyListRow, type ApiKeyRecord, type ListApiKeysOpts, type NewApiKey } from './auth.js';
|
|
2
|
+
import { type AppendAuditOpts, type AuditEvent, type ListAuditAfterOpts } from './audit.js';
|
|
3
3
|
import type { ContinuityBlock } from './api/recall-types.js';
|
|
4
4
|
import { type EmbeddingIndexState } from './embeddings.js';
|
|
5
5
|
import { type ActiveGoals, type GetActiveGoalsOpts, type GoalRecallLogRow } from './goals.js';
|
|
6
|
+
import type { JsonValue } from './json.js';
|
|
6
7
|
import type { MemoryEntry } from './memory.js';
|
|
7
8
|
import type { PhysicsParticle } from './physics.js';
|
|
8
9
|
import { type PlanningFallacyEvidence } from './predictions/planning-fallacy.js';
|
|
@@ -37,7 +38,7 @@ export interface RecallWrites {
|
|
|
37
38
|
readonly opts: StrengthenOptions;
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
|
-
/** The reads behind recall's vector arm
|
|
41
|
+
/** The reads behind recall's vector arm. */
|
|
41
42
|
export interface VectorReads {
|
|
42
43
|
/** The stored model and whether any vector exists, in one snapshot. */
|
|
43
44
|
embeddingIndexState(): Promise<EmbeddingIndexState>;
|
|
@@ -48,8 +49,103 @@ export interface VectorReads {
|
|
|
48
49
|
/** The particles of `ids` only; an empty list reads nothing. */
|
|
49
50
|
physicsParticles(ids: readonly string[]): Promise<Map<string, PhysicsParticle>>;
|
|
50
51
|
}
|
|
52
|
+
/** One memory's vector, computed by the caller's embedding provider. */
|
|
53
|
+
export interface VectorRowWrite {
|
|
54
|
+
readonly memoryId: string;
|
|
55
|
+
readonly vector: readonly number[];
|
|
56
|
+
/** Kept only when the memory has no particle yet, under `memoryId` whatever its own memoryId says. */
|
|
57
|
+
readonly particle?: PhysicsParticle;
|
|
58
|
+
}
|
|
59
|
+
export interface VectorWrite {
|
|
60
|
+
readonly tenantId: string;
|
|
61
|
+
/** The index identity the vectors were built by, `embeddingIndexIdentity(provider.id)`. */
|
|
62
|
+
readonly model: string;
|
|
63
|
+
/** Set only by embedAll's rebuild, so a write that read the index state before another process rebuilt it cannot drop that rebuild. */
|
|
64
|
+
readonly replaceIndex: boolean;
|
|
65
|
+
readonly rows: readonly VectorRowWrite[];
|
|
66
|
+
}
|
|
67
|
+
export interface VectorWriteResult {
|
|
68
|
+
readonly written: number;
|
|
69
|
+
/** True when another model built the index and `replaceIndex` is false; the store then keeps nothing and `written` is 0. */
|
|
70
|
+
readonly modelMismatch: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface VectorBackfillQuery {
|
|
73
|
+
readonly model: string;
|
|
74
|
+
/** Only ids above this one in byte order; unset starts at the first. */
|
|
75
|
+
readonly afterId?: string;
|
|
76
|
+
/** Clamped to 1..500; a non-integer rejects with RangeError `limit must be an integer`. */
|
|
77
|
+
readonly limit: number;
|
|
78
|
+
/** Every tenant when unset, since the backfill covers the whole store. */
|
|
79
|
+
readonly tenantId?: string;
|
|
80
|
+
}
|
|
81
|
+
/** The writes behind embed on write and the backfill: core computes each vector, the store only keeps it.
|
|
82
|
+
* A store deletes a memory's vector and particle when the memory is deleted (hippo.db does it by trigger and foreign-key cascade). */
|
|
83
|
+
export interface VectorWrites {
|
|
84
|
+
/** Memories of any kind with no vector stored under `query.model`, by id ascending in byte order. */
|
|
85
|
+
entriesWithoutVector(query: VectorBackfillQuery): Promise<MemoryEntry[]>;
|
|
86
|
+
/** In one transaction: refuses with `modelMismatch` if `replacesIndex` and not `replaceIndex`; else, when a row is writable (memory in `tenantId`, vector non-empty
|
|
87
|
+
* and finite), drops every vector and particle if `replacesIndex`, keeps each writable row under `model` with its particle where none exists, sets `model`. No audit row. */
|
|
88
|
+
writeVectors(write: VectorWrite): Promise<VectorWriteResult>;
|
|
89
|
+
}
|
|
90
|
+
export interface KeyRevoke {
|
|
91
|
+
readonly tenantId: string;
|
|
92
|
+
readonly keyId: string;
|
|
93
|
+
readonly actor: string;
|
|
94
|
+
readonly at: string;
|
|
95
|
+
}
|
|
96
|
+
export interface KeyAudit {
|
|
97
|
+
/** Sets revoked_at to `at` and appends one auth_revoke row (the key's tenant, `actor`, the key id) in one transaction, then resolves to `at`. A revoked
|
|
98
|
+
* key resolves to its own revoked_at and writes nothing; a key missing from `tenantId` rejects with NotFoundError `Unknown key_id: <keyId>`. */
|
|
99
|
+
revokeApiKey(revoke: KeyRevoke): Promise<string>;
|
|
100
|
+
/** As `listAuditEventsAfter`: rows above `afterId` in ascending id order, with its RangeErrors, limit clamp and tenant filter. */
|
|
101
|
+
auditEventsAfter(opts: ListAuditAfterOpts): Promise<AuditEvent[]>;
|
|
102
|
+
/** The highest audit id ever assigned, pruned rows included, 0 before the first; a tail that sees it drop knows the log was restored. */
|
|
103
|
+
auditHighId(): Promise<number>;
|
|
104
|
+
}
|
|
105
|
+
/** A key to insert and the actor and metadata of its auth_create row, whose tenant and target are the key's. Neither holds the plaintext. */
|
|
106
|
+
export interface KeyMint {
|
|
107
|
+
readonly key: NewApiKey;
|
|
108
|
+
readonly actor: string;
|
|
109
|
+
readonly metadata: Readonly<Record<string, JsonValue>>;
|
|
110
|
+
}
|
|
111
|
+
export interface SelfKeyMint extends KeyMint {
|
|
112
|
+
readonly key: NewApiKey & {
|
|
113
|
+
readonly ownerSubject: string;
|
|
114
|
+
readonly expiresAt: string;
|
|
115
|
+
};
|
|
116
|
+
/** Live keys the owner may hold in the tenant once this one is in. */
|
|
117
|
+
readonly perSubject: number;
|
|
118
|
+
}
|
|
119
|
+
/** `listApiKeyRows`'s filters, always inside one tenant. */
|
|
120
|
+
export interface KeyListQuery extends Omit<ListApiKeysOpts, 'tenantId'> {
|
|
121
|
+
readonly tenantId: string;
|
|
122
|
+
}
|
|
123
|
+
export interface KeyWrites {
|
|
124
|
+
/** Inserts the key and appends its auth_create row in one transaction; with either one failing, neither is written. A key id taken in any tenant
|
|
125
|
+
* rejects, since keys are looked up by id alone. */
|
|
126
|
+
createApiKey(mint: KeyMint): Promise<void>;
|
|
127
|
+
/** One transaction, serialized per (tenant, owner) before the count (hippo.db BEGIN IMMEDIATE, Postgres pg_advisory_xact_lock on the pair): revoke at `key.createdAt`
|
|
128
|
+
* the owner's oldest live keys in the key's tenant down to `perSubject - 1`, insert the key, append an auth_revoke row `{ replacedBy: <new key id> }` per revoked key,
|
|
129
|
+
* then the auth_create row; resolves to the revoked ids. Live: unrevoked, expires_at null or Date.parse(expires_at) after createdAt (NaN counts as expired). */
|
|
130
|
+
createSelfApiKey(mint: SelfKeyMint): Promise<string[]>;
|
|
131
|
+
/** As `listApiKeyRows`: newest inserted first, each with its scope grants; `active` drops revoked rows and an expires_at not above now's toISOString. */
|
|
132
|
+
listApiKeys(query: KeyListQuery): Promise<ApiKeyListRow[]>;
|
|
133
|
+
}
|
|
134
|
+
/** The optional groups: a store sets each one whole or leaves it unset, and a route or MCP tool names the one it needs. */
|
|
135
|
+
export interface StoreGroups {
|
|
136
|
+
/** Unset on a store built before them, where hybrid and physics recall under an embedding provider answer 501. */
|
|
137
|
+
readonly vectors: VectorReads;
|
|
138
|
+
readonly keyAudit: KeyAudit;
|
|
139
|
+
readonly keyWrites: KeyWrites;
|
|
140
|
+
/** embedMemory and embedAll with a store need it and `vectors`. */
|
|
141
|
+
readonly vectorWrites: VectorWrites;
|
|
142
|
+
}
|
|
143
|
+
export type StoreGroup = 'base' | keyof StoreGroups;
|
|
144
|
+
export declare function hasGroup(store: HippoStore, group: StoreGroup): boolean;
|
|
145
|
+
/** The group's methods; a store without them throws StoreNotPortedError, which answers 501 as any unported path does. */
|
|
146
|
+
export declare function requireGroup<G extends keyof StoreGroups>(store: HippoStore, group: G): StoreGroups[G];
|
|
51
147
|
/** What `serve()` reads and writes through. Each method is atomic and no transaction spans an await, since SQLite's lock wait blocks the event loop; a lock timeout throws `StoreBusyError`. */
|
|
52
|
-
export interface HippoStore {
|
|
148
|
+
export interface HippoStore extends Partial<StoreGroups> {
|
|
53
149
|
/** 'sqlite' is hippo.db under the served root. Under any other kind, an unported route answers 501 and a hippo.db open inside a request throws. */
|
|
54
150
|
readonly kind: string;
|
|
55
151
|
/** The api_keys row for `keyId` with its scope grants, revoked or not; null when no row matches. */
|
|
@@ -78,8 +174,6 @@ export interface HippoStore {
|
|
|
78
174
|
bumpRecallStats(recalled: number): Promise<void>;
|
|
79
175
|
/** One token-ledger row. Throws, so the caller decides whether a ledger failure matters. */
|
|
80
176
|
recordTokens(use: TokenUse): Promise<void>;
|
|
81
|
-
/** Unset on a store built before them, where hybrid and physics recall under an embedding provider answer 501. */
|
|
82
|
-
readonly vectors?: VectorReads;
|
|
83
177
|
/** Releases the store's connections; `serve()` closes only a store it made itself. */
|
|
84
178
|
close(): Promise<void>;
|
|
85
179
|
}
|
|
@@ -89,9 +183,7 @@ export declare function storeFor(ctx: {
|
|
|
89
183
|
readonly store?: HippoStore;
|
|
90
184
|
}): HippoStore;
|
|
91
185
|
/** The built-in store: today's synchronous hippo.db functions behind the port, each call on its own short-lived handles, so close has nothing to release. */
|
|
92
|
-
export declare function sqliteStore(hippoRoot: string): HippoStore &
|
|
93
|
-
readonly vectors: VectorReads;
|
|
94
|
-
};
|
|
186
|
+
export declare function sqliteStore(hippoRoot: string): HippoStore & StoreGroups;
|
|
95
187
|
/** sqliteStore's continuity read, for the synchronous recall that cannot await the port. */
|
|
96
188
|
export declare function continuityAt(hippoRoot: string, tenantId: string, eventLimit: number, key: ContinuityKey | null): ContinuityBlock;
|
|
97
189
|
/** sqliteStore's finishRecall, for the synchronous recall that cannot await the port. */
|
package/dist/store-port.js
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
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
|
-
import { readApiKeyRecord } from './auth.js';
|
|
3
|
-
import { appendAuditEvent } from './audit.js';
|
|
2
|
+
import { listApiKeyRows, readApiKeyRecord } from './auth.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';
|
|
5
6
|
import { embeddingIndexStateAt, loadStoredVectors } from './embeddings.js';
|
|
6
7
|
import { activeGoalsWithPolicies, localGoalRecallRows, writeGoalRecallLog, } from './goals.js';
|
|
7
8
|
import { loadPhysicsState } from './physics-state.js';
|
|
8
9
|
import { planningFallacyEvidenceAt } from './predictions/planning-fallacy.js';
|
|
9
10
|
import { writeRecallTrace } from './recall-trace.js';
|
|
10
11
|
import { loadEntriesByIds, loadFreshRawMemories } from './store/entry-reads.js';
|
|
12
|
+
import { auditHighIdAt, revokeKeyAt } from './store/key-audit.js';
|
|
13
|
+
import { createKeyAt, createSelfKeyAt } from './store/key-writes.js';
|
|
11
14
|
import { strengthenRetrievedInOwnTx } from './store/entry-writes.js';
|
|
12
15
|
import { loadLatestHandoff } from './store/handoffs.js';
|
|
13
16
|
import { updateStats } from './store/index-and-stats.js';
|
|
14
17
|
import { loadRecallSearchEntries, loadVectorCandidateEntries } from './store/search-rows.js';
|
|
15
18
|
import { listSessionEvents, loadActiveTaskSnapshot } from './store/sessions.js';
|
|
19
|
+
import { entriesWithoutVectorAt, writeVectorsAt } from './store/vector-writes.js';
|
|
16
20
|
import { recordTokenUse } from './token-ledger.js';
|
|
21
|
+
export function hasGroup(store, group) {
|
|
22
|
+
return group === 'base' || store[group] !== undefined;
|
|
23
|
+
}
|
|
24
|
+
/** The group's methods; a store without them throws StoreNotPortedError, which answers 501 as any unported path does. */
|
|
25
|
+
export function requireGroup(store, group) {
|
|
26
|
+
const groups = store;
|
|
27
|
+
const methods = groups[group];
|
|
28
|
+
if (methods === undefined)
|
|
29
|
+
throw new StoreNotPortedError(store.kind, group);
|
|
30
|
+
return methods;
|
|
31
|
+
}
|
|
17
32
|
function onHandle(hippoRoot, fn) {
|
|
18
33
|
const db = openHippoDb(hippoRoot);
|
|
19
34
|
try {
|
|
@@ -84,9 +99,45 @@ export function sqliteStore(hippoRoot) {
|
|
|
84
99
|
return ids.length === 0 ? new Map() : onHandle(hippoRoot, (db) => loadPhysicsState(db, [...ids]));
|
|
85
100
|
},
|
|
86
101
|
},
|
|
102
|
+
keyAudit: sqliteKeyAudit(hippoRoot),
|
|
103
|
+
keyWrites: sqliteKeyWrites(hippoRoot),
|
|
104
|
+
vectorWrites: {
|
|
105
|
+
async entriesWithoutVector(query) {
|
|
106
|
+
return onHandle(hippoRoot, (db) => entriesWithoutVectorAt(db, query));
|
|
107
|
+
},
|
|
108
|
+
async writeVectors(write) {
|
|
109
|
+
return onHandle(hippoRoot, (db) => writeVectorsAt(db, write));
|
|
110
|
+
},
|
|
111
|
+
},
|
|
87
112
|
async close() { },
|
|
88
113
|
};
|
|
89
114
|
}
|
|
115
|
+
function sqliteKeyWrites(hippoRoot) {
|
|
116
|
+
return {
|
|
117
|
+
async createApiKey(mint) {
|
|
118
|
+
onHandle(hippoRoot, (db) => createKeyAt(db, mint));
|
|
119
|
+
},
|
|
120
|
+
async createSelfApiKey(mint) {
|
|
121
|
+
return onHandle(hippoRoot, (db) => createSelfKeyAt(db, mint));
|
|
122
|
+
},
|
|
123
|
+
async listApiKeys(query) {
|
|
124
|
+
return onHandle(hippoRoot, (db) => listApiKeyRows(db, query));
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function sqliteKeyAudit(hippoRoot) {
|
|
129
|
+
return {
|
|
130
|
+
async revokeApiKey(revoke) {
|
|
131
|
+
return onHandle(hippoRoot, (db) => revokeKeyAt(db, revoke));
|
|
132
|
+
},
|
|
133
|
+
async auditEventsAfter(opts) {
|
|
134
|
+
return onHandle(hippoRoot, (db) => listAuditEventsAfter(db, opts));
|
|
135
|
+
},
|
|
136
|
+
async auditHighId() {
|
|
137
|
+
return onHandle(hippoRoot, auditHighIdAt);
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
90
141
|
/** sqliteStore's continuity read, for the synchronous recall that cannot await the port. */
|
|
91
142
|
export function continuityAt(hippoRoot, tenantId, eventLimit, key) {
|
|
92
143
|
const activeSnapshot = loadActiveTaskSnapshot(hippoRoot, tenantId, key ?? undefined);
|
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.68.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.68.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.68.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED