hippo-memory 1.52.2 → 1.52.3
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 +7 -1
- package/dist/secret-detect.d.ts +1 -1
- package/dist/secret-detect.js +9 -1
- package/dist/support-bundle.js +60 -41
- 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/README.md
CHANGED
|
@@ -628,7 +628,7 @@ hippo watch "npm run build"
|
|
|
628
628
|
| `hippo dormant [<query>]` | List faded memories sleep kept instead of deleting |
|
|
629
629
|
| `hippo dormant restore <id>` | Bring a dormant memory back to active memory |
|
|
630
630
|
| `hippo dormant forget <id>` | Delete a dormant memory permanently |
|
|
631
|
-
| `hippo doctor [--json]` | Check the install: Node, store, schema, sleep, agent hooks; each problem names its fix |
|
|
631
|
+
| `hippo doctor [--json]` | Check the install: Node, store, schema, sleep, agent hooks; each problem names its fix. It never changes `hippo.db`, though SQLite may leave empty `hippo.db-wal` and `hippo.db-shm` files beside it |
|
|
632
632
|
| `hippo support-bundle [--out <file>] [--include-logs]` | Write a redacted JSON file for a support ticket: versions, doctor checks, config, store counts and log names, never memory text; `--include-logs` adds each log's last 200 lines, which can quote it |
|
|
633
633
|
| `hippo tokens [--days n]` | Estimated tokens of memory text handed to agents, per surface, and what skipping unchanged hook blocks saved |
|
|
634
634
|
| `hippo failures [--days n]` | Failed tool calls the capture-error hook saw, by outcome, and how many errors first happened in another session |
|
|
@@ -963,6 +963,12 @@ The interesting problems:
|
|
|
963
963
|
- **MemPalace-style spatial organization.** Could spatial structure (wings/halls/rooms) improve hippo's semantic layer?
|
|
964
964
|
- **AAAK-style compression for semantic memories.** Lossless token compression for context injection.
|
|
965
965
|
|
|
966
|
+
## Open source and commercial
|
|
967
|
+
|
|
968
|
+
Hippo is open core, and the line is drawn by who pays. Everything an individual developer or a self-hosted team needs is in this repository under MIT: the CLI, the MCP server, hooks, connectors, the dashboard, tenants, API keys, roles, per-key scope grants, the audit log and zero-touch memory. Code published here stays MIT and stays here.
|
|
969
|
+
|
|
970
|
+
A commercial edition for larger companies ships as a separate package under a commercial licence from KITFUNSO LTD. It adds SSO (OIDC and SAML sign-in), SCIM, an org admin view, the pilot report and telemetry join, SIEM export of the audit log, offline licence keys, hosted SaaS, and support with an SLA. Pull requests for those features belong there, not here; see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
971
|
+
|
|
966
972
|
## License
|
|
967
973
|
|
|
968
974
|
MIT
|
package/dist/secret-detect.d.ts
CHANGED
|
@@ -38,6 +38,6 @@ export declare function detectSecret(entry: {
|
|
|
38
38
|
* CS1 pre-compact snapshot fields — can scrub it in place instead.
|
|
39
39
|
*/
|
|
40
40
|
export declare function redactSecrets(text: string): string;
|
|
41
|
-
/** Stricter redaction for text that leaves the machine: no co-occurrence guard, plus Bearer headers and JWTs. */
|
|
41
|
+
/** Stricter redaction for text that leaves the machine: no co-occurrence guard, plus Bearer and Basic auth headers and JWTs. */
|
|
42
42
|
export declare function redactSecretsStrict(text: string): string;
|
|
43
43
|
//# sourceMappingURL=secret-detect.d.ts.map
|
package/dist/secret-detect.js
CHANGED
|
@@ -28,6 +28,13 @@ const SECRET_PATTERNS = [
|
|
|
28
28
|
{ name: 'slack-token', re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/ },
|
|
29
29
|
{ name: 'stripe-key', re: /\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}\b/ },
|
|
30
30
|
{ name: 'google-api-key', re: /\bAIza[0-9A-Za-z_-]{35}\b/ },
|
|
31
|
+
// hk_ is hippo's own API key (src/auth.ts); npm, Hugging Face, GitLab and Slack webhook shapes follow gitleaks' rules.
|
|
32
|
+
{ name: 'hippo-api-key', re: /\bhk_[a-z2-7]{24}\.[a-z2-7]{32}\b/ },
|
|
33
|
+
{ name: 'npm-token', re: /\bnpm_[A-Za-z0-9]{36}\b/ },
|
|
34
|
+
{ name: 'huggingface-token', re: /\bhf_[A-Za-z]{34}\b/ },
|
|
35
|
+
{ name: 'gitlab-token', re: /\bglpat-[\w-]{20,}/ },
|
|
36
|
+
{ name: 'google-oauth-token', re: /\bya29\.[\w-]{20,}/ },
|
|
37
|
+
{ name: 'slack-webhook', re: /\bhooks\.slack\.com\/(?:services|workflows|triggers)\/[A-Za-z0-9+/]{43,56}/ },
|
|
31
38
|
{ name: 'private-key-block', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
|
|
32
39
|
// sk-... (OpenAI/Anthropic style) and sk_<vendor>_... shapes. Both require
|
|
33
40
|
// a key-ish noun somewhere in the content (co-occurrence guard) so prose
|
|
@@ -51,6 +58,7 @@ const CO_OCCURRENCE_GUARDED = new Set(['sk-style-key', 'sk-underscore-key']);
|
|
|
51
58
|
// redactSecretsStrict-only: too noisy for whole-entry memory scanning, worth hiding once text leaves the machine.
|
|
52
59
|
const STRICT_ONLY_PATTERNS = [
|
|
53
60
|
/\bbearer\s+[A-Za-z0-9._~+/-]{16,}=*/gi,
|
|
61
|
+
/\bauthorization["']?\s*[:=]\s*["']?basic\s+[A-Za-z0-9+/]{8,}={0,2}/gi,
|
|
54
62
|
/\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]*/g,
|
|
55
63
|
];
|
|
56
64
|
/**
|
|
@@ -82,7 +90,7 @@ export function detectSecret(entry) {
|
|
|
82
90
|
export function redactSecrets(text) {
|
|
83
91
|
return redactText(text, false);
|
|
84
92
|
}
|
|
85
|
-
/** Stricter redaction for text that leaves the machine: no co-occurrence guard, plus Bearer headers and JWTs. */
|
|
93
|
+
/** Stricter redaction for text that leaves the machine: no co-occurrence guard, plus Bearer and Basic auth headers and JWTs. */
|
|
86
94
|
export function redactSecretsStrict(text) {
|
|
87
95
|
return redactText(text, true);
|
|
88
96
|
}
|
package/dist/support-bundle.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
/** `hippo support-bundle`: one redacted JSON snapshot for a support ticket. Read-only; never touches memory content. */
|
|
1
|
+
/** `hippo support-bundle`: one redacted JSON snapshot for a support ticket. Read-only (SQLite may leave empty -wal and -shm files); never touches memory content. */
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
+
import { pathToFileURL } from 'url';
|
|
5
6
|
import { findHippoStoreDir, isGlobalStoreRoot, realpathOrResolve } from './project-identity.js';
|
|
6
7
|
import { getGlobalRoot } from './shared.js';
|
|
7
8
|
import { isInitialized } from './store.js';
|
|
@@ -189,18 +190,11 @@ function nativeRealPathKey(p) {
|
|
|
189
190
|
return null;
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const forms = new Set();
|
|
195
|
-
const add = (s) => { if (s.length >= 3)
|
|
196
|
-
forms.add(s); };
|
|
193
|
+
function collectHomeAliases(home, probes) {
|
|
194
|
+
const aliases = new Set();
|
|
197
195
|
for (const base of [home, realpathOrResolve(home), os.homedir(), realpathOrResolve(os.homedir())]) {
|
|
198
|
-
add(base);
|
|
199
|
-
add(path.resolve(base));
|
|
200
|
-
if (process.platform === 'win32') {
|
|
201
|
-
add(base.replace(/\\/g, '/'));
|
|
202
|
-
add(path.resolve(base).replace(/\\/g, '/'));
|
|
203
|
-
}
|
|
196
|
+
aliases.add(base);
|
|
197
|
+
aliases.add(path.resolve(base));
|
|
204
198
|
}
|
|
205
199
|
// The alias (e.g. a short-name temp root) can sit above the probe itself, so walk ancestors and
|
|
206
200
|
// compare native realpaths rather than checking each probe alone.
|
|
@@ -209,34 +203,56 @@ function collectHomeForms(home, probes) {
|
|
|
209
203
|
let dir = path.resolve(probe);
|
|
210
204
|
for (;;) {
|
|
211
205
|
const key = nativeRealPathKey(dir);
|
|
212
|
-
if (key !== null && homeKeys.has(key))
|
|
213
|
-
add(dir);
|
|
214
|
-
if (process.platform === 'win32')
|
|
215
|
-
add(dir.replace(/\\/g, '/'));
|
|
216
|
-
}
|
|
206
|
+
if (key !== null && homeKeys.has(key))
|
|
207
|
+
aliases.add(dir);
|
|
217
208
|
const parent = path.dirname(dir);
|
|
218
209
|
if (parent === dir)
|
|
219
210
|
break;
|
|
220
211
|
dir = parent;
|
|
221
212
|
}
|
|
222
213
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
214
|
+
return aliases;
|
|
215
|
+
}
|
|
216
|
+
// Node's own encoding, so it matches file URLs and ESM stack frames; a drive path's URL gains a leading slash.
|
|
217
|
+
function fileUrlPath(p) {
|
|
218
|
+
const pathname = pathToFileURL(p).pathname;
|
|
219
|
+
return /^\/[A-Za-z]:/.test(pathname) ? pathname.slice(1) : pathname;
|
|
220
|
+
}
|
|
221
|
+
// A home turns up as a path, as a file URL's percent-encoded path, and as a Claude Code project folder name.
|
|
222
|
+
const HOME_SPELLINGS = [
|
|
223
|
+
(p) => p,
|
|
224
|
+
fileUrlPath,
|
|
225
|
+
(p) => p.replace(/[^A-Za-z0-9]/g, '-'),
|
|
226
|
+
];
|
|
227
|
+
// A Windows separator can be written \, / or JSON-escaped (\\ in a log line quoting JSON), so any of them matches.
|
|
228
|
+
function spellingPattern(spelling) {
|
|
229
|
+
if (process.platform !== 'win32')
|
|
230
|
+
return escapeRegExp(spelling);
|
|
231
|
+
return spelling.split(/[\\/]+/).map(escapeRegExp).join('(?:\\\\+|/)');
|
|
233
232
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
233
|
+
/** One pattern for every alias of the home in every spelling, or null when none is long enough to swap safely. */
|
|
234
|
+
function buildHomePattern(home, probes) {
|
|
235
|
+
const spellings = new Set();
|
|
236
|
+
for (const alias of collectHomeAliases(home, probes)) {
|
|
237
|
+
for (const spell of HOME_SPELLINGS) {
|
|
238
|
+
const s = spell(alias);
|
|
239
|
+
if (s.length >= 3)
|
|
240
|
+
spellings.add(s);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Longest first: where one home nests in another (a test home under the real one), swap the longer.
|
|
244
|
+
const patterns = new Map();
|
|
245
|
+
for (const s of [...spellings].sort((a, b) => b.length - a.length)) {
|
|
246
|
+
const pattern = spellingPattern(s);
|
|
247
|
+
const key = process.platform === 'win32' ? pattern.toLowerCase() : pattern;
|
|
248
|
+
if (!patterns.has(key))
|
|
249
|
+
patterns.set(key, pattern);
|
|
250
|
+
}
|
|
251
|
+
if (patterns.size === 0)
|
|
252
|
+
return null;
|
|
253
|
+
// Whole names only: "<home> now" and "<home>." swap; "<home>ty", or "web-app" for a home of /app, do not.
|
|
254
|
+
const alternatives = [...patterns.values()].join('|');
|
|
255
|
+
return new RegExp(`(?<![\\p{L}\\p{N}_])(?:${alternatives})(?![\\p{L}\\p{N}_])`, process.platform === 'win32' ? 'giu' : 'gu');
|
|
240
256
|
}
|
|
241
257
|
const URL_RE = /\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/gi;
|
|
242
258
|
function redactUrlsInString(text) {
|
|
@@ -255,37 +271,40 @@ function redactUrlsInString(text) {
|
|
|
255
271
|
}
|
|
256
272
|
});
|
|
257
273
|
}
|
|
258
|
-
function redactString(text,
|
|
259
|
-
|
|
274
|
+
function redactString(text, homePattern) {
|
|
275
|
+
const scrubbed = redactSecretsStrict(redactUrlsInString(text));
|
|
276
|
+
return homePattern === null ? scrubbed : scrubbed.replace(homePattern, '~');
|
|
260
277
|
}
|
|
261
|
-
function redactStrings(value,
|
|
278
|
+
function redactStrings(value, homePattern) {
|
|
262
279
|
if (isJsonString(value))
|
|
263
|
-
return redactString(value,
|
|
280
|
+
return redactString(value, homePattern);
|
|
264
281
|
if (Array.isArray(value))
|
|
265
|
-
return value.map((v) => redactStrings(v,
|
|
282
|
+
return value.map((v) => redactStrings(v, homePattern));
|
|
266
283
|
if (isJsonObject(value)) {
|
|
267
284
|
const out = {};
|
|
268
285
|
for (const [k, v] of Object.entries(value))
|
|
269
|
-
out[k] = redactStrings(v,
|
|
286
|
+
out[k] = redactStrings(v, homePattern);
|
|
270
287
|
return out;
|
|
271
288
|
}
|
|
272
289
|
return value;
|
|
273
290
|
}
|
|
274
291
|
/** Read-only: builds one redacted support-ticket snapshot. Never reads a memory content column. */
|
|
275
292
|
export function buildSupportBundle(opts) {
|
|
293
|
+
// Before doctor: its read-only open leaves an empty -wal that the store's file list would then report.
|
|
294
|
+
const stores = buildStores(opts);
|
|
276
295
|
const bundle = {
|
|
277
296
|
format: 'hippo-support-bundle/1',
|
|
278
297
|
createdAt: opts.now.toISOString(),
|
|
279
298
|
hippo: opts.version,
|
|
280
299
|
runtime: buildRuntime(),
|
|
281
300
|
doctor: JSON.parse(JSON.stringify(runDoctor(opts))),
|
|
282
|
-
stores
|
|
301
|
+
stores,
|
|
283
302
|
env: listSetEnvNames(),
|
|
284
303
|
logs: buildLogsSection(opts),
|
|
285
304
|
};
|
|
286
305
|
// Store paths found from the working folder arrive resolved; the global root arrives as HIPPO_HOME was typed, and log lines
|
|
287
306
|
// often quote the temp folder, which usually sits under the home.
|
|
288
|
-
const redacted = redactStrings(bundle,
|
|
307
|
+
const redacted = redactStrings(bundle, buildHomePattern(opts.home, [getGlobalRoot(), os.tmpdir()]));
|
|
289
308
|
// SAFETY: the value built above is always a JsonObject; redactStrings preserves object shape.
|
|
290
309
|
return redacted;
|
|
291
310
|
}
|
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.
|
|
19
|
+
export declare const PACKAGE_VERSION = "1.52.3";
|
|
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.
|
|
19
|
+
export const PACKAGE_VERSION = '1.52.3';
|
|
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) => {
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hippo-memory",
|
|
3
3
|
"name": "Hippo Memory",
|
|
4
4
|
"description": "Biologically-inspired memory for AI agents. Decay by default, retrieval strengthening, sleep consolidation.",
|
|
5
|
-
"version": "1.52.
|
|
5
|
+
"version": "1.52.3",
|
|
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.
|
|
3
|
+
"version": "1.52.3",
|
|
4
4
|
"description": "Biologically-inspired memory for AI agents. Zero runtime deps, SQLite, MCP server, and an opt-in hosted TypeSafe Jev reranker. Decay, retrieval strengthening, consolidation.",
|
|
5
5
|
"mcpName": "io.github.kitfunso/hippo-memory",
|
|
6
6
|
"type": "module",
|