hippo-memory 1.58.0 → 1.59.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 +13 -1
- package/dist/agent-memories/apply.d.ts +1 -1
- package/dist/agent-memories/legacy.js +4 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +2 -0
- package/dist/cli/output.d.ts +3 -0
- package/dist/cli/output.js +7 -0
- package/dist/cli/projects.d.ts +4 -0
- package/dist/cli/projects.js +90 -0
- package/dist/cli/shared.js +14 -8
- package/dist/cli/sleep.js +5 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +389 -361
- package/dist/consolidate.js +12 -1
- package/dist/dag.js +4 -2
- package/dist/dedupe.js +1 -1
- package/dist/doctor.js +23 -0
- package/dist/dormant.d.ts +2 -2
- package/dist/extract.js +16 -16
- package/dist/project-merge.d.ts +52 -0
- package/dist/project-merge.js +168 -0
- package/dist/recall-scope.d.ts +3 -3
- package/dist/recall-scope.js +5 -4
- package/dist/rerankers/clef.d.ts +29 -0
- package/dist/rerankers/clef.js +182 -0
- package/dist/rerankers/index.js +3 -0
- package/dist/rerankers/jev.d.ts +11 -0
- package/dist/rerankers/jev.js +10 -5
- package/dist/rerankers/types.d.ts +16 -0
- package/dist/server.d.ts +6 -1
- package/dist/server.js +9 -3
- package/dist/store.js +6 -6
- 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 +5 -2
package/dist/rerankers/jev.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { RerankerFn } from './types.js';
|
|
2
|
+
import type { SearchResult } from '../search.js';
|
|
2
3
|
export declare const JEV_DEFAULT_TOP_K = 40;
|
|
4
|
+
/** The System One `state` and one `noul` question per candidate (`c1`..`cN`). */
|
|
5
|
+
export interface RelevanceRequest {
|
|
6
|
+
state: string;
|
|
7
|
+
questions: Record<string, {
|
|
8
|
+
type: 'noul';
|
|
9
|
+
instructions: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
/** Redacted query plus numbered, redacted, truncated candidates. Shared with CLEF so both arms see matched input. */
|
|
13
|
+
export declare function buildRelevanceRequest(query: string, head: readonly SearchResult[]): RelevanceRequest;
|
|
3
14
|
/** Builds a Jev reranker around the reranker it degrades to. Exported so a test can pass a stand-in. */
|
|
4
15
|
export declare function createJevReranker(localFallback: RerankerFn): RerankerFn;
|
|
5
16
|
/** Track 4 reranker: hosted TypeSafe Jev, opt-in and paid (TYPESAFE_API_KEY), one batched call per recall.
|
package/dist/rerankers/jev.js
CHANGED
|
@@ -28,11 +28,8 @@ function parseScores(answers, n) {
|
|
|
28
28
|
}
|
|
29
29
|
return out;
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
const key = process.env.TYPESAFE_API_KEY;
|
|
34
|
-
if (!key)
|
|
35
|
-
throw new Error('TYPESAFE_API_KEY not set');
|
|
31
|
+
/** Redacted query plus numbered, redacted, truncated candidates. Shared with CLEF so both arms see matched input. */
|
|
32
|
+
export function buildRelevanceRequest(query, head) {
|
|
36
33
|
const lines = head.map((r, i) => `[${i + 1}] ${truncate(redactSecrets(r.entry.content), TRUNCATE_CHARS)}`);
|
|
37
34
|
const state = `Query: ${redactSecrets(query)}\n\nNumbered candidate memories from an AI coding agent's project store:\n\n${lines.join('\n\n')}`;
|
|
38
35
|
const questions = {};
|
|
@@ -42,6 +39,14 @@ async function requestScores(query, head) {
|
|
|
42
39
|
instructions: `Probability that candidate ${i} (numbered in the state above) helps answer the query.`,
|
|
43
40
|
};
|
|
44
41
|
}
|
|
42
|
+
return { state, questions };
|
|
43
|
+
}
|
|
44
|
+
/** One batched request for the whole candidate list. Rejects with the reason when there are no usable scores. */
|
|
45
|
+
async function requestScores(query, head) {
|
|
46
|
+
const key = process.env.TYPESAFE_API_KEY;
|
|
47
|
+
if (!key)
|
|
48
|
+
throw new Error('TYPESAFE_API_KEY not set');
|
|
49
|
+
const { state, questions } = buildRelevanceRequest(query, head);
|
|
45
50
|
const parsed = Number.parseInt(process.env.HIPPO_JEV_TIMEOUT_MS ?? '', 10);
|
|
46
51
|
const timeoutMs = parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS;
|
|
47
52
|
const controller = new AbortController();
|
|
@@ -26,6 +26,20 @@ export interface RerankerOptions {
|
|
|
26
26
|
/** Per-track config blob; opaque to the seam. */
|
|
27
27
|
config?: Record<string, RerankerConfigValue>;
|
|
28
28
|
}
|
|
29
|
+
/** Which backend and model produced a rerank, or why it fell back. */
|
|
30
|
+
export interface RerankProvenance {
|
|
31
|
+
/** `cloudflare`, `private-endpoint`, or `native` when the input order was kept. */
|
|
32
|
+
backend: 'cloudflare' | 'private-endpoint' | 'native';
|
|
33
|
+
/** Model the caller asked for. */
|
|
34
|
+
requestedModel: string;
|
|
35
|
+
/** Model the provider says scored the request, when it reports one. */
|
|
36
|
+
actualModel?: string;
|
|
37
|
+
/** Set when the input order was kept instead of a model ranking. */
|
|
38
|
+
fallbackReason?: string;
|
|
39
|
+
/** Provider-reported token usage, when sent. */
|
|
40
|
+
inputTokens?: number;
|
|
41
|
+
outputTokens?: number;
|
|
42
|
+
}
|
|
29
43
|
export interface RerankResult extends SearchResult {
|
|
30
44
|
/** Score assigned by the reranker. Replaces `score` for downstream
|
|
31
45
|
* ordering; original `score` preserved on the SearchResult. */
|
|
@@ -34,5 +48,7 @@ export interface RerankResult extends SearchResult {
|
|
|
34
48
|
preRerankRank: number;
|
|
35
49
|
/** 1-indexed rank in the reranker output. */
|
|
36
50
|
postRerankRank: number;
|
|
51
|
+
/** Recorded by rerankers that track model identity (the CLEF rerankers). */
|
|
52
|
+
rerankProvenance?: RerankProvenance;
|
|
37
53
|
}
|
|
38
54
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type IncomingMessage } from 'node:http';
|
|
2
2
|
/** Test-only: reset the module-level recall-history Map. Call from beforeEach. */
|
|
3
3
|
export declare function __resetSessionRecallHistoryHttp(): void;
|
|
4
|
+
import { authRevoke, type Actor, type Context } from './api.js';
|
|
5
|
+
import { ForbiddenError } from './api-errors.js';
|
|
6
|
+
export { authRevoke, ForbiddenError, type Context, type Actor };
|
|
4
7
|
export interface ServerHandle {
|
|
5
8
|
port: number;
|
|
6
9
|
url: string;
|
|
@@ -15,7 +18,7 @@ export interface ServerHandle {
|
|
|
15
18
|
export interface ResolvedBearer {
|
|
16
19
|
tenantId: string;
|
|
17
20
|
subject: string;
|
|
18
|
-
/** Not 'admin' means 'member'. Admin is tenant-only, yet can mint member API keys (POST /v1/auth/keys)
|
|
21
|
+
/** Not 'admin' means 'member'. Admin is tenant-only, yet can mint member API keys (POST /v1/auth/keys); an add-on revokes them through the exported authRevoke when the IdP deprovisions the minter. */
|
|
19
22
|
role: 'admin' | 'member';
|
|
20
23
|
scopes?: readonly string[];
|
|
21
24
|
}
|
|
@@ -56,6 +59,8 @@ export declare function isCrossSite(req: IncomingMessage): boolean;
|
|
|
56
59
|
* bucket per request and bypass the limiter entirely.
|
|
57
60
|
*/
|
|
58
61
|
export declare function clientIpForRateLimit(req: IncomingMessage): string;
|
|
62
|
+
/** Add-ons call this to refuse a subject that would collide with a built-in actor. */
|
|
63
|
+
export declare function isReservedActor(subject: string): boolean;
|
|
59
64
|
/**
|
|
60
65
|
* Boot the HTTP daemon on host:port and write the pidfile under hippoRoot.
|
|
61
66
|
*
|
package/dist/server.js
CHANGED
|
@@ -39,7 +39,9 @@ import { handleMcpRequest } from './mcp/server.js';
|
|
|
39
39
|
import { handleSlackEventsWebhook } from './connectors/slack/webhook.js';
|
|
40
40
|
import { handleGitHubEventsWebhook } from './connectors/github/webhook.js';
|
|
41
41
|
import { HttpError, JSON_HEADERS, BodyTooLargeError, isHeaderString, isJsonObjectRecord, mapApiError, readBody, sendJson, } from './http-util.js';
|
|
42
|
-
import { NotFoundError } from './api-errors.js';
|
|
42
|
+
import { ForbiddenError, NotFoundError } from './api-errors.js';
|
|
43
|
+
// Add-on packages revoke keys through these without importing the whole api surface.
|
|
44
|
+
export { authRevoke, ForbiddenError };
|
|
43
45
|
// Review patch #2: explicit allow-list for unauthenticated /v1/* routes.
|
|
44
46
|
// New unauth routes MUST be added here AND get a corresponding entry in
|
|
45
47
|
// tests/server-bearer-lockdown.test.ts. Do not gate auth elsewhere by
|
|
@@ -364,6 +366,11 @@ export function clientIpForRateLimit(req) {
|
|
|
364
366
|
const RESERVED_ACTOR_NAMES = [
|
|
365
367
|
'api_key', 'localhost', 'cli', 'system', 'mcp', 'connector', 'sleep', 'post-compact', 'recall', 'agent-memories',
|
|
366
368
|
];
|
|
369
|
+
/** Add-ons call this to refuse a subject that would collide with a built-in actor. */
|
|
370
|
+
export function isReservedActor(subject) {
|
|
371
|
+
const lower = subject.toLowerCase();
|
|
372
|
+
return RESERVED_ACTOR_NAMES.some((n) => lower === n || lower.startsWith(`${n}:`));
|
|
373
|
+
}
|
|
367
374
|
function hasControlChar(s) {
|
|
368
375
|
for (let i = 0; i < s.length; i++) {
|
|
369
376
|
const c = s.charCodeAt(i);
|
|
@@ -388,8 +395,7 @@ function sanitiseResolved(r) {
|
|
|
388
395
|
// Padding would let "system " pass the reserved-name check yet read as `system` in an audit log.
|
|
389
396
|
if (hasControlChar(subject) || subject !== subject.trim())
|
|
390
397
|
return null;
|
|
391
|
-
|
|
392
|
-
if (RESERVED_ACTOR_NAMES.some((n) => lower === n || lower.startsWith(`${n}:`)))
|
|
398
|
+
if (isReservedActor(subject))
|
|
393
399
|
return null;
|
|
394
400
|
const clean = { tenantId: tenant, subject, role: role === 'admin' ? 'admin' : 'member' };
|
|
395
401
|
if (Array.isArray(scopes))
|
package/dist/store.js
CHANGED
|
@@ -180,9 +180,9 @@ export function serializeEntry(entry) {
|
|
|
180
180
|
if (tenantId !== 'default') {
|
|
181
181
|
frontmatter['tenant_id'] = tenantId;
|
|
182
182
|
}
|
|
183
|
-
// v39:
|
|
184
|
-
// only undefined
|
|
185
|
-
if (entry.origin_project !== undefined
|
|
183
|
+
// v39: '' (user-global) and null (unknown, hidden by default) must both round-trip;
|
|
184
|
+
// only undefined (unstamped) is omitted, so a rebuild stamps nothing it can read.
|
|
185
|
+
if (entry.origin_project !== undefined) {
|
|
186
186
|
frontmatter['origin_project'] = entry.origin_project;
|
|
187
187
|
}
|
|
188
188
|
// Spread into a fresh object literal: dumpFrontmatter's Record<string,
|
|
@@ -241,7 +241,7 @@ export function deserializeEntry(raw) {
|
|
|
241
241
|
owner: data['owner'] === null || data['owner'] === undefined ? null : String(data['owner']),
|
|
242
242
|
artifact_ref: data['artifact_ref'] === null || data['artifact_ref'] === undefined ? null : String(data['artifact_ref']),
|
|
243
243
|
tenantId: data['tenant_id'] === null || data['tenant_id'] === undefined ? 'default' : String(data['tenant_id']),
|
|
244
|
-
origin_project:
|
|
244
|
+
origin_project: !('origin_project' in data) ? undefined : data['origin_project'] === null ? null : String(data['origin_project']),
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
247
|
function normalizeStringArray(value) {
|
|
@@ -1174,7 +1174,7 @@ export function stampOriginProject(hippoRoot, entry) {
|
|
|
1174
1174
|
return { ...entry, origin_project: deriveOriginProject(path.dirname(hippoRoot)) };
|
|
1175
1175
|
}
|
|
1176
1176
|
/**
|
|
1177
|
-
* Import-time variant
|
|
1177
|
+
* Import-time variant for a mirror with no origin field (an explicit null stays null): used only where evidence exists
|
|
1178
1178
|
* for rows that predate the origin column - the legacy-markdown bootstrap and
|
|
1179
1179
|
* rebuildIndex import, which are the markdown-store equivalent of the v39 SQL
|
|
1180
1180
|
* backfill. Same evidence order as the migration: the provenance source
|
|
@@ -1183,7 +1183,7 @@ export function stampOriginProject(hippoRoot, entry) {
|
|
|
1183
1183
|
* owning project instead of becoming user-global (codex gating round 3 P1).
|
|
1184
1184
|
*/
|
|
1185
1185
|
function stampOriginProjectForImport(hippoRoot, entry) {
|
|
1186
|
-
if (entry.origin_project !== undefined
|
|
1186
|
+
if (entry.origin_project !== undefined)
|
|
1187
1187
|
return entry;
|
|
1188
1188
|
const fromSource = originFromSource(entry.source);
|
|
1189
1189
|
return {
|
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.59.0";
|
|
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.
|
|
19
|
+
export const PACKAGE_VERSION = '1.59.0';
|
|
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) => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hippo-memory",
|
|
3
3
|
"name": "Hippo Memory",
|
|
4
4
|
"description": "Memory for AI agents that learns what is wrong and ranks it down. Injects context at session start and captures errors.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.59.0",
|
|
6
6
|
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hippo-memory",
|
|
3
3
|
"name": "Hippo Memory",
|
|
4
4
|
"description": "Memory for AI agents that learns what is wrong and ranks it down. Injects context at session start and captures errors.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.59.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.
|
|
3
|
+
"version": "1.59.0",
|
|
4
4
|
"description": "Memory for AI agents that learns what is wrong and ranks it down. MCP server, hooks for Claude Code, OpenCode and Codex, AGENTS.md instructions for Codex, Cursor, OpenClaw and Pi. SQLite, zero runtime deps.",
|
|
5
5
|
"mcpName": "io.github.kitfunso/hippo-memory",
|
|
6
6
|
"type": "module",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"pretest": "npm run build",
|
|
35
35
|
"test": "vitest run",
|
|
36
36
|
"test:watch": "vitest",
|
|
37
|
+
"test:coverage": "vitest run --coverage",
|
|
37
38
|
"test:delivery-ledger": "vitest run tests/delivery-ledger",
|
|
38
39
|
"lint": "oxlint",
|
|
39
40
|
"sbom": "node scripts/sbom.mjs",
|
|
@@ -90,9 +91,11 @@
|
|
|
90
91
|
"devDependencies": {
|
|
91
92
|
"@oxlint/plugins": "^1.78.0",
|
|
92
93
|
"@types/node": "^22.16.0",
|
|
94
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
93
95
|
"oxlint": "^1.78.0",
|
|
94
96
|
"typescript": "^5.4.0",
|
|
95
|
-
"
|
|
97
|
+
"vite": "^8.3.2",
|
|
98
|
+
"vitest": "^5.0.3"
|
|
96
99
|
},
|
|
97
100
|
"peerDependenciesMeta": {
|
|
98
101
|
"@xenova/transformers": {
|