hippo-memory 1.64.0 → 1.65.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 +1 -1
- 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 +7 -1
- package/dist/api/recall.d.ts +3 -2
- package/dist/api/recall.js +174 -181
- package/dist/api/tokens.d.ts +1 -1
- package/dist/api/tokens.js +13 -17
- package/dist/cli/maintenance.d.ts +2 -1
- package/dist/cli/maintenance.js +17 -11
- 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 +9 -11
- package/dist/db/open.js +23 -116
- package/dist/db/request-stores.d.ts +30 -0
- package/dist/db/request-stores.js +100 -0
- package/dist/db/sqlite-blocked.d.ts +6 -0
- package/dist/db/sqlite-blocked.js +11 -0
- package/dist/db.d.ts +3 -1
- package/dist/db.js +3 -1
- package/dist/embedding-provider.js +4 -3
- package/dist/embeddings.js +2 -1
- 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/local-embedding.d.ts +2 -0
- package/dist/local-embedding.js +20 -4
- 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/recall-trace.d.ts +1 -1
- package/dist/recall-trace.js +5 -4
- package/dist/search/physics-search.js +4 -1
- package/dist/search/vector.js +2 -0
- package/dist/server/routes/recall.d.ts +1 -1
- package/dist/server/routes/recall.js +30 -218
- package/dist/server.d.ts +12 -1
- package/dist/server.js +33 -22
- package/dist/store/entry-writes.d.ts +2 -0
- package/dist/store/entry-writes.js +24 -16
- package/dist/store-port.d.ts +70 -1
- package/dist/store-port.js +87 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/cli/maintenance.js
CHANGED
|
@@ -3,7 +3,6 @@ import { envAnthropicApiKey } from '../env.js';
|
|
|
3
3
|
import { loadAllEntries } from '../store/entry-reads.js';
|
|
4
4
|
import { deduplicateStore } from '../dedupe.js';
|
|
5
5
|
import { embedAll, loadEmbeddingIndex } from '../embeddings.js';
|
|
6
|
-
import { resolveEmbeddingModel } from '../local-embedding.js';
|
|
7
6
|
import { resolveEmbeddingProvider } from '../embedding-provider.js';
|
|
8
7
|
import { resetAllPhysicsState } from '../physics-state.js';
|
|
9
8
|
import { loadConfig } from '../config.js';
|
|
@@ -93,7 +92,7 @@ export function cmdDedup(hippoRoot, flags) {
|
|
|
93
92
|
// ---------------------------------------------------------------------------
|
|
94
93
|
// Embed command
|
|
95
94
|
// ---------------------------------------------------------------------------
|
|
96
|
-
export async function cmdEmbed(hippoRoot, flags) {
|
|
95
|
+
export async function cmdEmbed(hippoRoot, flags, given) {
|
|
97
96
|
// --global mirrors resolveAuthRoot (cli.ts:6900): initGlobal() + the global
|
|
98
97
|
// root, skipping the local requireInit entirely, so this is the healing
|
|
99
98
|
// path for pre-1.27.0 global stores from a directory with no local .hippo.
|
|
@@ -109,12 +108,13 @@ export async function cmdEmbed(hippoRoot, flags) {
|
|
|
109
108
|
printEmbedStatus(root);
|
|
110
109
|
return;
|
|
111
110
|
}
|
|
112
|
-
|
|
111
|
+
const provider = readyEmbedProvider(root, given);
|
|
112
|
+
if (!provider)
|
|
113
113
|
return;
|
|
114
114
|
console.log('Embedding all memories (this may take a moment on first run to download model)...');
|
|
115
115
|
let count;
|
|
116
116
|
try {
|
|
117
|
-
count = await embedAll(root,
|
|
117
|
+
count = await embedAll(root, undefined, provider);
|
|
118
118
|
}
|
|
119
119
|
catch (err) {
|
|
120
120
|
printError(`Embedding failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -126,6 +126,11 @@ export async function cmdEmbed(hippoRoot, flags) {
|
|
|
126
126
|
const entriesAfter = loadAllEntries(root);
|
|
127
127
|
const embIndexAfter = loadEmbeddingIndex(root);
|
|
128
128
|
console.log(`Done. ${count} new embeddings created. ${Object.keys(embIndexAfter).length}/${entriesAfter.length} total.`);
|
|
129
|
+
const unembedded = entriesAfter.filter((e) => !embIndexAfter[e.id]).length;
|
|
130
|
+
if (unembedded > 0) {
|
|
131
|
+
printError(`${unembedded} memories are still not embedded (the warnings above name them). Re-run \`hippo embed\` to retry.`);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
}
|
|
129
134
|
}
|
|
130
135
|
function resetPhysics(root) {
|
|
131
136
|
const entries = loadAllEntries(root);
|
|
@@ -154,10 +159,10 @@ function printEmbedStatus(root) {
|
|
|
154
159
|
console.log(` ${missing.length} memories need embedding (run \`hippo embed\` to embed them)`);
|
|
155
160
|
}
|
|
156
161
|
}
|
|
157
|
-
/**
|
|
158
|
-
function
|
|
162
|
+
/** The provider to embed with, or null after saying why none is usable. */
|
|
163
|
+
function readyEmbedProvider(root, given) {
|
|
159
164
|
// Embedding (unlike status/reset) needs an available provider.
|
|
160
|
-
const embedProvider = (() => {
|
|
165
|
+
const embedProvider = given ?? (() => {
|
|
161
166
|
try {
|
|
162
167
|
return resolveEmbeddingProvider(root);
|
|
163
168
|
}
|
|
@@ -168,23 +173,24 @@ function embedProviderReady(root) {
|
|
|
168
173
|
})();
|
|
169
174
|
if (!embedProvider) {
|
|
170
175
|
process.exitCode = 1;
|
|
171
|
-
return
|
|
176
|
+
return null;
|
|
172
177
|
}
|
|
173
178
|
if (embedProvider.isAvailable())
|
|
174
|
-
return
|
|
179
|
+
return embedProvider;
|
|
175
180
|
if (loadConfig(root).embeddings.enabled === false) {
|
|
176
181
|
console.log('Embeddings are disabled in config (embeddings.enabled = false). Set it to true or "auto" to enable.');
|
|
177
|
-
return
|
|
182
|
+
return null;
|
|
178
183
|
}
|
|
179
184
|
if (embedProvider.kind === 'local') {
|
|
180
185
|
console.log('Embeddings not available. Install @huggingface/transformers to enable:');
|
|
181
186
|
console.log(' npm install @huggingface/transformers');
|
|
187
|
+
process.exitCode = 1;
|
|
182
188
|
}
|
|
183
189
|
else {
|
|
184
190
|
printError(`Embedding provider '${embedProvider.kind}' is configured but ${embedProvider.keyEnv} is not set.`);
|
|
185
191
|
printError(`Export ${embedProvider.keyEnv}, or set config.embeddings.provider back to 'local'.`);
|
|
186
192
|
process.exitCode = 1;
|
|
187
193
|
}
|
|
188
|
-
return
|
|
194
|
+
return null;
|
|
189
195
|
}
|
|
190
196
|
//# sourceMappingURL=maintenance.js.map
|
package/dist/cli/recall.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CliFlags, type CommandContext } from './shared.js';
|
|
2
|
-
/** Test-only: reset the
|
|
2
|
+
/** Test-only: reset the CLI recall rings. Call from beforeEach. */
|
|
3
3
|
export declare function __resetSessionRecallHistoryCli(): void;
|
|
4
4
|
/** Runs `hippo recall`: parse, rank, load continuity, fit the printed block to the budget, audit, then write and print. */
|
|
5
5
|
export declare function cmdRecall(hippoRoot: string, query: string, flags: CliFlags): Promise<void>;
|
package/dist/cli/recall.js
CHANGED
|
@@ -20,10 +20,10 @@ import { dropHeldCopies } from '../same-text.js';
|
|
|
20
20
|
import { isGlobalStoreRoot } from '../project-identity.js';
|
|
21
21
|
import { detectScope } from '../scope.js';
|
|
22
22
|
import { getGlobalRoot } from '../shared.js';
|
|
23
|
-
import { auditQueryFields } from '../audit.js';
|
|
24
23
|
import * as api from '../api.js';
|
|
25
24
|
import { computePlanningFallacyOutput } from '../predictions/planning-fallacy.js';
|
|
26
|
-
import { detectAnchoring, hashQueryText, biasHintEnabled,
|
|
25
|
+
import { detectAnchoring, hashQueryText, biasHintEnabled, snapshotRing } from '../recall-history.js';
|
|
26
|
+
import { appendRecallAudit, recallAuditMetadata, recordShownRecall, resetSessionRings, sessionRing } from '../api/recall-record.js';
|
|
27
27
|
import { detectAvailabilityBias } from '../availability.js';
|
|
28
28
|
import { resolveTenantId } from '../tenant.js';
|
|
29
29
|
import { MAX_HOPS, DEFAULT_MAX_NEIGHBORS } from '../graph-recall.js';
|
|
@@ -34,13 +34,12 @@ import { isClefModel } from '../rerankers/clef.js';
|
|
|
34
34
|
import { handoffText, printedTokens, sessionTrailText, settleTokens, snapshotText } from '../context-render.js';
|
|
35
35
|
import { withLedgerDb } from '../ledger-db.js';
|
|
36
36
|
import { printError } from './output.js';
|
|
37
|
-
import { parseLimitFlag, parseBudgetFlag,
|
|
37
|
+
import { parseLimitFlag, parseBudgetFlag, requireInit, recallEntryText, recallHeading, parseAsOfFlag, engineFlags, printActiveTaskSnapshot, printSessionEvents, printHandoff, hostSessionId, captureConsole, hookStoreRoot, } from './shared.js';
|
|
38
38
|
// Per-process rings: a single-shot `hippo recall` starts empty, so anchoring only accumulates in long-lived
|
|
39
39
|
// hosts (in-process loops, `hippo serve`, the MCP server).
|
|
40
|
-
|
|
41
|
-
/** Test-only: reset the module-level recall-history Map. Call from beforeEach. */
|
|
40
|
+
/** Test-only: reset the CLI recall rings. Call from beforeEach. */
|
|
42
41
|
export function __resetSessionRecallHistoryCli() {
|
|
43
|
-
|
|
42
|
+
resetSessionRings('cli');
|
|
44
43
|
}
|
|
45
44
|
// JSON.stringify keeps quotes or parens in the matched phrase from blurring the line.
|
|
46
45
|
function planningLine(p) {
|
|
@@ -185,7 +184,7 @@ export async function cmdRecall(hippoRoot, query, flags) {
|
|
|
185
184
|
const o = parseRecallOptions(hippoRoot, flags);
|
|
186
185
|
const ranked = await rankForRecall(hippoRoot, query, flags, o);
|
|
187
186
|
const fit = fitRecallBlock(hippoRoot, query, o, ranked, loadRecallContinuity(hippoRoot, o));
|
|
188
|
-
auditRecall(hippoRoot, o
|
|
187
|
+
auditRecall(hippoRoot, o, query, fit);
|
|
189
188
|
writeRecallResult(hippoRoot, query, o, fit, ranked.localIndex);
|
|
190
189
|
}
|
|
191
190
|
/** Every flag recall reads, parsed in the order the single-body command checked them. */
|
|
@@ -281,9 +280,7 @@ function loadRecallContinuity(hippoRoot, o) {
|
|
|
281
280
|
function recallHinter(query, o, rank) {
|
|
282
281
|
const { tenantId, sessionId } = o;
|
|
283
282
|
// HIPPO_ANCHORING=off and HIPPO_AVAILABILITY=off skip the work entirely.
|
|
284
|
-
const anchorRing =
|
|
285
|
-
? getOrCreateRing(sessionRecallHistoryCli, buildSessionKey(tenantId, sessionId))
|
|
286
|
-
: null;
|
|
283
|
+
const anchorRing = sessionRing('cli', tenantId, sessionId);
|
|
287
284
|
const queryHash = hashQueryText(query);
|
|
288
285
|
const availabilityPool = biasHintEnabled('availability')
|
|
289
286
|
? [...rank.localEntries, ...rank.globalEntries].map((e) => ({ id: e.id, created: e.created }))
|
|
@@ -304,7 +301,7 @@ function recallHinter(query, o, rank) {
|
|
|
304
301
|
});
|
|
305
302
|
return { anchoring, availability, summary };
|
|
306
303
|
};
|
|
307
|
-
return { anchorRing,
|
|
304
|
+
return { anchorRing, hintsFor };
|
|
308
305
|
}
|
|
309
306
|
function renderRecallBlock(list, h, v) {
|
|
310
307
|
const { query, showWhy, showPlan, planText, hasContinuity, entryText } = v;
|
|
@@ -388,7 +385,7 @@ function fitRecallBlock(hippoRoot, query, o, ranked, loaded) {
|
|
|
388
385
|
const shown = (n) => dropHeldCopies(fitted.slice(0, n), (r) => r.entry);
|
|
389
386
|
let kept = fitted.length;
|
|
390
387
|
let results = shown(kept);
|
|
391
|
-
const { anchorRing,
|
|
388
|
+
const { anchorRing, hintsFor } = recallHinter(query, o, ranked.rank);
|
|
392
389
|
const view = { query, showWhy: o.showWhy, showPlan, planText, hasContinuity, continuity, entryText: ranked.entryText };
|
|
393
390
|
let hints = hintsFor(results, kept - results.length);
|
|
394
391
|
let recallText = renderRecallBlock(results, hints, view);
|
|
@@ -399,45 +396,17 @@ function fitRecallBlock(hippoRoot, query, o, ranked, loaded) {
|
|
|
399
396
|
hints = hintsFor(results, kept - results.length);
|
|
400
397
|
recallText = renderRecallBlock(results, hints, view);
|
|
401
398
|
}
|
|
402
|
-
return { results, hints, recallText, continuity, continuityTokens, cmdPlanningFallacyHint, cmdPlanningFallacyWatching, anchorRing
|
|
399
|
+
return { results, hints, recallText, continuity, continuityTokens, cmdPlanningFallacyHint, cmdPlanningFallacyWatching, anchorRing };
|
|
403
400
|
}
|
|
404
|
-
function auditRecall(hippoRoot,
|
|
405
|
-
const { results,
|
|
406
|
-
const {
|
|
407
|
-
|
|
408
|
-
// Appended after every detect: anchoredOn feeds the cooldown for the next recall on this session.
|
|
409
|
-
appendRecall(anchorRing, queryHash, results[0]?.entry.id ?? null, cmdAnchoringHint?.memoryId);
|
|
410
|
-
}
|
|
411
|
-
else if (biasHintEnabled('anchoring')) {
|
|
412
|
-
// SHA-256/16 per the recall-audit convention; hashQueryText is FNV-1a and brute-forceable on short queries.
|
|
413
|
-
emitCliAudit(hippoRoot, 'recall_anchor_skipped_no_session', undefined, auditQueryFields(query));
|
|
414
|
-
}
|
|
415
|
-
if (cmdAnchoringHint?.reason === 'memory_dominance') {
|
|
416
|
-
emitCliAudit(hippoRoot, 'recall_anchor_detected_memory_dominance', cmdAnchoringHint.memoryId, {
|
|
417
|
-
memory_id: cmdAnchoringHint.memoryId,
|
|
418
|
-
query_count: cmdAnchoringHint.queryCount ?? null,
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
else if (cmdAnchoringHint?.reason === 'query_repeat') {
|
|
422
|
-
emitCliAudit(hippoRoot, 'recall_anchor_detected_query_repeat', cmdAnchoringHint.memoryId, {
|
|
423
|
-
memory_id: cmdAnchoringHint.memoryId,
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
if (cmdAvailabilityHint) {
|
|
427
|
-
emitCliAudit(hippoRoot, 'recall_availability_detected', undefined, {
|
|
428
|
-
recent_fraction: cmdAvailabilityHint.recentFraction,
|
|
429
|
-
older_passed_over: cmdAvailabilityHint.olderCandidatesPassedOver,
|
|
430
|
-
returned_count: cmdAvailabilityHint.returnedCount,
|
|
431
|
-
});
|
|
432
|
-
}
|
|
401
|
+
function auditRecall(hippoRoot, o, query, fit) {
|
|
402
|
+
const { results, hints } = fit;
|
|
403
|
+
const who = { hippoRoot, tenantId: o.tenantId, actor: 'cli', bestEffort: true };
|
|
404
|
+
recordShownRecall(who, { query, ring: fit.anchorRing, topId: results[0]?.entry.id ?? null, anchoring: hints.anchoring, availability: hints.availability });
|
|
433
405
|
// One 'recall' event per query, before the early-empty return, in every participating store.
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
emitCliAudit(hippoRoot, 'recall', undefined, recallMetadata);
|
|
439
|
-
if (isInitialized(globalRoot) && globalRoot !== hippoRoot) {
|
|
440
|
-
emitCliAudit(globalRoot, 'recall', undefined, recallMetadata);
|
|
406
|
+
const metadata = recallAuditMetadata(query, results.length);
|
|
407
|
+
appendRecallAudit(who, 'recall', undefined, metadata);
|
|
408
|
+
if (isInitialized(o.globalRoot) && o.globalRoot !== hippoRoot) {
|
|
409
|
+
appendRecallAudit({ ...who, hippoRoot: o.globalRoot }, 'recall', undefined, metadata);
|
|
441
410
|
}
|
|
442
411
|
}
|
|
443
412
|
/** Traces the recall, books retrieval for a non-empty list, then prints JSON or the fitted block. */
|
package/dist/cli/shared.js
CHANGED
|
@@ -10,7 +10,7 @@ import { confidenceLabel } from '../memory.js';
|
|
|
10
10
|
import { isInitialized } from '../store/open.js';
|
|
11
11
|
import { explainMatch } from '../search/explain.js';
|
|
12
12
|
import { isSharedStore } from '../config.js';
|
|
13
|
-
import { openHippoDb, closeHippoDb, isSqliteBusy, noteStoreBusy,
|
|
13
|
+
import { openHippoDb, closeHippoDb, isSqliteBusy, noteStoreBusy, runWithRequestStores, HOOK_DB_WAIT_MS } from '../db.js';
|
|
14
14
|
import { withLedgerDb } from '../ledger-db.js';
|
|
15
15
|
import { sessionPilotArm } from '../pilot-arm.js';
|
|
16
16
|
import { hookPayloadSessionId, isSubagentPayload, recordTokenUse } from '../token-ledger.js';
|
|
@@ -474,7 +474,7 @@ export function resolveAuthRoot(hippoRoot, flags) {
|
|
|
474
474
|
/** Hook commands share one handle per store and wait at most HOOK_DB_WAIT_MS for a lock; a store still busy after that skips the hook's work with one warning, exit 0. */
|
|
475
475
|
export async function runHookWithStores(fn) {
|
|
476
476
|
try {
|
|
477
|
-
return await
|
|
477
|
+
return await runWithRequestStores(fn, { busyWaitMs: HOOK_DB_WAIT_MS, failFastWhenBusy: true });
|
|
478
478
|
}
|
|
479
479
|
catch (error) {
|
|
480
480
|
if (!isSqliteBusy(error))
|
package/dist/cli.d.ts
CHANGED
|
@@ -54,14 +54,17 @@ export declare const COMMANDS: {
|
|
|
54
54
|
};
|
|
55
55
|
recall: {
|
|
56
56
|
run: (c: CommandContext) => Promise<void>;
|
|
57
|
+
scoped: true;
|
|
57
58
|
usage: string[];
|
|
58
59
|
};
|
|
59
60
|
drill: {
|
|
60
61
|
run: (c: CommandContext) => Promise<void>;
|
|
62
|
+
scoped: true;
|
|
61
63
|
usage: string[];
|
|
62
64
|
};
|
|
63
65
|
assemble: {
|
|
64
66
|
run: (c: CommandContext) => Promise<void>;
|
|
67
|
+
scoped: true;
|
|
65
68
|
usage: string[];
|
|
66
69
|
};
|
|
67
70
|
supersede: {
|
|
@@ -70,6 +73,7 @@ export declare const COMMANDS: {
|
|
|
70
73
|
};
|
|
71
74
|
explain: {
|
|
72
75
|
run: (c: CommandContext) => Promise<void>;
|
|
76
|
+
scoped: true;
|
|
73
77
|
usage: string[];
|
|
74
78
|
};
|
|
75
79
|
eval: {
|
|
@@ -86,6 +90,7 @@ export declare const COMMANDS: {
|
|
|
86
90
|
};
|
|
87
91
|
sleep: {
|
|
88
92
|
run: ({ hippoRoot, flags }: CommandContext) => Promise<void>;
|
|
93
|
+
scoped: true;
|
|
89
94
|
usage: string[];
|
|
90
95
|
};
|
|
91
96
|
'last-sleep': {
|
|
@@ -134,10 +139,12 @@ export declare const COMMANDS: {
|
|
|
134
139
|
};
|
|
135
140
|
auth: {
|
|
136
141
|
run: ({ hippoRoot, args, flags }: CommandContext) => Promise<void>;
|
|
142
|
+
scoped: true;
|
|
137
143
|
usage: string[];
|
|
138
144
|
};
|
|
139
145
|
goal: {
|
|
140
146
|
run: ({ hippoRoot, args, flags }: CommandContext) => Promise<void>;
|
|
147
|
+
scoped: true;
|
|
141
148
|
usage: string[];
|
|
142
149
|
};
|
|
143
150
|
slack: {
|
|
@@ -150,6 +157,7 @@ export declare const COMMANDS: {
|
|
|
150
157
|
};
|
|
151
158
|
audit: {
|
|
152
159
|
run: (c: CommandContext) => Promise<void>;
|
|
160
|
+
scoped: true;
|
|
153
161
|
usage: string[];
|
|
154
162
|
};
|
|
155
163
|
'correction-latency': {
|
|
@@ -166,6 +174,7 @@ export declare const COMMANDS: {
|
|
|
166
174
|
};
|
|
167
175
|
outcome: {
|
|
168
176
|
run: ({ hippoRoot, flags }: CommandContext) => Promise<void>;
|
|
177
|
+
scoped: true;
|
|
169
178
|
usage: string[];
|
|
170
179
|
};
|
|
171
180
|
conflicts: {
|
|
@@ -190,6 +199,7 @@ export declare const COMMANDS: {
|
|
|
190
199
|
};
|
|
191
200
|
dormant: {
|
|
192
201
|
run: ({ hippoRoot, args, flags }: CommandContext) => Promise<void>;
|
|
202
|
+
scoped: true;
|
|
193
203
|
usage: string[];
|
|
194
204
|
};
|
|
195
205
|
projects: {
|
|
@@ -198,14 +208,17 @@ export declare const COMMANDS: {
|
|
|
198
208
|
};
|
|
199
209
|
quarantine: {
|
|
200
210
|
run: ({ hippoRoot, args, flags }: CommandContext) => Promise<void>;
|
|
211
|
+
scoped: true;
|
|
201
212
|
usage: string[];
|
|
202
213
|
};
|
|
203
214
|
tokens: {
|
|
204
215
|
run: ({ hippoRoot, flags }: CommandContext) => Promise<void>;
|
|
216
|
+
scoped: true;
|
|
205
217
|
usage: string[];
|
|
206
218
|
};
|
|
207
219
|
failures: {
|
|
208
220
|
run: ({ hippoRoot, flags }: CommandContext) => Promise<void>;
|
|
221
|
+
scoped: true;
|
|
209
222
|
usage: string[];
|
|
210
223
|
};
|
|
211
224
|
doctor: {
|
|
@@ -242,6 +255,7 @@ export declare const COMMANDS: {
|
|
|
242
255
|
};
|
|
243
256
|
forget: {
|
|
244
257
|
run: (c: CommandContext) => Promise<void>;
|
|
258
|
+
scoped: true;
|
|
245
259
|
usage: string[];
|
|
246
260
|
};
|
|
247
261
|
inspect: {
|
|
@@ -274,10 +288,12 @@ export declare const COMMANDS: {
|
|
|
274
288
|
};
|
|
275
289
|
learn: {
|
|
276
290
|
run: ({ hippoRoot, flags }: CommandContext) => Promise<void>;
|
|
291
|
+
scoped: true;
|
|
277
292
|
usage: string[];
|
|
278
293
|
};
|
|
279
294
|
promote: {
|
|
280
295
|
run: (c: CommandContext) => Promise<void>;
|
|
296
|
+
scoped: true;
|
|
281
297
|
usage: string[];
|
|
282
298
|
};
|
|
283
299
|
sync: {
|
package/dist/cli.js
CHANGED
|
@@ -209,14 +209,17 @@ export const COMMANDS = {
|
|
|
209
209
|
},
|
|
210
210
|
recall: {
|
|
211
211
|
run: async (c) => { await (await import('./cli/recall.js')).handleRecall(c); },
|
|
212
|
+
scoped: true,
|
|
212
213
|
usage: VERB_USAGE.recall,
|
|
213
214
|
},
|
|
214
215
|
drill: {
|
|
215
216
|
run: async (c) => { await (await import('./cli/dag.js')).handleDrill(c); },
|
|
217
|
+
scoped: true,
|
|
216
218
|
usage: VERB_USAGE.drill,
|
|
217
219
|
},
|
|
218
220
|
assemble: {
|
|
219
221
|
run: async (c) => { await (await import('./cli/dag.js')).handleAssemble(c); },
|
|
222
|
+
scoped: true,
|
|
220
223
|
usage: VERB_USAGE.assemble,
|
|
221
224
|
},
|
|
222
225
|
supersede: {
|
|
@@ -225,6 +228,7 @@ export const COMMANDS = {
|
|
|
225
228
|
},
|
|
226
229
|
explain: {
|
|
227
230
|
run: async (c) => { await (await import('./cli/explain.js')).handleExplain(c); },
|
|
231
|
+
scoped: true,
|
|
228
232
|
usage: VERB_USAGE.explain,
|
|
229
233
|
},
|
|
230
234
|
eval: {
|
|
@@ -241,6 +245,7 @@ export const COMMANDS = {
|
|
|
241
245
|
},
|
|
242
246
|
sleep: {
|
|
243
247
|
run: async ({ hippoRoot, flags }) => { await (await import('./cli/sleep.js')).cmdSleep(hippoRoot, flags); },
|
|
248
|
+
scoped: true,
|
|
244
249
|
usage: VERB_USAGE.sleep,
|
|
245
250
|
},
|
|
246
251
|
'last-sleep': {
|
|
@@ -289,10 +294,12 @@ export const COMMANDS = {
|
|
|
289
294
|
},
|
|
290
295
|
auth: {
|
|
291
296
|
run: async ({ hippoRoot, args, flags }) => { (await import('./cli/auth.js')).cmdAuth(hippoRoot, args, flags); },
|
|
297
|
+
scoped: true,
|
|
292
298
|
usage: VERB_USAGE.auth,
|
|
293
299
|
},
|
|
294
300
|
goal: {
|
|
295
301
|
run: async ({ hippoRoot, args, flags }) => { (await import('./cli/goals.js')).cmdGoal(hippoRoot, args, flags); },
|
|
302
|
+
scoped: true,
|
|
296
303
|
usage: VERB_USAGE.goal,
|
|
297
304
|
},
|
|
298
305
|
slack: {
|
|
@@ -305,6 +312,7 @@ export const COMMANDS = {
|
|
|
305
312
|
},
|
|
306
313
|
audit: {
|
|
307
314
|
run: async (c) => { await (await import('./cli/audit.js')).handleAudit(c); },
|
|
315
|
+
scoped: true,
|
|
308
316
|
usage: VERB_USAGE.audit,
|
|
309
317
|
},
|
|
310
318
|
'correction-latency': {
|
|
@@ -321,6 +329,7 @@ export const COMMANDS = {
|
|
|
321
329
|
},
|
|
322
330
|
outcome: {
|
|
323
331
|
run: async ({ hippoRoot, flags }) => { (await import('./cli/curate.js')).cmdOutcome(hippoRoot, flags); },
|
|
332
|
+
scoped: true,
|
|
324
333
|
usage: VERB_USAGE.outcome,
|
|
325
334
|
},
|
|
326
335
|
conflicts: {
|
|
@@ -345,6 +354,7 @@ export const COMMANDS = {
|
|
|
345
354
|
},
|
|
346
355
|
dormant: {
|
|
347
356
|
run: async ({ hippoRoot, args, flags }) => { (await import('./cli/curate.js')).cmdDormant(hippoRoot, args, flags); },
|
|
357
|
+
scoped: true,
|
|
348
358
|
usage: VERB_USAGE.dormant,
|
|
349
359
|
},
|
|
350
360
|
projects: {
|
|
@@ -353,14 +363,17 @@ export const COMMANDS = {
|
|
|
353
363
|
},
|
|
354
364
|
quarantine: {
|
|
355
365
|
run: async ({ hippoRoot, args, flags }) => { (await import('./cli/curate.js')).cmdQuarantine(hippoRoot, args, flags); },
|
|
366
|
+
scoped: true,
|
|
356
367
|
usage: VERB_USAGE.quarantine,
|
|
357
368
|
},
|
|
358
369
|
tokens: {
|
|
359
370
|
run: async ({ hippoRoot, flags }) => { (await import('./cli/status.js')).cmdTokens(hippoRoot, flags); },
|
|
371
|
+
scoped: true,
|
|
360
372
|
usage: VERB_USAGE.tokens,
|
|
361
373
|
},
|
|
362
374
|
failures: {
|
|
363
375
|
run: async ({ hippoRoot, flags }) => { (await import('./cli/status.js')).cmdFailures(hippoRoot, flags); },
|
|
376
|
+
scoped: true,
|
|
364
377
|
usage: VERB_USAGE.failures,
|
|
365
378
|
},
|
|
366
379
|
doctor: {
|
|
@@ -397,6 +410,7 @@ export const COMMANDS = {
|
|
|
397
410
|
},
|
|
398
411
|
forget: {
|
|
399
412
|
run: async (c) => { await (await import('./cli/curate.js')).handleForget(c); },
|
|
413
|
+
scoped: true,
|
|
400
414
|
usage: VERB_USAGE.forget,
|
|
401
415
|
},
|
|
402
416
|
inspect: {
|
|
@@ -429,10 +443,12 @@ export const COMMANDS = {
|
|
|
429
443
|
},
|
|
430
444
|
learn: {
|
|
431
445
|
run: async ({ hippoRoot, flags }) => { (await import('./cli/transfer.js')).cmdLearn(hippoRoot, flags); },
|
|
446
|
+
scoped: true,
|
|
432
447
|
usage: VERB_USAGE.learn,
|
|
433
448
|
},
|
|
434
449
|
promote: {
|
|
435
450
|
run: async (c) => { await (await import('./cli/transfer.js')).handlePromote(c); },
|
|
451
|
+
scoped: true,
|
|
436
452
|
usage: VERB_USAGE.promote,
|
|
437
453
|
},
|
|
438
454
|
sync: {
|
|
@@ -630,7 +646,8 @@ async function main(command, args, flags, hippoRoot) {
|
|
|
630
646
|
printUsage();
|
|
631
647
|
process.exit(1);
|
|
632
648
|
}
|
|
633
|
-
|
|
649
|
+
const run = () => spec.run({ hippoRoot, args, flags });
|
|
650
|
+
await (spec.scoped ? (await import('./db/request-stores.js')).runWithRequestStores(run) : run());
|
|
634
651
|
}
|
|
635
652
|
export async function runCli(argv = process.argv) {
|
|
636
653
|
const { command, args, flags } = parseArgs(argv);
|
package/dist/config.d.ts
CHANGED
|
@@ -159,6 +159,8 @@ export declare function loadConfig(hippoRoot: string): HippoConfig;
|
|
|
159
159
|
/** True when the store's config.json sets `"sharedStore": true`. Once true, a root stays true for this process,
|
|
160
160
|
* so a broken edit while a server runs cannot turn it off. Reads only this key: loadConfig has no cache. */
|
|
161
161
|
export declare function isSharedStore(hippoRoot: string): boolean;
|
|
162
|
+
/** Reads the root as shared for the rest of this process, whatever its config.json says. */
|
|
163
|
+
export declare function markSharedStore(hippoRoot: string): void;
|
|
162
164
|
/** Test seam: forget every root seen as shared. */
|
|
163
165
|
export declare function _resetSharedStoreCacheForTests(): void;
|
|
164
166
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.js
CHANGED
|
@@ -321,6 +321,10 @@ export function isSharedStore(hippoRoot) {
|
|
|
321
321
|
sharedStoreRoots.add(key);
|
|
322
322
|
return true;
|
|
323
323
|
}
|
|
324
|
+
/** Reads the root as shared for the rest of this process, whatever its config.json says. */
|
|
325
|
+
export function markSharedStore(hippoRoot) {
|
|
326
|
+
sharedStoreRoots.add(sharedStoreKey(hippoRoot));
|
|
327
|
+
}
|
|
324
328
|
/** Test seam: forget every root seen as shared. */
|
|
325
329
|
export function _resetSharedStoreCacheForTests() {
|
|
326
330
|
sharedStoreRoots.clear();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type DatabaseSyncLike } from './sqlite.js';
|
|
2
|
+
export declare function getHippoDbPath(hippoRoot: string): string;
|
|
3
|
+
/** A new connection with the store's pragmas, migrations and mirror cleanup applied; the caller owns and closes it. */
|
|
4
|
+
export declare function connectHippoDb(hippoRoot: string, busyWaitMs?: number): DatabaseSyncLike;
|
|
5
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { cleanupArchivedMirrors } from '../raw-archive-mirror-cleanup.js';
|
|
4
|
+
import { log } from '../log.js';
|
|
5
|
+
import { DatabaseSync } from './sqlite.js';
|
|
6
|
+
import { execWithBusyRetry } from './busy.js';
|
|
7
|
+
import { runMigrations } from './migrate.js';
|
|
8
|
+
export function getHippoDbPath(hippoRoot) {
|
|
9
|
+
return path.join(hippoRoot, 'hippo.db');
|
|
10
|
+
}
|
|
11
|
+
// Owner-only on create; SQLite gives the WAL and SHM files the db's mode. Existing paths keep theirs.
|
|
12
|
+
function createStoreFilesOwnerOnly(hippoRoot) {
|
|
13
|
+
fs.mkdirSync(hippoRoot, { recursive: true, mode: 0o700 });
|
|
14
|
+
try {
|
|
15
|
+
fs.closeSync(fs.openSync(getHippoDbPath(hippoRoot), 'wx', 0o600));
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if (!(err instanceof Error && 'code' in err && err.code === 'EEXIST'))
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** A new connection with the store's pragmas, migrations and mirror cleanup applied; the caller owns and closes it. */
|
|
23
|
+
export function connectHippoDb(hippoRoot, busyWaitMs) {
|
|
24
|
+
createStoreFilesOwnerOnly(hippoRoot);
|
|
25
|
+
const db = new DatabaseSync(getHippoDbPath(hippoRoot));
|
|
26
|
+
try {
|
|
27
|
+
db.exec(`PRAGMA busy_timeout = ${busyWaitMs ?? 5000}`);
|
|
28
|
+
execWithBusyRetry(db, 'PRAGMA journal_mode = WAL', busyWaitMs);
|
|
29
|
+
db.exec('PRAGMA synchronous = NORMAL');
|
|
30
|
+
db.exec('PRAGMA wal_autocheckpoint = 100');
|
|
31
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
32
|
+
runMigrations(db, hippoRoot, busyWaitMs);
|
|
33
|
+
// Orphan mirrors of archived raw rows go here; a filesystem failure must not block the open.
|
|
34
|
+
try {
|
|
35
|
+
cleanupArchivedMirrors(hippoRoot, db);
|
|
36
|
+
}
|
|
37
|
+
catch (cleanupErr) {
|
|
38
|
+
log.error(`openHippoDb: cleanupArchivedMirrors failed (non-fatal): ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`);
|
|
39
|
+
}
|
|
40
|
+
return db;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
try {
|
|
44
|
+
db.close();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Best effort only.
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=connect.js.map
|
package/dist/db/open.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type DatabaseSyncLike } from './sqlite.js';
|
|
2
|
-
|
|
2
|
+
import { getHippoDbPath } from './connect.js';
|
|
3
|
+
export { getHippoDbPath };
|
|
3
4
|
/** Lock wait for hook commands: under the 5 s prompt-hook budget even after a few skipped writes, and far above a normal write's hold. */
|
|
4
5
|
export declare const HOOK_DB_WAIT_MS = 1000;
|
|
6
|
+
/** Lock wait inside an HTTP request: SQLite waits synchronously, so a long wait would stall every other request on the event loop. */
|
|
7
|
+
export declare const SERVER_DB_WAIT_MS = 250;
|
|
5
8
|
/** A busy store made a command skip work: warn once per process (the holder is usually `hippo sleep`). */
|
|
6
9
|
export declare function noteStoreBusy(skipped: string): void;
|
|
7
10
|
/** Runs `fn` with one handle per store: openHippoDb reuses it and closeHippoDb leaves it open until `fn` settles or the process exits.
|
|
@@ -9,21 +12,16 @@ export declare function noteStoreBusy(skipped: string): void;
|
|
|
9
12
|
export declare function withSharedStoreHandles<T>(fn: () => T | Promise<T>, opts?: {
|
|
10
13
|
busyWaitMs?: number;
|
|
11
14
|
}): Promise<T>;
|
|
12
|
-
/** Lock wait inside an HTTP request: SQLite waits synchronously, so a long wait would stall every other request on the event loop. */
|
|
13
|
-
export declare const SERVER_DB_WAIT_MS = 250;
|
|
14
15
|
/** Lock wait for each of sleep's short write transactions, even inside a server request: a run stops only on a writer that holds the lock longer. */
|
|
15
16
|
export declare const SLEEP_DB_WAIT_MS = 5000;
|
|
16
|
-
/**
|
|
17
|
-
export declare function withBusyWait<T>(busyWaitMs: number, fn: () => T): T;
|
|
18
|
-
/** The lock wait an open here would get without its own `busyWaitMs`, in openHippoDb's order; undefined outside both scopes. */
|
|
17
|
+
/** The lock wait an open here would get without its own `busyWaitMs`: the request scope's; undefined outside every scope. */
|
|
19
18
|
export declare function scopedBusyWait(): number | undefined;
|
|
20
|
-
/** Thrown by a hippo.db open inside a request served from another store: the code path is not ported to the store port yet. */
|
|
21
|
-
export declare class SqliteBlockedError extends Error {
|
|
22
|
-
readonly storeKind: string;
|
|
23
|
-
constructor(storeKind: string);
|
|
24
|
-
}
|
|
25
19
|
/** Runs `fn` so that every hippo.db open inside it, across awaits, throws; otherwise a missed port would create and write a hippo.db nobody reads. */
|
|
26
20
|
export declare function withSqliteBlocked<T>(storeKind: string, fn: () => T): T;
|
|
21
|
+
/** Runs `fn` with hippo.db opens allowed again, for a store whose own methods are backed by hippo.db. */
|
|
22
|
+
export declare function withSqliteAllowed<T>(fn: () => T): T;
|
|
23
|
+
/** First line of a best-effort catch around a hippo.db open: an unported path must fail closed, not fall back silently. */
|
|
24
|
+
export declare function rethrowIfSqliteBlocked<E>(err: E): void;
|
|
27
25
|
/** `busyWaitMs` shortens every lock wait of this open, for a hook that must finish inside its own timeout. */
|
|
28
26
|
export declare function openHippoDb(hippoRoot: string, opts?: {
|
|
29
27
|
busyWaitMs?: number;
|