hippo-memory 1.64.1 → 1.66.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/dist/agent-memories/sync.js +3 -2
- package/dist/api/recall-record.d.ts +46 -0
- package/dist/api/recall-record.js +98 -0
- package/dist/api/recall-request.d.ts +32 -0
- package/dist/api/recall-request.js +121 -0
- package/dist/api/recall-types.d.ts +8 -1
- package/dist/api/recall.d.ts +3 -2
- package/dist/api/recall.js +176 -183
- package/dist/api/tokens.d.ts +1 -1
- package/dist/api/tokens.js +13 -17
- package/dist/cli/recall.d.ts +1 -1
- package/dist/cli/recall.js +18 -49
- package/dist/cli/shared.js +2 -2
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +18 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +4 -0
- package/dist/db/connect.d.ts +5 -0
- package/dist/db/connect.js +52 -0
- package/dist/db/open.d.ts +13 -11
- package/dist/db/open.js +47 -119
- package/dist/db/request-stores.d.ts +30 -0
- package/dist/db/request-stores.js +100 -0
- package/dist/db/sqlite-blocked.d.ts +16 -0
- package/dist/db/sqlite-blocked.js +29 -0
- package/dist/db.d.ts +3 -1
- package/dist/db.js +3 -1
- package/dist/embeddings.d.ts +11 -2
- package/dist/embeddings.js +22 -12
- package/dist/goals.d.ts +18 -11
- package/dist/goals.js +38 -36
- package/dist/http-util.d.ts +2 -0
- package/dist/http-util.js +5 -0
- package/dist/mcp/memory-tools.js +4 -1
- package/dist/mcp/recall-tools.js +28 -133
- package/dist/mcp/request.d.ts +7 -0
- package/dist/mcp/request.js +63 -41
- package/dist/mcp/session-state.d.ts +1 -3
- package/dist/mcp/session-state.js +3 -5
- package/dist/predictions/planning-fallacy.d.ts +52 -0
- package/dist/predictions/planning-fallacy.js +70 -55
- package/dist/quality-repair.js +3 -0
- package/dist/recall-trace.d.ts +1 -1
- package/dist/recall-trace.js +5 -4
- package/dist/search/hybrid.d.ts +3 -0
- package/dist/search/physics-search.d.ts +3 -0
- package/dist/search/physics-search.js +21 -19
- package/dist/search/vector.d.ts +7 -2
- package/dist/search/vector.js +29 -10
- package/dist/server/routes/recall.d.ts +1 -1
- package/dist/server/routes/recall.js +27 -218
- package/dist/server.d.ts +17 -1
- package/dist/server.js +38 -22
- package/dist/store/entry-writes.d.ts +2 -0
- package/dist/store/entry-writes.js +24 -16
- package/dist/store/open.js +2 -1
- package/dist/store-port.d.ts +88 -2
- package/dist/store-port.js +104 -9
- 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
|
@@ -3,185 +3,44 @@ import { dirname, resolve } from 'node:path';
|
|
|
3
3
|
import { assertCallerProject, resolveProjectIdentity } from '../../project-identity.js';
|
|
4
4
|
import { isSharedStore } from '../../config.js';
|
|
5
5
|
import { assembleCost, contextCost, drillCost } from '../../context-render.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { appendRecall, biasHintEnabled, buildSessionKey, getOrCreateRing, hashQueryText, snapshotRing } from '../../recall-history.js';
|
|
9
|
-
import { appendAuditEvent, auditQueryFields } from '../../audit.js';
|
|
6
|
+
import { storeFor } from '../../store-port.js';
|
|
7
|
+
import { biasHintEnabled } from '../../recall-history.js';
|
|
10
8
|
import { assemble, drillDown, getContext, recordTokens, retrieve } from '../../api.js';
|
|
11
|
-
import {
|
|
9
|
+
import { httpParams, parseContextRequest, parseRecallRequest } from '../../api/recall-request.js';
|
|
10
|
+
import { anchorSkippedRows, noteRecall, peekSessionRing, resetSessionRings, sessionRing } from '../../api/recall-record.js';
|
|
11
|
+
import { HttpError, sendJson } from '../../http-util.js';
|
|
12
12
|
import { buildContextWithAuth } from '../auth.js';
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
// for the HTTP pipeline. Separate from CLI/MCP rings (per-
|
|
16
|
-
// pipeline rings; no IPC). HTTP is the only caller that threads its
|
|
17
|
-
// snapshot through opts.recallHistory to api.recall — api.recall's
|
|
18
|
-
// anchoringHint on the returned RecallResult IS the user-visible hint
|
|
19
|
-
// here (no separate compute needed).
|
|
20
|
-
const sessionRecallHistoryHttp = new Map();
|
|
21
|
-
/** Test-only: reset the module-level recall-history Map. Call from beforeEach. */
|
|
13
|
+
import { validateIdSegment } from '../validation.js';
|
|
14
|
+
/** Test-only: reset the HTTP recall rings. Call from beforeEach. */
|
|
22
15
|
export function __resetSessionRecallHistoryHttp() {
|
|
23
|
-
|
|
16
|
+
resetSessionRings('http');
|
|
24
17
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const freshTailCountRaw = query.get('fresh_tail_count');
|
|
29
|
-
const freshTailCount = freshTailCountRaw === null ? undefined : Number(freshTailCountRaw);
|
|
30
|
-
if (freshTailCount !== undefined && (!Number.isFinite(freshTailCount) || freshTailCount < 0)) {
|
|
31
|
-
throw new HttpError(400, 'fresh_tail_count must be a non-negative number');
|
|
32
|
-
}
|
|
33
|
-
// Cap session_id length consistent with the
|
|
34
|
-
// rest of the API. Untrimmed strings round-trip through the SQL layer
|
|
35
|
-
// and through any downstream metric/log; 256 is generous for a session
|
|
36
|
-
// id and matches the rest of this file's id-shaped param parsers.
|
|
37
|
-
const freshTailSessionIdRaw = query.get('fresh_tail_session_id');
|
|
38
|
-
if (freshTailSessionIdRaw !== null && freshTailSessionIdRaw.length > MAX_ID_LEN) {
|
|
39
|
-
throw new HttpError(400, `fresh_tail_session_id exceeds ${MAX_ID_LEN}-character cap`);
|
|
40
|
-
}
|
|
41
|
-
const freshTailSessionId = freshTailSessionIdRaw && freshTailSessionIdRaw.length > 0
|
|
42
|
-
? freshTailSessionIdRaw
|
|
43
|
-
: undefined;
|
|
44
|
-
return { freshTailCount, freshTailSessionId };
|
|
45
|
-
}
|
|
46
|
-
function parseSessionId(query) {
|
|
47
|
-
// session_id for the dlPFC goal-stack boost. 256-char cap mirrors
|
|
48
|
-
// fresh_tail_session_id (above). Trim then drop if empty so api.recall
|
|
49
|
-
// sees undefined when the param is omitted or whitespace-only.
|
|
50
|
-
const sessionIdRaw = query.get('session_id');
|
|
51
|
-
if (sessionIdRaw !== null && sessionIdRaw.length > MAX_ID_LEN) {
|
|
52
|
-
throw new HttpError(400, `session_id exceeds ${MAX_ID_LEN}-character cap`);
|
|
53
|
-
}
|
|
54
|
-
return sessionIdRaw && sessionIdRaw.trim().length > 0
|
|
55
|
-
? sessionIdRaw.trim()
|
|
56
|
-
: undefined;
|
|
57
|
-
}
|
|
58
|
-
function parseRecallQuery(query) {
|
|
59
|
-
const q = query.get('q');
|
|
60
|
-
if (!q) {
|
|
61
|
-
throw new HttpError(400, 'q is required');
|
|
62
|
-
}
|
|
63
|
-
const limitRaw = query.get('limit');
|
|
64
|
-
const limit = limitRaw === null ? undefined : parseListLimit(limitRaw);
|
|
65
|
-
const mode = query.get('mode');
|
|
66
|
-
if (mode !== null && mode !== 'bm25' && mode !== 'hybrid' && mode !== 'physics') {
|
|
67
|
-
throw new HttpError(400, "mode must be 'bm25', 'hybrid', or 'physics'");
|
|
68
|
-
}
|
|
69
|
-
const scope = query.get('scope');
|
|
70
|
-
const includeContinuityRaw = query.get('include_continuity');
|
|
71
|
-
const includeContinuity = includeContinuityRaw === '1'
|
|
72
|
-
|| includeContinuityRaw === 'true';
|
|
73
|
-
const { freshTailCount, freshTailSessionId } = parseFreshTail(query);
|
|
74
|
-
// Strict parse matching the includeContinuity convention, so values
|
|
75
|
-
// like `?summarize_overflow=banana` or an empty value do not turn it on.
|
|
76
|
-
const summarizeOverflowRaw = query.get('summarize_overflow');
|
|
77
|
-
const summarizeOverflow = summarizeOverflowRaw === null
|
|
78
|
-
? undefined
|
|
79
|
-
: (summarizeOverflowRaw === '1' || summarizeOverflowRaw === 'true');
|
|
80
|
-
// recall() owns the shape rule (NaN, 0 and negatives throw invalid_scorer_window); the transport caps remote cost.
|
|
81
|
-
const scorerWindowRaw = query.get('scorer_window');
|
|
82
|
-
const scorerWindow = scorerWindowRaw === null ? undefined : Number(scorerWindowRaw);
|
|
83
|
-
if (scorerWindow !== undefined && scorerWindow > 1000) {
|
|
84
|
-
throw new HttpError(400, 'scorer_window must be <= 1000');
|
|
85
|
-
}
|
|
86
|
-
const sessionId = parseSessionId(query);
|
|
87
|
-
// Recall-trace: opt-in explain flag. When set, api.recall attaches the
|
|
88
|
-
// lifecycle re-ranking trace (goal-boost step on the api pipeline) +
|
|
89
|
-
// rerankPipeline:'api' to each result item; the field then rides on the
|
|
90
|
-
// serialized RecallResult. Mirrors the include_continuity convention.
|
|
91
|
-
const explainRaw = query.get('explain');
|
|
92
|
-
const explain = explainRaw === '1' || explainRaw === 'true';
|
|
93
|
-
return { q, limit, mode, scope, includeContinuity, freshTailCount, freshTailSessionId, summarizeOverflow, scorerWindow, sessionId, explain };
|
|
94
|
-
}
|
|
95
|
-
// HTTP per-pipeline anchoring detector. HTTP threads its
|
|
96
|
-
// ring snapshot via opts.recallHistory so api.recall's own
|
|
97
|
-
// anchoringHint compute path activates. Unlike CLI (which computes
|
|
98
|
-
// its own hint separately because cmdRecall runs its own physics/
|
|
99
|
-
// hybrid pipeline outside api.recall), HTTP's /v1/memories response
|
|
100
|
-
// body IS api.recall's result directly. So the api.recall-computed
|
|
101
|
-
// hint flows through. HIPPO_ANCHORING=off short-circuits.
|
|
102
|
-
function snapshotSessionRing(ctx, hippoRoot, q, sessionId) {
|
|
103
|
-
let httpRecallHistory;
|
|
104
|
-
let httpRingKey;
|
|
105
|
-
if (biasHintEnabled('anchoring')) {
|
|
106
|
-
if (sessionId) {
|
|
107
|
-
// Do NOT mutate sessionRecallHistoryHttp before recall() preflight: a request
|
|
108
|
-
// that 400s would otherwise create-or-touch a ring and LRU-evict valid sessions.
|
|
109
|
-
httpRingKey = buildSessionKey(ctx.tenantId, sessionId);
|
|
110
|
-
const existingRing = sessionRecallHistoryHttp.get(httpRingKey);
|
|
111
|
-
httpRecallHistory = existingRing ? snapshotRing(existingRing) : [];
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
// Telemetry: caller had no session_id so ring tracking skipped.
|
|
115
|
-
// Per the normal recall-audit convention (api.ts:854 stores
|
|
116
|
-
// SHA-256/16 hash of the query, NOT raw text), avoid retaining
|
|
117
|
-
// prompts in audit_log here too — query content can contain
|
|
118
|
-
// secrets, PII, or RTBF-restricted material. hashQueryText is a 32-bit
|
|
119
|
-
// FNV-1a, NOT a privacy hash, so use the recall audit's SHA-256/16 truncation.
|
|
120
|
-
const dbForAudit = openHippoDb(hippoRoot);
|
|
121
|
-
try {
|
|
122
|
-
appendAuditEvent(dbForAudit, {
|
|
123
|
-
tenantId: ctx.tenantId,
|
|
124
|
-
actor: ctx.actor.subject,
|
|
125
|
-
op: 'recall_anchor_skipped_no_session',
|
|
126
|
-
targetId: undefined,
|
|
127
|
-
metadata: auditQueryFields(q),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
finally {
|
|
131
|
-
closeHippoDb(dbForAudit);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return { httpRecallHistory, httpRingKey };
|
|
136
|
-
}
|
|
137
|
-
function recallExtraOpts(parsed, httpRecallHistory) {
|
|
138
|
-
const recallExtra = {};
|
|
139
|
-
if (parsed.freshTailCount !== undefined)
|
|
140
|
-
recallExtra.freshTailCount = parsed.freshTailCount;
|
|
141
|
-
if (parsed.freshTailSessionId !== undefined)
|
|
142
|
-
recallExtra.freshTailSessionId = parsed.freshTailSessionId;
|
|
143
|
-
if (parsed.summarizeOverflow !== undefined)
|
|
144
|
-
recallExtra.summarizeOverflow = parsed.summarizeOverflow;
|
|
145
|
-
if (parsed.scorerWindow !== undefined)
|
|
146
|
-
recallExtra.scorerWindow = parsed.scorerWindow;
|
|
147
|
-
if (parsed.sessionId !== undefined)
|
|
148
|
-
recallExtra.sessionId = parsed.sessionId;
|
|
149
|
-
if (httpRecallHistory !== undefined)
|
|
150
|
-
recallExtra.recallHistory = httpRecallHistory;
|
|
151
|
-
if (parsed.explain)
|
|
152
|
-
recallExtra.explain = parsed.explain;
|
|
153
|
-
return recallExtra;
|
|
18
|
+
// HTTP threads the ring through opts.recallHistory, so the hint retrieve() returns is the one the caller sees.
|
|
19
|
+
function recallHistoryFor(ctx, sessionId) {
|
|
20
|
+
return sessionId && biasHintEnabled('anchoring') ? peekSessionRing('http', ctx.tenantId, sessionId) : undefined;
|
|
154
21
|
}
|
|
155
22
|
// GET /v1/memories?q=...&limit=...&mode=...&scope=...&include_continuity=1
|
|
156
23
|
export async function handleRecallMemories({ req, res, opts, query }) {
|
|
157
|
-
const
|
|
158
|
-
const { q, includeContinuity, sessionId } =
|
|
24
|
+
const { opts: recallOpts, limit, mode, explain } = parseRecallRequest(httpParams(query));
|
|
25
|
+
const { query: q, includeContinuity, sessionId } = recallOpts;
|
|
159
26
|
const ctx = await buildContextWithAuth(req, opts);
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
169
|
-
// Append only after recall succeeds, so a 400 cannot LRU-evict valid sessions;
|
|
170
|
-
// anchoredOn feeds the cooldown logic for the NEXT recall on this session.
|
|
171
|
-
if (httpRingKey) {
|
|
172
|
-
const httpRing = getOrCreateRing(sessionRecallHistoryHttp, httpRingKey);
|
|
173
|
-
const topId = result.results[0]?.id ?? null;
|
|
174
|
-
appendRecall(httpRing, hashQueryText(q), topId, result.anchoringHint?.memoryId);
|
|
175
|
-
}
|
|
27
|
+
const recallHistory = recallHistoryFor(ctx, sessionId);
|
|
28
|
+
// Written first in the recall's own write, so a recall that fails leaves no row.
|
|
29
|
+
const leadingAudit = sessionId ? [] : anchorSkippedRows({ tenantId: ctx.tenantId, actor: ctx.actor.subject }, q);
|
|
30
|
+
const result = await retrieve(ctx, { ...recallOpts, limit, mode, explain, recallHistory, leadingAudit });
|
|
31
|
+
// The ring is created only after recall succeeds, so a 400 cannot LRU-evict a live session.
|
|
32
|
+
const ring = sessionRing('http', ctx.tenantId, sessionId);
|
|
33
|
+
if (ring)
|
|
34
|
+
noteRecall(ring, q, result.results[0]?.id ?? null, result.anchoringHint?.memoryId);
|
|
176
35
|
// Each recall surface counts its own hits; api.recall is no chokepoint,
|
|
177
36
|
// since the CLI never calls it and MCP shows the user a different band.
|
|
178
|
-
|
|
37
|
+
await storeFor(ctx).bumpRecallStats(result.results.length);
|
|
179
38
|
// Continuity payloads should never be cached. The caller is asking for
|
|
180
39
|
// session-state-aware data; intermediaries must not reuse it across users.
|
|
181
40
|
if (includeContinuity) {
|
|
182
41
|
res.setHeader('Cache-Control', 'no-store');
|
|
183
42
|
}
|
|
184
|
-
recordTokens(ctx, 'http_recall', { items: result.results.length, tokens: result.tokens + (result.continuityTokens ?? 0), sessionId: sessionId ?? null });
|
|
43
|
+
await recordTokens(ctx, 'http_recall', { items: result.results.length, tokens: result.tokens + (result.continuityTokens ?? 0), sessionId: sessionId ?? null });
|
|
185
44
|
sendJson(res, 200, result);
|
|
186
45
|
return;
|
|
187
46
|
}
|
|
@@ -220,7 +79,7 @@ export async function handleAssembleSession({ req, res, opts, query }, assembleM
|
|
|
220
79
|
if (scope !== undefined)
|
|
221
80
|
assembleExtra.scope = scope;
|
|
222
81
|
const result = assemble(ctx, assembleMatch.id, { ...assembleExtra, cost: assembleCost(assembleMatch.id) });
|
|
223
|
-
recordTokens(ctx, 'http_assemble', { items: result.items.length, tokens: result.tokens, sessionId: assembleMatch.id });
|
|
82
|
+
await recordTokens(ctx, 'http_assemble', { items: result.items.length, tokens: result.tokens, sessionId: assembleMatch.id });
|
|
224
83
|
sendJson(res, 200, result);
|
|
225
84
|
return;
|
|
226
85
|
}
|
|
@@ -290,64 +149,14 @@ function contextReader(hippoRoot, query) {
|
|
|
290
149
|
// via the Bearer. Pinned-only + '*' fallback skip the recall audit emit
|
|
291
150
|
// (matches cmdContext); real-query hybrid search emits one 'recall' row.
|
|
292
151
|
export async function handleGetContext({ req, res, opts, query }) {
|
|
293
|
-
const
|
|
294
|
-
// DoS cap on q-param length. 1024 covers real multi-clause queries
|
|
295
|
-
// (pasted error messages, multi-stem searches) while bounding BM25
|
|
296
|
-
// tokenisation cost (~150 tokens worst case at 1024 chars).
|
|
297
|
-
if (q !== undefined && q.length > 1024) {
|
|
298
|
-
throw new HttpError(400, 'q exceeds 1024-character cap');
|
|
299
|
-
}
|
|
300
|
-
const budgetRaw = query.get('budget');
|
|
301
|
-
let budget;
|
|
302
|
-
if (budgetRaw !== null) {
|
|
303
|
-
budget = Number(budgetRaw);
|
|
304
|
-
if (!Number.isFinite(budget) || budget < 0) {
|
|
305
|
-
throw new HttpError(400, 'budget must be a non-negative number');
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
const limitRaw = query.get('limit');
|
|
309
|
-
let limit;
|
|
310
|
-
if (limitRaw !== null) {
|
|
311
|
-
limit = Number(limitRaw);
|
|
312
|
-
if (!Number.isFinite(limit) || limit <= 0) {
|
|
313
|
-
throw new HttpError(400, 'limit must be a positive number');
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
const pinnedOnlyRaw = query.get('pinned_only');
|
|
317
|
-
const pinnedOnly = pinnedOnlyRaw === '1' || pinnedOnlyRaw === 'true';
|
|
318
|
-
const scopeRaw = query.get('scope');
|
|
319
|
-
if (scopeRaw !== null && scopeRaw.length > MAX_ID_LEN) {
|
|
320
|
-
throw new HttpError(400, `scope exceeds ${MAX_ID_LEN}-character cap`);
|
|
321
|
-
}
|
|
322
|
-
const scope = scopeRaw === null ? undefined : scopeRaw;
|
|
323
|
-
const includeRecentRaw = query.get('include_recent');
|
|
324
|
-
let includeRecent;
|
|
325
|
-
if (includeRecentRaw !== null) {
|
|
326
|
-
includeRecent = Number(includeRecentRaw);
|
|
327
|
-
if (!Number.isFinite(includeRecent) || includeRecent < 0) {
|
|
328
|
-
throw new HttpError(400, 'include_recent must be a non-negative number');
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
// v39 memory scope isolation: cross_project=1|true re-includes
|
|
332
|
-
// other-project rows (tagged category 'cross-project' in the response).
|
|
333
|
-
// The partition identity comes from the SERVED STORE's location, not the
|
|
334
|
-
// daemon's process cwd - a daemon started from anywhere still isolates
|
|
335
|
-
// the project it serves.
|
|
336
|
-
const crossProjectRaw = query.get('cross_project');
|
|
337
|
-
const crossProject = crossProjectRaw === '1' || crossProjectRaw === 'true';
|
|
152
|
+
const parsed = parseContextRequest(httpParams(query));
|
|
338
153
|
const ctx = await buildContextWithAuth(req, opts);
|
|
339
154
|
const result = await getContext(ctx, {
|
|
340
|
-
|
|
341
|
-
budget,
|
|
342
|
-
limit,
|
|
343
|
-
pinnedOnly,
|
|
344
|
-
scope,
|
|
345
|
-
includeRecent,
|
|
346
|
-
crossProject,
|
|
155
|
+
...parsed,
|
|
347
156
|
currentProject: contextReader(opts.hippoRoot, query),
|
|
348
157
|
cost: contextCost('markdown', 'observe'), // clients render; the budget prices the block `hippo context` would print
|
|
349
158
|
});
|
|
350
|
-
recordTokens(ctx, 'http_context', { items: result.entries.length, tokens: result.tokens });
|
|
159
|
+
await recordTokens(ctx, 'http_context', { items: result.entries.length, tokens: result.tokens });
|
|
351
160
|
sendJson(res, 200, result);
|
|
352
161
|
return;
|
|
353
162
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -11,9 +11,25 @@ export { HttpError } from './http-util.js';
|
|
|
11
11
|
export { promptHookContext, type CallerProject } from './prompt-hook.js';
|
|
12
12
|
export type { JsonValue } from './json.js';
|
|
13
13
|
export { captureSessionTexts, type SessionCaptureRequest, type SessionCaptureResult } from './capture/session-texts.js';
|
|
14
|
-
export { sqliteStore, type HippoStore } from './store-port.js';
|
|
14
|
+
export { sqliteStore, type HippoStore, type RecallSearchArgs, type RecallWrites, type VectorReads } from './store-port.js';
|
|
15
15
|
export type { ApiKeyRecord } from './auth.js';
|
|
16
|
+
export type { AppendAuditOpts } from './audit.js';
|
|
17
|
+
export type { ContinuityBlock } from './api/recall-types.js';
|
|
18
|
+
export type { ActiveGoals, GetActiveGoalsOpts, Goal, GoalRecallLogRow, RetrievalPolicy } from './goals.js';
|
|
19
|
+
export type { MemoryEntry } from './memory.js';
|
|
20
|
+
export type { ClassResolution, PlanningFallacyEvidence } from './predictions/planning-fallacy.js';
|
|
21
|
+
export type { PredictionBaserate } from './predictions/store.js';
|
|
22
|
+
export type { RecallTraceInput } from './recall-trace.js';
|
|
23
|
+
export type { StrengthenOptions } from './store/entry-writes.js';
|
|
24
|
+
export type { OriginFilter, RecallScopeFilter, VectorCandidateSpec } from './store/search-rows.js';
|
|
25
|
+
export type { ContinuityKey } from './store/sessions.js';
|
|
26
|
+
export type { TokenUse } from './token-ledger.js';
|
|
27
|
+
export type { EmbeddingIndexState } from './embeddings.js';
|
|
28
|
+
export type { PhysicsParticle } from './physics.js';
|
|
16
29
|
export { StoreBusyError } from './db.js';
|
|
30
|
+
export { decodeVector, EMBEDDING_MODEL_META_KEY, rankVectorRows, type VectorMatch, type VectorRow } from './vector-store.js';
|
|
31
|
+
export { bufferToFloat32 } from './physics-state.js';
|
|
32
|
+
export { OTHER_STORE_MARKER, OtherStoreFolderError, withSqliteAllowed } from './db.js';
|
|
17
33
|
export { authCreate, type AuthCreateOpts, type AuthCreateResult } from './api.js';
|
|
18
34
|
export { isSharedStore } from './config.js';
|
|
19
35
|
export { ownerOrSubject } from './api.js';
|
package/dist/server.js
CHANGED
|
@@ -2,8 +2,9 @@ import { envPort, envRequireAuth, envV1Rps } from './env.js';
|
|
|
2
2
|
import { createServer } from 'node:http';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { detectServer, removePidfileIfOwned, writePidfile } from './server-detect.js';
|
|
5
|
-
import { closeHippoDb, getHippoDbPath, isStoreBusy, openHippoDb,
|
|
5
|
+
import { closeHippoDb, getHippoDbPath, isStoreBusy, openHippoDb, outsideRequestStores, runWithRequestStores, SERVER_DB_WAIT_MS, withSqliteBlocked } from './db.js';
|
|
6
6
|
import { sqliteStore } from './store-port.js';
|
|
7
|
+
import { markSharedStore } from './config.js';
|
|
7
8
|
import { auditWriteFailureCount } from './audit.js';
|
|
8
9
|
import { PACKAGE_VERSION } from './version.js';
|
|
9
10
|
import { errorFields, log } from './log.js';
|
|
@@ -11,7 +12,7 @@ import { createRateLimiter } from './rate-limit.js';
|
|
|
11
12
|
import { authCreateSelf, authRevoke, RecallContractError } from './api.js';
|
|
12
13
|
import { handleSlackEventsWebhook } from './connectors/slack/webhook.js';
|
|
13
14
|
import { handleGitHubEventsWebhook } from './connectors/github/webhook.js';
|
|
14
|
-
import { BodyTimeoutError, BodyTooLargeError, closeAfterReply, HttpError, JSON_HEADERS, sendJson } from './http-util.js';
|
|
15
|
+
import { BodyTimeoutError, BodyTooLargeError, closeAfterReply, HttpError, JSON_HEADERS, sendJson, STORE_NOT_PORTED_MESSAGE } from './http-util.js';
|
|
15
16
|
import { ForbiddenError } from './api-errors.js';
|
|
16
17
|
import { buildContextWithAuth, isLoopback, LIMITER_MAX_KEYS, requireAuth } from './server/auth.js';
|
|
17
18
|
import { enforceRateLimit } from './server/client-ip.js';
|
|
@@ -47,6 +48,11 @@ export { captureSessionTexts } from './capture/session-texts.js';
|
|
|
47
48
|
// An add-on serves from another database by passing serve() its own HippoStore.
|
|
48
49
|
export { sqliteStore } from './store-port.js';
|
|
49
50
|
export { StoreBusyError } from './db.js';
|
|
51
|
+
// An add-on store decodes and ranks with hippo.db's own code, so both return the same ids in the same order.
|
|
52
|
+
export { decodeVector, EMBEDDING_MODEL_META_KEY, rankVectorRows } from './vector-store.js';
|
|
53
|
+
export { bufferToFloat32 } from './physics-state.js';
|
|
54
|
+
// store copy --db writes the marker and reads the old hippo.db under the waiver.
|
|
55
|
+
export { OTHER_STORE_MARKER, OtherStoreFolderError, withSqliteAllowed } from './db.js';
|
|
50
56
|
// An add-on's install step mints the first admin key into a store folder it names, which `hippo auth create` cannot reach.
|
|
51
57
|
export { authCreate } from './api.js';
|
|
52
58
|
// An add-on that serves a team store checks the flag before it starts.
|
|
@@ -92,7 +98,7 @@ const LOOPBACK_HOSTS = new Set(['127.0.0.1', '::1', 'localhost']);
|
|
|
92
98
|
const V1_ROUTES = [
|
|
93
99
|
{ method: 'POST', path: '/v1/memories', handler: handleCreateMemory },
|
|
94
100
|
{ method: 'GET', path: '/v1/graph', handler: handleGetGraph },
|
|
95
|
-
{ method: 'GET', path: '/v1/memories', handler: handleRecallMemories },
|
|
101
|
+
{ method: 'GET', path: '/v1/memories', storeReady: true, handler: handleRecallMemories },
|
|
96
102
|
{ method: 'GET', pattern: '/v1/sessions/:id/assemble', handler: handleAssembleSession },
|
|
97
103
|
{ method: 'GET', pattern: '/v1/recall/drill/:id', handler: handleDrillRecall },
|
|
98
104
|
{ method: 'POST', pattern: '/v1/memories/:id/archive', handler: handleArchiveMemory },
|
|
@@ -238,10 +244,9 @@ function dispatchPublicJson({ res, opts }, method, path) {
|
|
|
238
244
|
res.end(text);
|
|
239
245
|
return true;
|
|
240
246
|
}
|
|
241
|
-
const NOT_ON_STORE_MESSAGE = 'not available on this store';
|
|
242
247
|
function assertSqliteStore(opts) {
|
|
243
248
|
if (opts.store.kind !== 'sqlite')
|
|
244
|
-
throw new HttpError(501,
|
|
249
|
+
throw new HttpError(501, STORE_NOT_PORTED_MESSAGE);
|
|
245
250
|
}
|
|
246
251
|
/** Under another store, a route not yet ported answers 501 without running; the caller is checked first, so a bad key is still a 401. */
|
|
247
252
|
async function refuseUnportedRoute(req, opts) {
|
|
@@ -264,12 +269,25 @@ async function handleRequest(req, res, opts, startedAt, streamSlots, limiter) {
|
|
|
264
269
|
}
|
|
265
270
|
enforceRateLimit(req, path, limiter);
|
|
266
271
|
const routeRequest = { req, res, opts, query };
|
|
267
|
-
if (await
|
|
272
|
+
if (await runWithRequestStores(() => dispatchScopedRoute(routeRequest, method, path), { busyWaitMs: SERVER_DB_WAIT_MS }))
|
|
268
273
|
return;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
274
|
+
// Outside the request scope: the heartbeat timer outlives the request. Store-ready: the stream only authenticates, through the port.
|
|
275
|
+
if (method === 'GET' && path === '/mcp/stream') {
|
|
276
|
+
await handleMcpStream(req, res, opts, streamSlots);
|
|
272
277
|
return;
|
|
278
|
+
}
|
|
279
|
+
res.writeHead(404, JSON_HEADERS);
|
|
280
|
+
res.end(JSON.stringify({ error: 'not found' }));
|
|
281
|
+
}
|
|
282
|
+
/** Every route that runs inside a request scope, so it opens each store once: the /v1 table, public JSON, add-on routes, the webhooks and POST /mcp. */
|
|
283
|
+
async function dispatchScopedRoute(r, method, path) {
|
|
284
|
+
if (await dispatchV1Route(r, method, path))
|
|
285
|
+
return true;
|
|
286
|
+
if (dispatchPublicJson(r, method, path))
|
|
287
|
+
return true;
|
|
288
|
+
if (await dispatchAddonRoute(r, method, path))
|
|
289
|
+
return true;
|
|
290
|
+
const { req, res, opts } = r;
|
|
273
291
|
if (method === 'POST' && path === '/v1/connectors/slack/events') {
|
|
274
292
|
// Bearer auth deliberately skipped: this route is in PUBLIC_ROUTES and authenticates via the Slack HMAC signature.
|
|
275
293
|
if (!isPublicRoute(method, path)) {
|
|
@@ -278,7 +296,7 @@ async function handleRequest(req, res, opts, startedAt, streamSlots, limiter) {
|
|
|
278
296
|
}
|
|
279
297
|
assertSqliteStore(opts);
|
|
280
298
|
await handleSlackEventsWebhook({ req, res, opts });
|
|
281
|
-
return;
|
|
299
|
+
return true;
|
|
282
300
|
}
|
|
283
301
|
if (method === 'POST' && path === '/v1/connectors/github/events') {
|
|
284
302
|
if (!isPublicRoute(method, path)) {
|
|
@@ -286,20 +304,14 @@ async function handleRequest(req, res, opts, startedAt, streamSlots, limiter) {
|
|
|
286
304
|
}
|
|
287
305
|
assertSqliteStore(opts);
|
|
288
306
|
await handleGitHubEventsWebhook({ req, res, opts });
|
|
289
|
-
return;
|
|
307
|
+
return true;
|
|
290
308
|
}
|
|
309
|
+
// Store-ready: under another store the MCP layer lists and runs only the tools ported to the port.
|
|
291
310
|
if (method === 'POST' && path === '/mcp') {
|
|
292
|
-
await refuseUnportedRoute(req, opts);
|
|
293
311
|
await handleMcpPost(req, res, opts);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
// Store-ready: the stream and its heartbeat only authenticate, and auth goes through the port.
|
|
297
|
-
if (method === 'GET' && path === '/mcp/stream') {
|
|
298
|
-
await handleMcpStream(req, res, opts, streamSlots);
|
|
299
|
-
return;
|
|
312
|
+
return true;
|
|
300
313
|
}
|
|
301
|
-
|
|
302
|
-
res.end(JSON.stringify({ error: 'not found' }));
|
|
314
|
+
return false;
|
|
303
315
|
}
|
|
304
316
|
function sendHealth(req, res, startedAt) {
|
|
305
317
|
// Loopback callers (detectServer's stale-pidfile probe reads version and
|
|
@@ -387,7 +399,8 @@ function createStoreHolder(hippoRoot, store) {
|
|
|
387
399
|
if (heldDb || stopHolding || !existsSync(getHippoDbPath(hippoRoot)))
|
|
388
400
|
return;
|
|
389
401
|
try {
|
|
390
|
-
|
|
402
|
+
// The 'finish' listener can fire inside a request scope, which would close this connection with the request.
|
|
403
|
+
heldDb = outsideRequestStores(() => openHippoDb(hippoRoot));
|
|
391
404
|
}
|
|
392
405
|
catch (err) {
|
|
393
406
|
stopHolding = true;
|
|
@@ -522,6 +535,9 @@ export async function serve(opts) {
|
|
|
522
535
|
...opts, routes, publicJsonBodies, store: opts.store ?? sqliteStore(opts.hippoRoot), callerLimiter, failedAuthLimiter,
|
|
523
536
|
};
|
|
524
537
|
const { kind } = served.store;
|
|
538
|
+
// A store other than hippo.db is a team's central server, so its folder's config.json must not decide shared-ness.
|
|
539
|
+
if (kind !== 'sqlite')
|
|
540
|
+
markSharedStore(opts.hippoRoot);
|
|
525
541
|
const holder = createStoreHolder(opts.hippoRoot, served.store);
|
|
526
542
|
const inflight = new Set();
|
|
527
543
|
const server = createServer((req, res) => {
|
|
@@ -531,7 +547,7 @@ export async function serve(opts) {
|
|
|
531
547
|
const requestId = resolveRequestId(req.headers['x-request-id']);
|
|
532
548
|
requestIds.set(req, requestId);
|
|
533
549
|
res.setHeader('X-Request-Id', requestId);
|
|
534
|
-
const run = () =>
|
|
550
|
+
const run = () => handleRequest(req, res, served, startedAt, streamSlots, limiter);
|
|
535
551
|
// A missed port under another store would otherwise create and write a hippo.db that store never reads.
|
|
536
552
|
(kind === 'sqlite' ? run() : withSqliteBlocked(kind, run)).catch((err) => replyOrClose(req, res, err, requestId));
|
|
537
553
|
});
|
|
@@ -36,6 +36,8 @@ export interface StrengthenOptions {
|
|
|
36
36
|
/** Strengthen what a read returned: update only the four retrieval columns on the live row, never a stale copy.
|
|
37
37
|
* Best effort: a failure logs and never fails the read. Returns the ids found in this store. */
|
|
38
38
|
export declare function strengthenRetrieved(hippoRoot: string, ids: readonly string[], opts: StrengthenOptions): Set<string>;
|
|
39
|
+
/** strengthenRetrieved on an open handle that holds no transaction, so a recall's last writes share one handle. */
|
|
40
|
+
export declare function strengthenRetrievedInOwnTx(db: DatabaseSyncLike, ids: readonly string[], opts: StrengthenOptions): Set<string>;
|
|
39
41
|
/** strengthenRetrieved on the caller's handle, inside the caller's transaction. Throws; the caller decides. */
|
|
40
42
|
export declare function strengthenRetrievedOn(db: DatabaseSyncLike, ids: readonly string[], opts: StrengthenOptions): Set<string>;
|
|
41
43
|
/** Rewrites a live row's tags and its full-text row on the caller's transaction, with no audit row. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { markRetrieved } from '../memory.js';
|
|
2
|
-
import { closeHippoDb, openHippoDb, withWriteScope } from '../db.js';
|
|
2
|
+
import { closeHippoDb, openHippoDb, rethrowIfSqliteBlocked, withWriteScope } from '../db.js';
|
|
3
3
|
import { RejectedValueError } from '../rejection.js';
|
|
4
4
|
import { markSummaryDirtyInTx } from '../summary-dirty.js';
|
|
5
5
|
import { log } from '../log.js';
|
|
@@ -73,30 +73,38 @@ export function writeEntryMirrors(hippoRoot, entry) {
|
|
|
73
73
|
/** Strengthen what a read returned: update only the four retrieval columns on the live row, never a stale copy.
|
|
74
74
|
* Best effort: a failure logs and never fails the read. Returns the ids found in this store. */
|
|
75
75
|
export function strengthenRetrieved(hippoRoot, ids, opts) {
|
|
76
|
-
const found = new Set();
|
|
77
76
|
if (ids.length === 0 || opts.recallBoostAblated)
|
|
78
|
-
return
|
|
77
|
+
return new Set();
|
|
79
78
|
let db;
|
|
80
79
|
try {
|
|
81
80
|
db = openHippoDb(hippoRoot);
|
|
82
|
-
db.exec('BEGIN IMMEDIATE');
|
|
83
|
-
for (const id of strengthenRetrievedOn(db, ids, opts))
|
|
84
|
-
found.add(id);
|
|
85
|
-
db.exec('COMMIT');
|
|
86
81
|
}
|
|
87
82
|
catch (error) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
rethrowIfSqliteBlocked(error);
|
|
84
|
+
warnStrengthenFailed(error);
|
|
85
|
+
return new Set();
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return strengthenRetrievedInOwnTx(db, ids, opts);
|
|
94
89
|
}
|
|
95
90
|
finally {
|
|
96
|
-
|
|
97
|
-
closeHippoDb(db);
|
|
91
|
+
closeHippoDb(db);
|
|
98
92
|
}
|
|
99
|
-
|
|
93
|
+
}
|
|
94
|
+
/** strengthenRetrieved on an open handle that holds no transaction, so a recall's last writes share one handle. */
|
|
95
|
+
export function strengthenRetrievedInOwnTx(db, ids, opts) {
|
|
96
|
+
if (ids.length === 0 || opts.recallBoostAblated)
|
|
97
|
+
return new Set();
|
|
98
|
+
try {
|
|
99
|
+
return withWriteScope(db, 'strengthen_retrieved', () => strengthenRetrievedOn(db, ids, opts));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
warnStrengthenFailed(error);
|
|
103
|
+
return new Set();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function warnStrengthenFailed(error) {
|
|
107
|
+
log.warn(`retrieval stats not saved (${error instanceof Error ? error.message : String(error)})`);
|
|
100
108
|
}
|
|
101
109
|
/** strengthenRetrieved on the caller's handle, inside the caller's transaction. Throws; the caller decides. */
|
|
102
110
|
export function strengthenRetrievedOn(db, ids, opts) {
|
package/dist/store/open.js
CHANGED
|
@@ -25,9 +25,10 @@ export function initStore(hippoRoot) {
|
|
|
25
25
|
}
|
|
26
26
|
/** One open connection with init done on it, for callers who used to pay for `initStore` + a second `openHippoDb`. */
|
|
27
27
|
export function openStore(hippoRoot, opts) {
|
|
28
|
-
|
|
28
|
+
// Open first: a folder marked for another store must refuse before any mirror folder appears.
|
|
29
29
|
const db = openHippoDb(hippoRoot, opts);
|
|
30
30
|
try {
|
|
31
|
+
ensureMirrorDirectories(hippoRoot);
|
|
31
32
|
const bootstrapped = bootstrapLegacyStore(db, hippoRoot);
|
|
32
33
|
if (bootstrapped) {
|
|
33
34
|
syncMirrorFiles(hippoRoot, db);
|
package/dist/store-port.d.ts
CHANGED
|
@@ -1,13 +1,99 @@
|
|
|
1
1
|
import { type ApiKeyRecord } from './auth.js';
|
|
2
|
+
import { type AppendAuditOpts } from './audit.js';
|
|
3
|
+
import type { ContinuityBlock } from './api/recall-types.js';
|
|
4
|
+
import { type EmbeddingIndexState } from './embeddings.js';
|
|
5
|
+
import { type ActiveGoals, type GetActiveGoalsOpts, type GoalRecallLogRow } from './goals.js';
|
|
6
|
+
import type { MemoryEntry } from './memory.js';
|
|
7
|
+
import type { PhysicsParticle } from './physics.js';
|
|
8
|
+
import { type PlanningFallacyEvidence } from './predictions/planning-fallacy.js';
|
|
9
|
+
import { type RecallTraceInput } from './recall-trace.js';
|
|
10
|
+
import { type StrengthenOptions } from './store/entry-writes.js';
|
|
11
|
+
import { type OriginFilter, type VectorCandidateSpec } from './store/search-rows.js';
|
|
12
|
+
import { type ContinuityKey } from './store/sessions.js';
|
|
13
|
+
import { type TokenUse } from './token-ledger.js';
|
|
14
|
+
/** The arguments of `loadRecallSearchEntries` after the query, by name. */
|
|
15
|
+
export interface RecallSearchArgs {
|
|
16
|
+
readonly limit: number;
|
|
17
|
+
readonly tenantId?: string;
|
|
18
|
+
readonly requestedScope?: string;
|
|
19
|
+
readonly explicitScopeMode: 'exact' | 'additive';
|
|
20
|
+
readonly includeSuperseded: boolean;
|
|
21
|
+
readonly originProjects?: OriginFilter;
|
|
22
|
+
/** The caller's personal scope, which the default deny admits; undefined admits no personal row. */
|
|
23
|
+
readonly ownScope: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** Everything one recall writes once its reply is decided, so a store writes it on one connection. */
|
|
26
|
+
export interface RecallWrites {
|
|
27
|
+
/** Insert or ignore on (memory_id, goal_id), so the first row wins, a duplicate inside this batch included. First drop a row
|
|
28
|
+
* whose memory_id is in no memories row of this store, whatever that row's tenant, as `localGoalRecallRows` does. */
|
|
29
|
+
readonly goalLog: readonly GoalRecallLogRow[];
|
|
30
|
+
/** In the order they happened. */
|
|
31
|
+
readonly audit: readonly AppendAuditOpts[];
|
|
32
|
+
readonly trace?: RecallTraceInput;
|
|
33
|
+
/** Nothing when `recallBoostAblated`. Each id found, in `tenantId` when set, gets the retrieval_count, last_retrieved,
|
|
34
|
+
* half_life_days and strength `markRetrieved` computes from the row read before any update, so a repeated id moves once. */
|
|
35
|
+
readonly strengthen?: {
|
|
36
|
+
readonly ids: readonly string[];
|
|
37
|
+
readonly opts: StrengthenOptions;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** The reads behind recall's vector arm, one group a store sets in full as `HippoStore.vectors` or leaves unset. */
|
|
41
|
+
export interface VectorReads {
|
|
42
|
+
/** The stored model and whether any vector exists, in one snapshot. */
|
|
43
|
+
embeddingIndexState(): Promise<EmbeddingIndexState>;
|
|
44
|
+
storedVectors(ids: readonly string[]): Promise<Map<string, number[]>>;
|
|
45
|
+
/** The `spec.limit ?? 50` rows nearest `queryVector` by `rankVectorRows` among those where the tenant matches (when set), kind is not 'archived', a superseded row
|
|
46
|
+
* shows only with `includeSuperseded`, scope follows `RecallScopeFilter`, and origin is '' or listed (when `spec.origin` is set). */
|
|
47
|
+
nearestEntries(queryVector: readonly number[], spec: VectorCandidateSpec): Promise<MemoryEntry[]>;
|
|
48
|
+
/** The particles of `ids` only; an empty list reads nothing. */
|
|
49
|
+
physicsParticles(ids: readonly string[]): Promise<Map<string, PhysicsParticle>>;
|
|
50
|
+
}
|
|
2
51
|
/** 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`. */
|
|
3
52
|
export interface HippoStore {
|
|
4
53
|
/** '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. */
|
|
5
54
|
readonly kind: string;
|
|
6
55
|
/** The api_keys row for `keyId` with its scope grants, revoked or not; null when no row matches. */
|
|
7
56
|
findApiKey(keyId: string): Promise<ApiKeyRecord | null>;
|
|
57
|
+
/** Recall candidates in `loadRecallSearchEntries` order: FTS, then LIKE, then every row in scope. */
|
|
58
|
+
searchRecallEntries(query: string, args: RecallSearchArgs): Promise<MemoryEntry[]>;
|
|
59
|
+
/** The rows among the first 500 ids, a tenant narrowing them, ordered by created, then content, then id, all ascending,
|
|
60
|
+
* as `loadEntriesByIds` does; the input order is ignored. */
|
|
61
|
+
entriesByIds(ids: readonly string[], tenantId?: string): Promise<MemoryEntry[]>;
|
|
62
|
+
/** The session's active goals and their policies, read together so a goal and its policy never disagree. */
|
|
63
|
+
activeGoals(opts: GetActiveGoalsOpts): Promise<ActiveGoals>;
|
|
64
|
+
/** The first `count` (at most 200) unsuperseded raw rows a tenant, a non-empty session id and non-null `origins` (those projects and
|
|
65
|
+
* user-global rows) must narrow, by created descending, then content and id ascending, as `loadFreshRawMemories` does. */
|
|
66
|
+
freshRawEntries(count: number, tenantId: string | undefined, sessionId: string | undefined, origins: readonly string[] | null): Promise<MemoryEntry[]>;
|
|
67
|
+
/** The newest active snapshot a non-null `key` must narrow to one owner and project (null only on an unshared hippo.db), then for its session
|
|
68
|
+
* the newest handoff (same key) and `eventLimit` newest events returned oldest first, each tie broken by the larger id; the caller applies scope. */
|
|
69
|
+
continuity(tenantId: string, eventLimit: number, key: ContinuityKey | null): Promise<ContinuityBlock>;
|
|
70
|
+
/** Resolves a forward claim's tokens to one class and reads its baserate, writing no audit row. */
|
|
71
|
+
planningFallacyEvidence(tenantId: string, classQueryTokens: readonly string[]): Promise<PlanningFallacyEvidence>;
|
|
72
|
+
/** Appends the rows in order, all or none. */
|
|
73
|
+
appendAuditEvents(events: readonly AppendAuditOpts[]): Promise<void>;
|
|
74
|
+
/** Writes the goal log and audit rows in one transaction and rejects with none written if it fails. Then the trace and
|
|
75
|
+
* the strengthen, each on its own: a failure there logs and still resolves, since the reply is already decided. */
|
|
76
|
+
finishRecall(writes: RecallWrites): Promise<void>;
|
|
77
|
+
/** Adds `recalled` to the one store-wide total_recalled counter, no tenant; the SQLite store also rewrites stats.json. */
|
|
78
|
+
bumpRecallStats(recalled: number): Promise<void>;
|
|
79
|
+
/** One token-ledger row. Throws, so the caller decides whether a ledger failure matters. */
|
|
80
|
+
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;
|
|
8
83
|
/** Releases the store's connections; `serve()` closes only a store it made itself. */
|
|
9
84
|
close(): Promise<void>;
|
|
10
85
|
}
|
|
11
|
-
/** The
|
|
12
|
-
export declare function
|
|
86
|
+
/** The store a request runs on: the served one, else hippo.db under its root, as the CLI and SDK callers have it. */
|
|
87
|
+
export declare function storeFor(ctx: {
|
|
88
|
+
readonly hippoRoot: string;
|
|
89
|
+
readonly store?: HippoStore;
|
|
90
|
+
}): HippoStore;
|
|
91
|
+
/** 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
|
+
};
|
|
95
|
+
/** sqliteStore's continuity read, for the synchronous recall that cannot await the port. */
|
|
96
|
+
export declare function continuityAt(hippoRoot: string, tenantId: string, eventLimit: number, key: ContinuityKey | null): ContinuityBlock;
|
|
97
|
+
/** sqliteStore's finishRecall, for the synchronous recall that cannot await the port. */
|
|
98
|
+
export declare function finishRecallAt(hippoRoot: string, writes: RecallWrites): void;
|
|
13
99
|
//# sourceMappingURL=store-port.d.ts.map
|