hippo-memory 1.63.0 → 1.63.2
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 +4 -2
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/usage.js +1 -1
- package/dist/consolidate/run.d.ts +2 -0
- package/dist/consolidate/run.js +18 -1
- package/dist/consolidate/sleep.js +2 -1
- package/dist/db/continuity.d.ts +5 -0
- package/dist/db/continuity.js +41 -23
- package/dist/db/meta.d.ts +1 -0
- package/dist/db/meta.js +4 -3
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.js +110 -52
- package/dist/db.d.ts +1 -1
- package/dist/db.js +1 -1
- package/dist/doctor.js +21 -1
- package/dist/raw-archive.js +3 -13
- package/dist/store/open.js +3 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/README.md +3 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +2 -2
- package/extensions/openclaw-plugin/package.json +3 -3
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# 🦛 Hippo
|
|
1
|
+
# 🦛 Hippo
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Stop re-teaching your agent.**
|
|
4
|
+
|
|
5
|
+
Make your agent's memory work like a brain. Hippo is long-term memory for coding agents. It's a critical layer for your AI harness that connects across your different tools (Cursor, Claude Code, Codex). It keeps your proprietary data completely local, and it actually learns over time. By strengthening memories each time they are recalled, Hippo preserves what works, lets mistakes decay, and continuously compounds your agents' intelligence.
|
|
4
6
|
|
|
5
7
|
[](https://npmjs.com/package/hippo-memory)
|
|
6
8
|
[](https://npmjs.com/package/hippo-memory)
|
package/dist/cli/usage.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare function printAuditPruneUsage(): void;
|
|
2
2
|
export declare function printSlackBackfillUsage(): void;
|
|
3
3
|
export declare function printSlackWorkspacesUsage(): void;
|
|
4
|
-
export declare const USAGE_HEADER = "\nHippo - memory
|
|
4
|
+
export declare const USAGE_HEADER = "\nHippo - Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.\n\nUsage: hippo <command> [options]\n\nCommands:";
|
|
5
5
|
export declare const USAGE_EXAMPLES = "\n\nExamples:\n hippo init\n hippo remember \"FRED cache can silently drop series\" --tag error\n hippo recall \"data pipeline issues\" --budget 2000\n hippo context --auto --budget 1500\n hippo conflicts\n hippo reject mem_abc123 --reason \"leaked credential\"\n hippo reject --value \"never store my key again\" --reason \"secret\"\n hippo rejections\n hippo unreject a1b2c3d4e5f6\n hippo dormant \"staging hostname\"\n hippo dormant restore mem_abc123\n hippo tokens --days 7\n hippo session log --id sess_123 --task \"Ship feature\" --type progress --content \"Build is green, next step is docs\"\n hippo session latest --json\n hippo session resume\n hippo snapshot save --task \"Ship feature\" --summary \"Tests are green\" --next-step \"Open the PR\" --session sess_123\n hippo handoff create --summary \"PR is open, tests green\" --next \"Merge after review\" --session sess_123 --artifact src/foo.ts\n hippo handoff create --summary s --constraint a --constraint b --outcome partial --target-runtime codex --card-id c1 --tests pass\n hippo card create --title \"Add cards table\" --repo hippo --depends-on card_abc\n hippo card claim card_abc --runtime codex\n hippo card complete card_abc --outcome success\n hippo card heartbeat card_abc --run 7\n hippo card reclaim\n hippo embed --status\n hippo watch \"npm run build\"\n hippo learn --git --days 30\n hippo promote mem_abc123\n hippo sync\n hippo setup\n hippo hook install claude-code\n hippo decide \"Use PostgreSQL for new services\" --context \"JSONB support\"\n hippo incident \"Prod outage: DB connection pool exhausted\" --context \"spike at 14:00\"\n hippo process new \"Release\" --step \"run tests\" --step \"bump version\" --step \"publish\"\n hippo policy new \"Data retention\" --text \"Delete logs after 90 days\" --from 2026-01-01\n hippo policy asof 2026-03-01 --name \"Data retention\"\n hippo skill new \"Run tests\" --instructions \"npm test before every commit\" --trigger \"before commit\"\n hippo skill export\n hippo brief new \"hippo\" --summary \"Agent-memory library; E2 first-class objects in progress\"\n hippo brief refresh \"hippo\"\n hippo note new \"Acme Corp\" --text \"Renewal call: wants SSO before Q3; champion is the VP Eng\"\n hippo note list --customer \"Acme Corp\" --status active\n hippo graph extract\n hippo invalidate \"REST API\" --dry-run\n hippo invalidate \"REST API\" --reason \"migrated to GraphQL\"\n hippo invalidate --id mem_a1b2c3d4e5f6 --reason \"superseded by new policy\"\n hippo invalidate --churn --dry-run\n hippo export memories.json\n hippo export --format markdown memories.md\n hippo sleep --dry-run\n hippo outcome --good\n hippo status\n";
|
|
6
6
|
/** Each verb's help blocks for the command table, keyed like COMMANDS; a block opens with a newline. */
|
|
7
7
|
export declare const VERB_USAGE: {
|
package/dist/cli/usage.js
CHANGED
|
@@ -21,7 +21,7 @@ export function printSlackWorkspacesUsage() {
|
|
|
21
21
|
console.log(' remove --team <T> Remove a workspace registration');
|
|
22
22
|
}
|
|
23
23
|
export const USAGE_HEADER = `
|
|
24
|
-
Hippo - memory
|
|
24
|
+
Hippo - Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.
|
|
25
25
|
|
|
26
26
|
Usage: hippo <command> [options]
|
|
27
27
|
|
|
@@ -41,6 +41,8 @@ interface LazyDb {
|
|
|
41
41
|
close: () => void;
|
|
42
42
|
}
|
|
43
43
|
export declare function lazyConsolidateDb(hippoRoot: string, dryRun: boolean): LazyDb;
|
|
44
|
+
/** Re-syncs the full-text index with `memories`; a store open that is already current no longer counts the two. */
|
|
45
|
+
export declare function syncFtsIndex(hippoRoot: string, dryRun: boolean, result: ConsolidationResult): void;
|
|
44
46
|
/** State every sleep stage reads or appends to; the pending lists are flushed in one transaction at the end. */
|
|
45
47
|
export interface SleepRun {
|
|
46
48
|
hippoRoot: string;
|
package/dist/consolidate/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { openHippoDb, closeHippoDb } from '../db.js';
|
|
1
|
+
import { openHippoDb, closeHippoDb, ftsRowCounts, isFtsAvailable, repairFtsDrift } from '../db.js';
|
|
2
2
|
import { NO_MERGE_TAGS } from '../shared.js';
|
|
3
3
|
export const REPLAY_COUNT_DEFAULT = 5;
|
|
4
4
|
export function keptAsWritten(entry) {
|
|
@@ -25,6 +25,23 @@ export function lazyConsolidateDb(hippoRoot, dryRun) {
|
|
|
25
25
|
};
|
|
26
26
|
return { get, close };
|
|
27
27
|
}
|
|
28
|
+
/** Re-syncs the full-text index with `memories`; a store open that is already current no longer counts the two. */
|
|
29
|
+
export function syncFtsIndex(hippoRoot, dryRun, result) {
|
|
30
|
+
const db = openHippoDb(hippoRoot);
|
|
31
|
+
try {
|
|
32
|
+
if (!isFtsAvailable(db))
|
|
33
|
+
return;
|
|
34
|
+
const counts = ftsRowCounts(db);
|
|
35
|
+
if (counts === null || counts.memories === counts.fts)
|
|
36
|
+
return;
|
|
37
|
+
if (!dryRun)
|
|
38
|
+
repairFtsDrift(db);
|
|
39
|
+
result.details.push(` 🔎 ${dryRun ? 'would re-sync' : 're-synced'} the full-text index (${counts.fts} indexed rows for ${counts.memories} memories)`);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
closeHippoDb(db);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
28
45
|
export function newConsolidationResult(dryRun) {
|
|
29
46
|
return {
|
|
30
47
|
decayed: 0,
|
|
@@ -21,7 +21,7 @@ import { log } from '../log.js';
|
|
|
21
21
|
import { decayPass } from './decay.js';
|
|
22
22
|
import { retireHeldTexts, mergePass } from './merge.js';
|
|
23
23
|
import { detectConflicts } from './conflicts.js';
|
|
24
|
-
import { lazyConsolidateDb, newConsolidationResult } from './run.js';
|
|
24
|
+
import { lazyConsolidateDb, newConsolidationResult, syncFtsIndex } from './run.js';
|
|
25
25
|
import { promoteSessionTraces, replayPass } from './traces.js';
|
|
26
26
|
import { llmPasses } from './llm-passes.js';
|
|
27
27
|
import { physicsPass } from './physics-pass.js';
|
|
@@ -33,6 +33,7 @@ export async function consolidate(hippoRoot, options = {}) {
|
|
|
33
33
|
const dryRun = options.dryRun ?? false;
|
|
34
34
|
const result = newConsolidationResult(dryRun);
|
|
35
35
|
const halfLife = migrateHalfLives(hippoRoot, dryRun, result);
|
|
36
|
+
syncFtsIndex(hippoRoot, dryRun, result);
|
|
36
37
|
// Host-wide by design: per-tenant filtering would mean N runs per host and no cross-tenant dedup.
|
|
37
38
|
// The api.sleep audit row tags this with the admin synthetic actor.
|
|
38
39
|
const all = loadAllEntries(hippoRoot);
|
package/dist/db/continuity.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { DatabaseSyncLike } from './sqlite.js';
|
|
2
2
|
export declare const MEMORY_QUARANTINE_DDL = "\n CREATE TABLE IF NOT EXISTS memory_quarantine (\n tenant_id TEXT NOT NULL DEFAULT 'default',\n memory_id TEXT NOT NULL,\n original_scope TEXT,\n reason TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','approved','rejected')),\n quarantined_at TEXT NOT NULL,\n decided_at TEXT,\n decided_by TEXT,\n PRIMARY KEY (tenant_id, memory_id)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_memory_quarantine_status\n ON memory_quarantine(tenant_id, status, quarantined_at DESC);\n";
|
|
3
3
|
export declare function ensureContinuityTables(db: DatabaseSyncLike): void;
|
|
4
|
+
export declare const MEMORIES_FTS_DDL = "CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(id UNINDEXED, content, tags)";
|
|
5
|
+
/** Names of every table and index a `CREATE ... IF NOT EXISTS` in `ddl` makes. */
|
|
6
|
+
export declare function createdObjectNames(ddl: string): string[];
|
|
7
|
+
/** Every object runMigrations re-asserts on a stamped store; the open fast path requires all of them. */
|
|
8
|
+
export declare const REQUIRED_SCHEMA_OBJECTS: readonly string[];
|
|
4
9
|
export declare function ensureContinuityIndexes(db: DatabaseSyncLike): void;
|
|
5
10
|
//# sourceMappingURL=continuity.d.ts.map
|
package/dist/db/continuity.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { META_TABLE_DDL } from './meta.js';
|
|
1
2
|
// Shared by migration v48 and the stamped-store self-heal below.
|
|
2
3
|
export const MEMORY_QUARANTINE_DDL = `
|
|
3
4
|
CREATE TABLE IF NOT EXISTS memory_quarantine (
|
|
@@ -115,31 +116,48 @@ const CARD_COMMENTS_DDL = `
|
|
|
115
116
|
// Before the loop on stamped stores: a table lost after its migration stamped
|
|
116
117
|
// is never re-migrated, and v4/v16/v22 ALTER or read it. Fresh stores use the chain.
|
|
117
118
|
export function ensureContinuityTables(db) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
db.exec(SESSION_HANDOFFS_DDL);
|
|
121
|
-
db.exec(CARDS_DDL);
|
|
122
|
-
db.exec(CARD_DEPS_DDL);
|
|
123
|
-
db.exec(CARD_RUNS_DDL);
|
|
124
|
-
db.exec(CARD_COMMENTS_DDL);
|
|
125
|
-
db.exec(MEMORY_QUARANTINE_DDL);
|
|
119
|
+
for (const ddl of CONTINUITY_TABLE_DDL)
|
|
120
|
+
db.exec(ddl);
|
|
126
121
|
}
|
|
122
|
+
const CONTINUITY_TABLE_DDL = [
|
|
123
|
+
TASK_SNAPSHOTS_DDL,
|
|
124
|
+
SESSION_EVENTS_DDL,
|
|
125
|
+
SESSION_HANDOFFS_DDL,
|
|
126
|
+
CARDS_DDL,
|
|
127
|
+
CARD_DEPS_DDL,
|
|
128
|
+
CARD_RUNS_DDL,
|
|
129
|
+
CARD_COMMENTS_DDL,
|
|
130
|
+
MEMORY_QUARANTINE_DDL,
|
|
131
|
+
];
|
|
132
|
+
const CONTINUITY_INDEX_DDL = [
|
|
133
|
+
`CREATE INDEX IF NOT EXISTS idx_task_snapshots_status_updated ON task_snapshots(status, updated_at DESC, id DESC)`,
|
|
134
|
+
`CREATE INDEX IF NOT EXISTS idx_task_snapshots_tenant_status ON task_snapshots(tenant_id, status, updated_at DESC)`,
|
|
135
|
+
`CREATE INDEX IF NOT EXISTS idx_task_snapshots_tenant_scope ON task_snapshots(tenant_id, scope, status)`,
|
|
136
|
+
`CREATE INDEX IF NOT EXISTS idx_session_events_session_created ON session_events(session_id, created_at DESC, id DESC)`,
|
|
137
|
+
`CREATE INDEX IF NOT EXISTS idx_session_events_task_created ON session_events(task, created_at DESC, id DESC)`,
|
|
138
|
+
`CREATE INDEX IF NOT EXISTS idx_session_events_tenant_session ON session_events(tenant_id, session_id, created_at DESC, id DESC)`,
|
|
139
|
+
`CREATE INDEX IF NOT EXISTS idx_session_handoffs_session ON session_handoffs(session_id, created_at DESC)`,
|
|
140
|
+
`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_session ON session_handoffs(tenant_id, session_id, created_at DESC)`,
|
|
141
|
+
`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_outcome ON session_handoffs(tenant_id, outcome, created_at DESC)`,
|
|
142
|
+
`CREATE INDEX IF NOT EXISTS idx_cards_tenant_status ON cards(tenant_id, status, updated_at DESC)`,
|
|
143
|
+
`CREATE INDEX IF NOT EXISTS idx_card_deps_tenant_child ON card_deps(tenant_id, child)`,
|
|
144
|
+
`CREATE INDEX IF NOT EXISTS idx_card_deps_tenant_parent ON card_deps(tenant_id, parent)`,
|
|
145
|
+
`CREATE INDEX IF NOT EXISTS idx_card_runs_tenant_card ON card_runs(tenant_id, card, started DESC)`,
|
|
146
|
+
`CREATE INDEX IF NOT EXISTS idx_card_comments_tenant_card ON card_comments(tenant_id, card_id, created_at DESC)`,
|
|
147
|
+
`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_card ON session_handoffs(tenant_id, card_id, created_at DESC)`,
|
|
148
|
+
];
|
|
149
|
+
// Lives with the other re-asserted DDL so the required-object list below derives from one place.
|
|
150
|
+
export const MEMORIES_FTS_DDL = `CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(id UNINDEXED, content, tags)`;
|
|
151
|
+
const CREATED_NAME = /CREATE\s+(?:VIRTUAL\s+)?(?:TABLE|INDEX)\s+IF\s+NOT\s+EXISTS\s+(\w+)/gi;
|
|
152
|
+
/** Names of every table and index a `CREATE ... IF NOT EXISTS` in `ddl` makes. */
|
|
153
|
+
export function createdObjectNames(ddl) {
|
|
154
|
+
return [...ddl.matchAll(CREATED_NAME)].map((m) => m[1]);
|
|
155
|
+
}
|
|
156
|
+
/** Every object runMigrations re-asserts on a stamped store; the open fast path requires all of them. */
|
|
157
|
+
export const REQUIRED_SCHEMA_OBJECTS = Object.freeze([META_TABLE_DDL, ...CONTINUITY_TABLE_DDL, ...CONTINUITY_INDEX_DDL, MEMORIES_FTS_DDL].flatMap(createdObjectNames));
|
|
127
158
|
// After the loop: tenant_id (v16) and scope (v23) do not exist yet on a genuine old store.
|
|
128
159
|
export function ensureContinuityIndexes(db) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_task_snapshots_tenant_scope ON task_snapshots(tenant_id, scope, status)`);
|
|
132
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_session_events_session_created ON session_events(session_id, created_at DESC, id DESC)`);
|
|
133
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_session_events_task_created ON session_events(task, created_at DESC, id DESC)`);
|
|
134
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_session_events_tenant_session ON session_events(tenant_id, session_id, created_at DESC, id DESC)`);
|
|
135
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_session_handoffs_session ON session_handoffs(session_id, created_at DESC)`);
|
|
136
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_session ON session_handoffs(tenant_id, session_id, created_at DESC)`);
|
|
137
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_outcome ON session_handoffs(tenant_id, outcome, created_at DESC)`);
|
|
138
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_cards_tenant_status ON cards(tenant_id, status, updated_at DESC)`);
|
|
139
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_card_deps_tenant_child ON card_deps(tenant_id, child)`);
|
|
140
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_card_deps_tenant_parent ON card_deps(tenant_id, parent)`);
|
|
141
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_card_runs_tenant_card ON card_runs(tenant_id, card, started DESC)`);
|
|
142
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_card_comments_tenant_card ON card_comments(tenant_id, card_id, created_at DESC)`);
|
|
143
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_card ON session_handoffs(tenant_id, card_id, created_at DESC)`);
|
|
160
|
+
for (const ddl of CONTINUITY_INDEX_DDL)
|
|
161
|
+
db.exec(ddl);
|
|
144
162
|
}
|
|
145
163
|
//# sourceMappingURL=continuity.js.map
|
package/dist/db/meta.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DatabaseSyncLike } from './sqlite.js';
|
|
2
|
+
export declare const META_TABLE_DDL = "\n CREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n ";
|
|
2
3
|
export declare function ensureMetaTable(db: DatabaseSyncLike): void;
|
|
3
4
|
export declare function getSchemaVersion(db: DatabaseSyncLike): number;
|
|
4
5
|
export declare function setSchemaVersion(db: DatabaseSyncLike, version: number): void;
|
package/dist/db/meta.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { tableExists } from './tables.js';
|
|
2
|
-
export
|
|
3
|
-
db.exec(`
|
|
2
|
+
export const META_TABLE_DDL = `
|
|
4
3
|
CREATE TABLE IF NOT EXISTS meta (
|
|
5
4
|
key TEXT PRIMARY KEY,
|
|
6
5
|
value TEXT NOT NULL
|
|
7
6
|
);
|
|
8
|
-
|
|
7
|
+
`;
|
|
8
|
+
export function ensureMetaTable(db) {
|
|
9
|
+
db.exec(META_TABLE_DDL);
|
|
9
10
|
}
|
|
10
11
|
export function getSchemaVersion(db) {
|
|
11
12
|
if (!tableExists(db, 'meta'))
|
package/dist/db/migrate.d.ts
CHANGED
|
@@ -5,5 +5,14 @@ export declare class IncompatibleBinaryError extends Error {
|
|
|
5
5
|
}
|
|
6
6
|
/** Refuse a store stamped for a newer binary. Fails closed: runMigrations creates meta first, so a failed read is a real error. */
|
|
7
7
|
export declare function assertBinaryCompatible(db: DatabaseSyncLike): void;
|
|
8
|
+
/** The fast path's single probe: versions, the binary minimum, the FTS flag, and counts of the meta defaults and required objects. */
|
|
9
|
+
export declare const SCHEMA_PROBE_SQL: string;
|
|
8
10
|
export declare function runMigrations(db: DatabaseSyncLike, hippoRoot?: string, busyWaitMs?: number): void;
|
|
11
|
+
/** Row counts of `memories` and its full-text index, which differ once the index drifts; null when either table is missing. */
|
|
12
|
+
export declare function ftsRowCounts(db: DatabaseSyncLike): {
|
|
13
|
+
memories: number;
|
|
14
|
+
fts: number;
|
|
15
|
+
} | null;
|
|
16
|
+
/** Re-syncs `memories_fts` with `memories` when their counts differ and returns whether it did; sleep runs it because a fast open does not. */
|
|
17
|
+
export declare function repairFtsDrift(db: DatabaseSyncLike): boolean;
|
|
9
18
|
//# sourceMappingURL=migrate.d.ts.map
|
package/dist/db/migrate.js
CHANGED
|
@@ -4,7 +4,7 @@ import { importLegacyEmbeddingIndex } from '../vector-store.js';
|
|
|
4
4
|
import { execWithBusyRetry } from './busy.js';
|
|
5
5
|
import { tableExists } from './tables.js';
|
|
6
6
|
import { ensureMetaTable, getMeta, getSchemaVersion, setMeta, setSchemaVersion } from './meta.js';
|
|
7
|
-
import { ensureContinuityIndexes, ensureContinuityTables } from './continuity.js';
|
|
7
|
+
import { MEMORIES_FTS_DDL, REQUIRED_SCHEMA_OBJECTS, ensureContinuityIndexes, ensureContinuityTables } from './continuity.js';
|
|
8
8
|
import { CURRENT_SCHEMA_VERSION, MIGRATIONS } from './migrations/index.js';
|
|
9
9
|
export function getCurrentSchemaVersion() {
|
|
10
10
|
return CURRENT_SCHEMA_VERSION;
|
|
@@ -12,15 +12,64 @@ export function getCurrentSchemaVersion() {
|
|
|
12
12
|
/** Thrown by {@link assertBinaryCompatible}; doctor uses it to pick the upgrade fix over a generic permissions fix. */
|
|
13
13
|
export class IncompatibleBinaryError extends Error {
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
export function assertBinaryCompatible(db) {
|
|
17
|
-
const minRequired = getMeta(db, 'min_compatible_binary');
|
|
15
|
+
function assertMinBinary(minRequired) {
|
|
18
16
|
if (minRequired && compareSemver(minRequired, PACKAGE_VERSION) > 0) {
|
|
19
17
|
throw new IncompatibleBinaryError(`hippo-memory: this database requires hippo-memory >= ${minRequired}, but the running binary is ${PACKAGE_VERSION}. ` +
|
|
20
18
|
`Upgrade hippo-memory to open it; an older binary does not know this schema and could expose private rows or damage the store.`);
|
|
21
19
|
}
|
|
22
20
|
}
|
|
21
|
+
/** Refuse a store stamped for a newer binary. Fails closed: runMigrations creates meta first, so a failed read is a real error. */
|
|
22
|
+
export function assertBinaryCompatible(db) {
|
|
23
|
+
assertMinBinary(getMeta(db, 'min_compatible_binary'));
|
|
24
|
+
}
|
|
25
|
+
const META_DEFAULTS = [
|
|
26
|
+
['schema_version', String(CURRENT_SCHEMA_VERSION)],
|
|
27
|
+
['last_retrieval_ids', '[]'],
|
|
28
|
+
['last_trace_id', ''],
|
|
29
|
+
['total_remembered', '0'],
|
|
30
|
+
['total_recalled', '0'],
|
|
31
|
+
['total_forgotten', '0'],
|
|
32
|
+
['fts5_available', '0'],
|
|
33
|
+
];
|
|
34
|
+
// Names come from our own constants and match \w+, so inlining them as literals is safe.
|
|
35
|
+
const sqlList = (names) => names.map((n) => `'${n}'`).join(', ');
|
|
36
|
+
/** The fast path's single probe: versions, the binary minimum, the FTS flag, and counts of the meta defaults and required objects. */
|
|
37
|
+
export const SCHEMA_PROBE_SQL = `SELECT
|
|
38
|
+
(SELECT user_version FROM pragma_user_version) AS user_version,
|
|
39
|
+
(SELECT value FROM meta WHERE key = 'schema_version') AS schema_version,
|
|
40
|
+
(SELECT value FROM meta WHERE key = 'min_compatible_binary') AS min_binary,
|
|
41
|
+
(SELECT value FROM meta WHERE key = 'fts5_available') AS fts5,
|
|
42
|
+
(SELECT COUNT(*) FROM meta WHERE key IN (${sqlList(META_DEFAULTS.map(([key]) => key))})) AS meta_keys,
|
|
43
|
+
(SELECT COUNT(*) FROM sqlite_master WHERE type IN ('table', 'index') AND name IN (${sqlList(REQUIRED_SCHEMA_OBJECTS)})) AS objects`;
|
|
44
|
+
/** True when the store is current and whole, so the open can skip every DDL statement. Runs the binary check either way. */
|
|
45
|
+
function schemaIsCurrent(db) {
|
|
46
|
+
let probe;
|
|
47
|
+
try {
|
|
48
|
+
// SAFETY: the SELECT names exactly these six columns and always returns one row.
|
|
49
|
+
probe = db.prepare(SCHEMA_PROBE_SQL).get();
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
// A store with no meta table yet is the slow path's job; anything else is a real error.
|
|
53
|
+
if (err instanceof Error && /no such table: meta/.test(err.message))
|
|
54
|
+
return false;
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
if (!probe)
|
|
58
|
+
return false;
|
|
59
|
+
assertMinBinary(probe.min_binary);
|
|
60
|
+
return Number(probe.user_version) === CURRENT_SCHEMA_VERSION
|
|
61
|
+
&& Number(probe.schema_version) === CURRENT_SCHEMA_VERSION
|
|
62
|
+
&& Number(probe.meta_keys) === META_DEFAULTS.length
|
|
63
|
+
&& Number(probe.objects) === REQUIRED_SCHEMA_OBJECTS.length
|
|
64
|
+
&& probe.fts5 === '1';
|
|
65
|
+
}
|
|
23
66
|
export function runMigrations(db, hippoRoot, busyWaitMs) {
|
|
67
|
+
if (!schemaIsCurrent(db))
|
|
68
|
+
migrateAndHeal(db, hippoRoot, busyWaitMs);
|
|
69
|
+
if (hippoRoot)
|
|
70
|
+
importLegacyVectors(db, hippoRoot);
|
|
71
|
+
}
|
|
72
|
+
function migrateAndHeal(db, hippoRoot, busyWaitMs) {
|
|
24
73
|
ensureMetaTable(db);
|
|
25
74
|
// Before anything writes, so a stale binary never repairs or migrates a store it does not understand.
|
|
26
75
|
assertBinaryCompatible(db);
|
|
@@ -30,36 +79,46 @@ export function runMigrations(db, hippoRoot, busyWaitMs) {
|
|
|
30
79
|
for (const migration of MIGRATIONS) {
|
|
31
80
|
if (migration.version <= currentVersion)
|
|
32
81
|
continue;
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
// A newer binary may have migrated and raised the minimum while we waited for the lock.
|
|
36
|
-
assertBinaryCompatible(db);
|
|
37
|
-
// Re-read under the write lock: another process may have applied this
|
|
38
|
-
// migration while we waited, and re-running one is not idempotent.
|
|
39
|
-
const applied = getSchemaVersion(db);
|
|
40
|
-
if (applied >= migration.version) {
|
|
41
|
-
db.exec('COMMIT');
|
|
42
|
-
currentVersion = applied;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
migration.up(db, { hippoRoot });
|
|
46
|
-
setSchemaVersion(db, migration.version);
|
|
47
|
-
db.exec('COMMIT');
|
|
48
|
-
currentVersion = migration.version;
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
try {
|
|
52
|
-
db.exec('ROLLBACK');
|
|
53
|
-
}
|
|
54
|
-
catch { /* already rolled back; keep the original error */ }
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
82
|
+
currentVersion = applyMigration(db, migration, hippoRoot, busyWaitMs);
|
|
57
83
|
}
|
|
84
|
+
healUserVersion(db, currentVersion, busyWaitMs);
|
|
58
85
|
ensureContinuityIndexes(db);
|
|
59
86
|
ensureMetaDefaults(db);
|
|
60
87
|
ensureOptionalFts(db);
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
}
|
|
89
|
+
/** Applies one migration under the write lock and returns the version the store is now at. */
|
|
90
|
+
function applyMigration(db, migration, hippoRoot, busyWaitMs) {
|
|
91
|
+
execWithBusyRetry(db, 'BEGIN IMMEDIATE', busyWaitMs);
|
|
92
|
+
try {
|
|
93
|
+
// A newer binary may have migrated and raised the minimum while we waited for the lock.
|
|
94
|
+
assertBinaryCompatible(db);
|
|
95
|
+
// Re-read under the write lock: another process may have applied this
|
|
96
|
+
// migration while we waited, and re-running one is not idempotent.
|
|
97
|
+
const applied = getSchemaVersion(db);
|
|
98
|
+
if (applied >= migration.version) {
|
|
99
|
+
db.exec('COMMIT');
|
|
100
|
+
return applied;
|
|
101
|
+
}
|
|
102
|
+
migration.up(db, { hippoRoot });
|
|
103
|
+
setSchemaVersion(db, migration.version);
|
|
104
|
+
db.exec('COMMIT');
|
|
105
|
+
return migration.version;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
try {
|
|
109
|
+
db.exec('ROLLBACK');
|
|
110
|
+
}
|
|
111
|
+
catch { /* already rolled back; keep the original error */ }
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// The fast path requires user_version as well, and a store stamped only in meta would otherwise never reach it.
|
|
116
|
+
function healUserVersion(db, version, busyWaitMs) {
|
|
117
|
+
// SAFETY: PRAGMA user_version returns one row with one integer column.
|
|
118
|
+
const row = db.prepare('PRAGMA user_version').get();
|
|
119
|
+
const target = Math.max(0, Math.trunc(version));
|
|
120
|
+
if (Number(row?.user_version ?? 0) < target)
|
|
121
|
+
execWithBusyRetry(db, `PRAGMA user_version = ${target}`, busyWaitMs);
|
|
63
122
|
}
|
|
64
123
|
// A failed import leaves embeddings.json in place for the next open to retry; the store still opens.
|
|
65
124
|
function importLegacyVectors(db, hippoRoot) {
|
|
@@ -71,27 +130,18 @@ function importLegacyVectors(db, hippoRoot) {
|
|
|
71
130
|
}
|
|
72
131
|
}
|
|
73
132
|
function ensureMetaDefaults(db) {
|
|
74
|
-
const defaults = [
|
|
75
|
-
['schema_version', String(CURRENT_SCHEMA_VERSION)],
|
|
76
|
-
['last_retrieval_ids', '[]'],
|
|
77
|
-
['last_trace_id', ''],
|
|
78
|
-
['total_remembered', '0'],
|
|
79
|
-
['total_recalled', '0'],
|
|
80
|
-
['total_forgotten', '0'],
|
|
81
|
-
['fts5_available', '0'],
|
|
82
|
-
];
|
|
83
133
|
// Read-first: an already-current store has all 7 keys, so this is one
|
|
84
134
|
// SELECT and zero writes instead of 7 no-op INSERT OR IGNOREs, each of
|
|
85
135
|
// which takes a RESERVED lock even when nothing changes.
|
|
86
|
-
const keys =
|
|
136
|
+
const keys = META_DEFAULTS.map(([key]) => key);
|
|
87
137
|
const present = new Set(db
|
|
88
138
|
.prepare(`SELECT key FROM meta WHERE key IN (${keys.map(() => '?').join(',')})`)
|
|
89
139
|
.all(...keys)
|
|
90
140
|
.map((r) => r.key));
|
|
91
|
-
if (present.size ===
|
|
141
|
+
if (present.size === META_DEFAULTS.length)
|
|
92
142
|
return;
|
|
93
143
|
const stmt = db.prepare(`INSERT OR IGNORE INTO meta(key, value) VALUES (?, ?)`);
|
|
94
|
-
for (const [key, value] of
|
|
144
|
+
for (const [key, value] of META_DEFAULTS) {
|
|
95
145
|
if (!present.has(key))
|
|
96
146
|
stmt.run(key, value);
|
|
97
147
|
}
|
|
@@ -99,8 +149,8 @@ function ensureMetaDefaults(db) {
|
|
|
99
149
|
function ensureOptionalFts(db) {
|
|
100
150
|
let available = false;
|
|
101
151
|
try {
|
|
102
|
-
db.exec(
|
|
103
|
-
|
|
152
|
+
db.exec(MEMORIES_FTS_DDL);
|
|
153
|
+
repairFtsDrift(db);
|
|
104
154
|
available = true;
|
|
105
155
|
}
|
|
106
156
|
catch (err) {
|
|
@@ -112,16 +162,23 @@ function ensureOptionalFts(db) {
|
|
|
112
162
|
if (getMeta(db, 'fts5_available') !== flag)
|
|
113
163
|
setMeta(db, 'fts5_available', flag);
|
|
114
164
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// SAFETY: this get() result's shape matches the single aliased `c` column named in the SELECT
|
|
165
|
+
/** Row counts of `memories` and its full-text index, which differ once the index drifts; null when either table is missing. */
|
|
166
|
+
export function ftsRowCounts(db) {
|
|
167
|
+
if (!tableExists(db, 'memories_fts') || !tableExists(db, 'memories'))
|
|
168
|
+
return null;
|
|
169
|
+
// SAFETY: this get() result's shape matches the single aliased `c` column named in the SELECT.
|
|
170
|
+
const memories = db.prepare(`SELECT COUNT(*) AS c FROM memories`).get()?.c ?? 0;
|
|
120
171
|
// memories_fts_docsize is FTS5's cheaper one-row-per-document shadow table; fall back for a foreign-built index.
|
|
121
172
|
const ftsTable = tableExists(db, 'memories_fts_docsize') ? 'memories_fts_docsize' : 'memories_fts';
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
173
|
+
// SAFETY: this get() result's shape matches the single aliased `c` column named in the SELECT.
|
|
174
|
+
const fts = db.prepare(`SELECT COUNT(*) AS c FROM ${ftsTable}`).get()?.c ?? 0;
|
|
175
|
+
return { memories: Number(memories), fts: Number(fts) };
|
|
176
|
+
}
|
|
177
|
+
/** Re-syncs `memories_fts` with `memories` when their counts differ and returns whether it did; sleep runs it because a fast open does not. */
|
|
178
|
+
export function repairFtsDrift(db) {
|
|
179
|
+
const counts = ftsRowCounts(db);
|
|
180
|
+
if (counts === null || counts.memories === counts.fts)
|
|
181
|
+
return false;
|
|
125
182
|
db.exec(`
|
|
126
183
|
INSERT INTO memories_fts(id, content, tags)
|
|
127
184
|
SELECT m.id, m.content, m.tags_json
|
|
@@ -134,5 +191,6 @@ function backfillFtsIndex(db) {
|
|
|
134
191
|
DELETE FROM memories_fts
|
|
135
192
|
WHERE id NOT IN (SELECT id FROM memories)
|
|
136
193
|
`);
|
|
194
|
+
return true;
|
|
137
195
|
}
|
|
138
196
|
//# sourceMappingURL=migrate.js.map
|
package/dist/db.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ export type { DatabaseSyncLike } from './db/sqlite.js';
|
|
|
2
2
|
export { isSqliteBusy, STORE_BUSY_MESSAGE, execWithBusyRetry } from './db/busy.js';
|
|
3
3
|
export { getSchemaVersion, getMeta, setMeta, isFtsAvailable } from './db/meta.js';
|
|
4
4
|
export { countTableRows, pruneConsolidationRuns } from './db/tables.js';
|
|
5
|
-
export { getCurrentSchemaVersion, IncompatibleBinaryError } from './db/migrate.js';
|
|
5
|
+
export { getCurrentSchemaVersion, IncompatibleBinaryError, ftsRowCounts, repairFtsDrift } from './db/migrate.js';
|
|
6
6
|
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, withBusyWait, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
7
7
|
//# sourceMappingURL=db.d.ts.map
|
package/dist/db.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { isSqliteBusy, STORE_BUSY_MESSAGE, execWithBusyRetry } from './db/busy.js';
|
|
2
2
|
export { getSchemaVersion, getMeta, setMeta, isFtsAvailable } from './db/meta.js';
|
|
3
3
|
export { countTableRows, pruneConsolidationRuns } from './db/tables.js';
|
|
4
|
-
export { getCurrentSchemaVersion, IncompatibleBinaryError } from './db/migrate.js';
|
|
4
|
+
export { getCurrentSchemaVersion, IncompatibleBinaryError, ftsRowCounts, repairFtsDrift } from './db/migrate.js';
|
|
5
5
|
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, withBusyWait, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
6
6
|
//# sourceMappingURL=db.js.map
|
package/dist/doctor.js
CHANGED
|
@@ -11,7 +11,7 @@ import { findHippoStoreDir } from './project-identity.js';
|
|
|
11
11
|
import { getGlobalRoot } from './shared.js';
|
|
12
12
|
import { isInitialized } from './store/open.js';
|
|
13
13
|
import { loadConfig } from './config.js';
|
|
14
|
-
import { openHippoDbReadOnly, closeHippoDb, getSchemaVersion, getCurrentSchemaVersion, countTableRows, IncompatibleBinaryError } from './db.js';
|
|
14
|
+
import { openHippoDbReadOnly, closeHippoDb, getSchemaVersion, getCurrentSchemaVersion, countTableRows, ftsRowCounts, IncompatibleBinaryError } from './db.js';
|
|
15
15
|
import { REPLAY_AFTER_MS, TRANSCRIPT_FILL_WINDOW_MS } from './compaction-record.js';
|
|
16
16
|
import { isEmbeddingAvailable } from './local-embedding.js';
|
|
17
17
|
import { CODEX_TRUST_LINE, codexHomeDir, isCodexPresent, isJsonObject } from './hooks/shared.js';
|
|
@@ -191,6 +191,25 @@ function memoriesCheck(db) {
|
|
|
191
191
|
}
|
|
192
192
|
return memoryCheck;
|
|
193
193
|
}
|
|
194
|
+
/** Whether the full-text index still matches `memories`; opening a current store no longer checks, so doctor reports and sleep repairs. */
|
|
195
|
+
function ftsCheck(db) {
|
|
196
|
+
try {
|
|
197
|
+
const counts = ftsRowCounts(db);
|
|
198
|
+
if (counts === null)
|
|
199
|
+
return { id: 'fts', status: 'info', detail: 'no full-text index; search uses slower LIKE matching' };
|
|
200
|
+
if (counts.memories === counts.fts)
|
|
201
|
+
return { id: 'fts', status: 'pass', detail: `full-text index in sync (${counts.fts} rows)` };
|
|
202
|
+
return {
|
|
203
|
+
id: 'fts',
|
|
204
|
+
status: 'warn',
|
|
205
|
+
detail: `full-text index out of sync: ${counts.fts} indexed rows for ${counts.memories} memories, so search can miss memories or match removed ones`,
|
|
206
|
+
fix: 'hippo sleep (re-syncs the index)',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
return { id: 'fts', status: 'warn', detail: `cannot read the full-text index: ${err instanceof Error ? err.message : String(err)}` };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
194
213
|
function tokensCheck(db, since) {
|
|
195
214
|
try {
|
|
196
215
|
// SAFETY: COUNT/SUM aggregate row.
|
|
@@ -217,6 +236,7 @@ function databaseChecks(store, now) {
|
|
|
217
236
|
const have = getSchemaVersion(db);
|
|
218
237
|
checks.push(schemaCheck(have, getCurrentSchemaVersion()));
|
|
219
238
|
checks.push(memoriesCheck(db));
|
|
239
|
+
checks.push(ftsCheck(db));
|
|
220
240
|
const since = new Date(now.getTime() - 7 * 86_400_000).toISOString();
|
|
221
241
|
checks.push(tokensCheck(db, since));
|
|
222
242
|
checks.push(failuresCheck(db, since, have));
|
package/dist/raw-archive.js
CHANGED
|
@@ -31,19 +31,9 @@ function moveRowToArchive(db, id, row, opts) {
|
|
|
31
31
|
// Flip kind to 'archived' so the BEFORE DELETE trigger no longer fires, then delete.
|
|
32
32
|
db.prepare(`UPDATE memories SET kind = 'archived' WHERE id = ?`).run(id);
|
|
33
33
|
db.prepare(`DELETE FROM memories WHERE id = ?`).run(id);
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// GDPR right-to-be-forgotten.
|
|
38
|
-
if (isFtsAvailable(db)) {
|
|
39
|
-
try {
|
|
40
|
-
db.prepare(`DELETE FROM memories_fts WHERE id = ?`).run(id);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// Best effort only. The DELETE on memories already succeeded; FTS will
|
|
44
|
-
// self-heal on next DB open via backfillFtsIndex.
|
|
45
|
-
}
|
|
46
|
-
}
|
|
34
|
+
// Archived text must be unsearchable at commit, so a failed purge fails (and rolls back) the whole archive.
|
|
35
|
+
if (isFtsAvailable(db))
|
|
36
|
+
db.prepare(`DELETE FROM memories_fts WHERE id = ?`).run(id);
|
|
47
37
|
}
|
|
48
38
|
// Emit the archive_raw audit event inside the SAVEPOINT so the audit row is
|
|
49
39
|
// committed atomically with the row deletion. Use the row's own tenant_id
|
package/dist/store/open.js
CHANGED
|
@@ -64,13 +64,10 @@ function recordHalfLifeBaseForNewStore(db) {
|
|
|
64
64
|
setMeta(db, TYPED_HALF_LIFE_META_KEY, '1');
|
|
65
65
|
}
|
|
66
66
|
function bootstrapLegacyStore(db, hippoRoot) {
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
const countRow = db.prepare(`SELECT COUNT(*) AS count FROM memories`).get();
|
|
70
|
-
const memoryCount = Number(countRow?.count ?? 0);
|
|
71
|
-
if (memoryCount > 0)
|
|
67
|
+
// Existence, not COUNT(*): a count walks every row on each write's open.
|
|
68
|
+
if (db.prepare(`SELECT 1 AS x FROM memories LIMIT 1`).get() !== undefined)
|
|
72
69
|
return false;
|
|
73
|
-
//
|
|
70
|
+
// The row check misses an all-rejected bootstrap (memories stays empty), which would re-run the
|
|
74
71
|
// import on every open and duplicate consolidation_runs; this meta flag settles it.
|
|
75
72
|
if (getMeta(db, 'legacy_bootstrap_completed', '0') === '1')
|
|
76
73
|
return false;
|
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.63.
|
|
19
|
+
export declare const PACKAGE_VERSION = "1.63.2";
|
|
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.63.
|
|
19
|
+
export const PACKAGE_VERSION = '1.63.2';
|
|
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) => {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Hippo Memory - OpenClaw Plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.
|
|
4
|
+
|
|
5
|
+
Memories decay by default, retrieval strengthens them, errors stick longer, and sleep consolidation merges related episodes into one memory.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "hippo-memory",
|
|
3
3
|
"name": "Hippo Memory",
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "1.63.
|
|
4
|
+
"description": "Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.",
|
|
5
|
+
"version": "1.63.2",
|
|
6
6
|
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hippo-memory",
|
|
3
|
-
"version": "1.63.
|
|
3
|
+
"version": "1.63.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"openclaw": {
|
|
8
8
|
"extensions": [
|
|
@@ -13,4 +13,4 @@
|
|
|
13
13
|
"hippo-memory": ">=0.4.0"
|
|
14
14
|
},
|
|
15
15
|
"license": "MIT"
|
|
16
|
-
}
|
|
16
|
+
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "hippo-memory",
|
|
3
3
|
"name": "Hippo Memory",
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "1.63.
|
|
4
|
+
"description": "Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.",
|
|
5
|
+
"version": "1.63.2",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hippo-memory",
|
|
3
|
-
"version": "1.63.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.63.2",
|
|
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",
|
|
7
7
|
"main": "./dist/index.js",
|