hippo-memory 1.9.3 → 1.10.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 +5 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +74 -19
- package/dist/cli.js.map +1 -1
- package/dist/rrf.d.ts +43 -0
- package/dist/rrf.d.ts.map +1 -0
- package/dist/rrf.js +61 -0
- package/dist/rrf.js.map +1 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +11 -17
- package/dist/search.js.map +1 -1
- package/dist/server-detect.d.ts +25 -4
- package/dist/server-detect.d.ts.map +1 -1
- package/dist/server-detect.js +125 -5
- package/dist/server-detect.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +19 -2
- package/dist/server.js.map +1 -1
- package/dist/src/cli.js +74 -19
- package/dist/src/cli.js.map +1 -1
- package/dist/src/rrf.js +61 -0
- package/dist/src/rrf.js.map +1 -0
- package/dist/src/search.js +11 -17
- package/dist/src/search.js.map +1 -1
- package/dist/src/server-detect.js +125 -5
- package/dist/src/server-detect.js.map +1 -1
- package/dist/src/server.js +19 -2
- package/dist/src/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,6 +85,11 @@ hippo recall "data pipeline issues" --budget 2000
|
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
+
### What's new in v1.10.0
|
|
89
|
+
|
|
90
|
+
- **Server and lifecycle hardening.** Closes the `TODOS.md` "server / lifecycle hardening" cluster (deferred follow-ups from the v0.37 server-mode work, the v0.40 security pass, and the A3 envelope review), six items in all. `detectServer` is now async and confirms a recorded server is genuinely this hippo process by matching a `/health` `started_at` before the CLI routes to it (H1). The pidfile carries a `schema` version (L3). `hippo serve` refuses to start when a live peer already serves the hippoRoot (H3). The 413 over-cap-body path closes the socket instead of draining the rest (M3). A `HIPPO_REQUIRE_SERVER` env knob turns a missing server into a loud error instead of a silent direct-mode fallback that discards `HIPPO_API_KEY` (H2). And `hippo forget --archive --reason` gives raw, append-only memories a real removal path via `archiveRaw` instead of a misleading "not found" (A3).
|
|
91
|
+
- **Reviewed via the dev-framework chain.** self-review, an independent code review, a cross-model codex (gpt-5.5) pass, and a security pass. Four findings were fixed before ship: the `forget --archive` server-routing bypass, an unbounded `/health` response parse, a timeout that wrongly unlinked a busy server's pidfile, and pidfile-url validation against off-box redirection. Full suite green: 216 files, 1557 tests.
|
|
92
|
+
|
|
88
93
|
### What's new in v1.9.3
|
|
89
94
|
|
|
90
95
|
- **Reranker review-tail patch.** Closes the three follow-ups raised on PR #25: `src/rerankers/llm.ts` now wires `AbortController` + `setTimeout` around the fetch (default 30 s, overridable via `HIPPO_LLM_RERANKER_TIMEOUT_MS`) so recall never hangs on a wedged endpoint; `src/rerankers/cross-encoder.ts` emits a single `console.warn` on first identity-fallback per process so silent fallback no longer masquerades as a working reranker; the orphan `RerankSignals` type (sole consumer retracted in v1.9.1) is removed at both the re-export and the definition.
|
package/dist/cli.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* hippo session <log|show|latest|resume|complete>
|
|
15
15
|
* hippo handoff <create|latest|show>
|
|
16
16
|
* hippo current <show>
|
|
17
|
-
* hippo forget <id>
|
|
17
|
+
* hippo forget <id> [--archive --reason "<why>"]
|
|
18
18
|
* hippo inspect <id>
|
|
19
19
|
* hippo embed [--status]
|
|
20
20
|
* hippo watch "<command>"
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* hippo session <log|show|latest|resume|complete>
|
|
15
15
|
* hippo handoff <create|latest|show>
|
|
16
16
|
* hippo current <show>
|
|
17
|
-
* hippo forget <id>
|
|
17
|
+
* hippo forget <id> [--archive --reason "<why>"]
|
|
18
18
|
* hippo inspect <id>
|
|
19
19
|
* hippo embed [--status]
|
|
20
20
|
* hippo watch "<command>"
|
|
@@ -117,6 +117,19 @@ function requireInit(hippoRoot) {
|
|
|
117
117
|
process.exit(1);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* H2: when HIPPO_REQUIRE_SERVER is set, the CLI must not silently fall back to
|
|
122
|
+
* direct DB mode — a missing server then masks a real misconfiguration (the
|
|
123
|
+
* configured HIPPO_API_KEY is also silently discarded on fallback). Throws a
|
|
124
|
+
* clear, actionable error in that case; a no-op when the knob is unset, so
|
|
125
|
+
* default behaviour is unchanged.
|
|
126
|
+
*/
|
|
127
|
+
function failIfServerRequired(reason) {
|
|
128
|
+
if (process.env['HIPPO_REQUIRE_SERVER']) {
|
|
129
|
+
throw new Error(`hippo: HIPPO_REQUIRE_SERVER is set but ${reason}. ` +
|
|
130
|
+
`Start \`hippo serve\`, or unset HIPPO_REQUIRE_SERVER to allow direct-mode fallback.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
120
133
|
/**
|
|
121
134
|
* Run an HTTP-routed command if a `hippo serve` instance is detected for
|
|
122
135
|
* `hippoRoot`. Returns:
|
|
@@ -127,11 +140,16 @@ function requireInit(hippoRoot) {
|
|
|
127
140
|
* and the caller should fall back to the direct path.
|
|
128
141
|
*
|
|
129
142
|
* Per the A1 plan footgun #1: stale pidfiles must self-heal, not crash.
|
|
143
|
+
* H2: when HIPPO_REQUIRE_SERVER is set, both fallback paths throw instead of
|
|
144
|
+
* returning false, so a missing server fails loudly rather than silently
|
|
145
|
+
* degrading to direct mode.
|
|
130
146
|
*/
|
|
131
147
|
async function runViaServerIfAvailable(hippoRoot, httpFn) {
|
|
132
|
-
const info = detectServer(hippoRoot);
|
|
133
|
-
if (!info)
|
|
148
|
+
const info = await detectServer(hippoRoot);
|
|
149
|
+
if (!info) {
|
|
150
|
+
failIfServerRequired('no running server was detected for this hippoRoot');
|
|
134
151
|
return false;
|
|
152
|
+
}
|
|
135
153
|
const apiKey = process.env['HIPPO_API_KEY'];
|
|
136
154
|
try {
|
|
137
155
|
await httpFn(info, apiKey);
|
|
@@ -139,6 +157,7 @@ async function runViaServerIfAvailable(hippoRoot, httpFn) {
|
|
|
139
157
|
}
|
|
140
158
|
catch (err) {
|
|
141
159
|
if (client.isConnectionRefused(err)) {
|
|
160
|
+
failIfServerRequired('the server pidfile was stale (connection refused)');
|
|
142
161
|
console.error('hippo: stale server pidfile detected, falling back to direct mode');
|
|
143
162
|
removePidfile(hippoRoot);
|
|
144
163
|
return false;
|
|
@@ -2363,20 +2382,49 @@ function cmdOutcome(hippoRoot, flags) {
|
|
|
2363
2382
|
}
|
|
2364
2383
|
console.log(`Applied ${good ? 'positive' : 'negative'} outcome to ${updated} memor${updated === 1 ? 'y' : 'ies'}`);
|
|
2365
2384
|
}
|
|
2366
|
-
function cmdForget(hippoRoot, id) {
|
|
2385
|
+
function cmdForget(hippoRoot, id, flags) {
|
|
2367
2386
|
requireInit(hippoRoot);
|
|
2368
2387
|
const ctx = {
|
|
2369
2388
|
hippoRoot,
|
|
2370
2389
|
tenantId: resolveTenantId({}),
|
|
2371
2390
|
actor: 'cli',
|
|
2372
2391
|
};
|
|
2392
|
+
// A3: raw memories (Slack / GitHub connector ingestion) are append-only — a
|
|
2393
|
+
// BEFORE-DELETE trigger aborts any delete. archiveRaw is the sanctioned
|
|
2394
|
+
// removal path; it records ctx.actor as the archiver for provenance.
|
|
2395
|
+
if (flags['archive'] === true) {
|
|
2396
|
+
const reason = typeof flags['reason'] === 'string' ? flags['reason'] : null;
|
|
2397
|
+
if (!reason) {
|
|
2398
|
+
console.error('hippo forget --archive requires --reason "<why>" (recorded on the archive).');
|
|
2399
|
+
process.exit(1);
|
|
2400
|
+
}
|
|
2401
|
+
try {
|
|
2402
|
+
api.archiveRaw(ctx, id, reason);
|
|
2403
|
+
updateStats(hippoRoot, { forgotten: 1 });
|
|
2404
|
+
console.log(`Archived ${id}`);
|
|
2405
|
+
}
|
|
2406
|
+
catch (err) {
|
|
2407
|
+
console.error(`Could not archive ${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2408
|
+
process.exit(1);
|
|
2409
|
+
}
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2373
2412
|
try {
|
|
2374
2413
|
api.forget(ctx, id);
|
|
2375
2414
|
updateStats(hippoRoot, { forgotten: 1 });
|
|
2376
2415
|
console.log(`Forgot ${id}`);
|
|
2377
2416
|
}
|
|
2378
|
-
catch {
|
|
2379
|
-
|
|
2417
|
+
catch (err) {
|
|
2418
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2419
|
+
if (/append-only/i.test(msg)) {
|
|
2420
|
+
// The delete was refused by the append-only trigger — this is a raw
|
|
2421
|
+
// memory, not a missing one. Point the user at the archive path.
|
|
2422
|
+
console.error(`Cannot forget ${id}: it is a raw, append-only memory. ` +
|
|
2423
|
+
`Archive it instead: hippo forget ${id} --archive --reason "<why>"`);
|
|
2424
|
+
}
|
|
2425
|
+
else {
|
|
2426
|
+
console.error(`Memory not found: ${id}`);
|
|
2427
|
+
}
|
|
2380
2428
|
process.exit(1);
|
|
2381
2429
|
}
|
|
2382
2430
|
}
|
|
@@ -4841,6 +4889,8 @@ Commands:
|
|
|
4841
4889
|
current show Active task + recent session events (default)
|
|
4842
4890
|
--json Output as JSON
|
|
4843
4891
|
forget <id> Force remove a memory
|
|
4892
|
+
--archive Archive a raw (append-only) memory instead of deleting
|
|
4893
|
+
--reason "<why>" Reason recorded on the archive (required with --archive)
|
|
4844
4894
|
inspect <id> Show full memory detail
|
|
4845
4895
|
embed Embed all memories for semantic search
|
|
4846
4896
|
--status Show embedding coverage
|
|
@@ -5283,19 +5333,24 @@ async function main() {
|
|
|
5283
5333
|
console.error('Please provide a memory ID.');
|
|
5284
5334
|
process.exit(1);
|
|
5285
5335
|
}
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5336
|
+
// A3: --archive is a direct-DB operation (raw archival via archiveRaw);
|
|
5337
|
+
// the HTTP forget route does not carry it, so archive requests always
|
|
5338
|
+
// take the direct path rather than silently no-op'ing over a server.
|
|
5339
|
+
if (flags['archive'] !== true) {
|
|
5340
|
+
const routed = await runViaServerIfAvailable(hippoRoot, async (info, apiKey) => {
|
|
5341
|
+
try {
|
|
5342
|
+
await client.forget(info.url, apiKey, id);
|
|
5343
|
+
console.log(`Forgot ${id}`);
|
|
5344
|
+
}
|
|
5345
|
+
catch (err) {
|
|
5346
|
+
console.error(err.message);
|
|
5347
|
+
process.exit(1);
|
|
5348
|
+
}
|
|
5349
|
+
});
|
|
5350
|
+
if (routed)
|
|
5351
|
+
break;
|
|
5352
|
+
}
|
|
5353
|
+
cmdForget(hippoRoot, id, flags);
|
|
5299
5354
|
break;
|
|
5300
5355
|
}
|
|
5301
5356
|
case 'inspect': {
|