hippo-memory 1.61.0 → 1.63.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 +37 -53
- package/dist/agent-memories/apply.d.ts +3 -1
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +4 -1
- package/dist/agent-memories/claude-code.js +53 -9
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +46 -24
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/ambient-store.d.ts +4 -4
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +7 -10
- package/dist/api/assemble.js +62 -66
- package/dist/api/audit.d.ts +2 -2
- package/dist/api/audit.js +2 -2
- package/dist/api/auth.d.ts +6 -6
- package/dist/api/auth.js +5 -7
- package/dist/api/context-select.d.ts +50 -0
- package/dist/api/context-select.js +342 -0
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +5 -4
- package/dist/api/context.js +208 -535
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +56 -41
- package/dist/api/outcome.d.ts +8 -13
- package/dist/api/outcome.js +13 -17
- package/dist/api/promote.d.ts +5 -8
- package/dist/api/promote.js +55 -66
- package/dist/api/quarantine.js +3 -2
- package/dist/api/recall-types.d.ts +43 -55
- package/dist/api/recall.d.ts +3 -3
- package/dist/api/recall.js +310 -456
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +206 -220
- package/dist/api/tokens.d.ts +2 -2
- package/dist/api/tokens.js +2 -2
- package/dist/api/types.d.ts +6 -10
- package/dist/api/types.js +2 -4
- package/dist/audit-prune.d.ts +4 -6
- package/dist/audit-prune.js +3 -5
- package/dist/audit.js +11 -33
- package/dist/auth.d.ts +8 -9
- package/dist/auth.js +5 -7
- package/dist/autolearn.d.ts +1 -1
- package/dist/autolearn.js +1 -1
- package/dist/availability.js +3 -5
- package/dist/capture/command.d.ts +5 -4
- package/dist/capture/command.js +8 -22
- package/dist/capture/compact.d.ts +2 -2
- package/dist/capture/compact.js +11 -15
- package/dist/capture/extract.js +37 -115
- package/dist/capture-error.d.ts +1 -1
- package/dist/capture-error.js +1 -1
- package/dist/churn-git.d.ts +1 -1
- package/dist/churn-git.js +1 -1
- package/dist/cli/audit.js +3 -4
- package/dist/cli/auth.js +3 -6
- package/dist/cli/briefs.js +324 -306
- package/dist/cli/context.js +44 -34
- package/dist/cli/continuity.js +283 -271
- package/dist/cli/curate.d.ts +1 -1
- package/dist/cli/curate.js +43 -58
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +334 -345
- package/dist/cli/explain.js +68 -61
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/maintenance.js +62 -51
- package/dist/cli/playbooks.js +391 -379
- package/dist/cli/projects.js +11 -6
- package/dist/cli/recall.js +30 -44
- package/dist/cli/remember.js +118 -87
- package/dist/cli/session-hooks.js +106 -115
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +267 -250
- package/dist/cli/shared.js +3 -3
- package/dist/cli/slack.js +1 -1
- package/dist/cli/sleep.js +27 -1
- package/dist/cli/status.d.ts +4 -4
- package/dist/cli/status.js +80 -76
- package/dist/cli/transfer.js +88 -107
- package/dist/cli/usage.js +9 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +4 -9
- package/dist/compaction-record.d.ts +2 -2
- package/dist/compaction-record.js +89 -68
- package/dist/compare.d.ts +11 -16
- package/dist/compare.js +11 -16
- package/dist/config.d.ts +19 -18
- package/dist/config.js +90 -67
- package/dist/connectors/github/backfill.d.ts +2 -2
- package/dist/connectors/github/backfill.js +8 -15
- package/dist/connectors/github/cli-impl.js +3 -8
- package/dist/connectors/github/deletion.d.ts +5 -12
- package/dist/connectors/github/deletion.js +5 -12
- package/dist/connectors/github/dlq.d.ts +6 -9
- package/dist/connectors/github/dlq.js +2 -3
- package/dist/connectors/github/ingest.d.ts +5 -7
- package/dist/connectors/github/ingest.js +8 -12
- package/dist/connectors/github/octokit-client.d.ts +3 -5
- package/dist/connectors/github/octokit-client.js +5 -6
- package/dist/connectors/github/signature.d.ts +9 -39
- package/dist/connectors/github/signature.js +9 -39
- package/dist/connectors/github/tenant-routing.d.ts +1 -1
- package/dist/connectors/github/tenant-routing.js +1 -1
- package/dist/connectors/github/transform.js +2 -2
- package/dist/connectors/github/types.d.ts +2 -10
- package/dist/connectors/github/types.js +1 -3
- package/dist/connectors/slack/deletion.d.ts +3 -8
- package/dist/connectors/slack/deletion.js +3 -8
- package/dist/connectors/slack/dlq.d.ts +1 -1
- package/dist/connectors/slack/ingest.d.ts +1 -1
- package/dist/connectors/slack/ingest.js +7 -16
- package/dist/connectors/slack/signature.d.ts +1 -1
- package/dist/connectors/slack/tenant-routing.d.ts +3 -5
- package/dist/connectors/slack/tenant-routing.js +3 -5
- package/dist/connectors/slack/transform.d.ts +5 -6
- package/dist/connectors/slack/transform.js +5 -6
- package/dist/connectors/slack/types.d.ts +2 -6
- package/dist/connectors/slack/types.js +1 -3
- package/dist/connectors/slack/web-client.js +10 -3
- package/dist/connectors/slack/workspaces.d.ts +3 -5
- package/dist/connectors/slack/workspaces.js +3 -5
- package/dist/consolidate/conflicts.js +3 -14
- package/dist/consolidate/decay.js +9 -29
- package/dist/consolidate/llm-passes.js +4 -5
- package/dist/consolidate/merge.js +8 -23
- package/dist/consolidate/run.d.ts +1 -8
- package/dist/consolidate/run.js +3 -25
- package/dist/consolidate/sleep.js +5 -17
- package/dist/consolidate/traces.js +9 -21
- package/dist/customer-notes.d.ts +5 -7
- package/dist/customer-notes.js +82 -76
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +189 -203
- package/dist/db/continuity.js +2 -2
- package/dist/db/migrations/v14.js +1 -1
- package/dist/db/migrations/v15.js +1 -2
- package/dist/db/migrations/v16.js +3 -4
- package/dist/db/migrations/v17.js +2 -3
- package/dist/db/migrations/v19.js +1 -1
- package/dist/db/migrations/v20.js +1 -1
- package/dist/db/migrations/v21.js +2 -6
- package/dist/db/migrations/v22.js +2 -4
- package/dist/db/migrations/v23.js +1 -1
- package/dist/db/migrations/v24.js +4 -6
- package/dist/db/migrations/v25.js +2 -3
- package/dist/db/migrations/v26.js +3 -3
- package/dist/db/migrations/v27.js +2 -10
- package/dist/db/migrations/v28.js +5 -8
- package/dist/db/migrations/v29.js +3 -4
- package/dist/db/migrations/v30.js +2 -2
- package/dist/db/migrations/v31.js +1 -1
- package/dist/db/migrations/v32.js +1 -1
- package/dist/db/migrations/v33.js +3 -3
- package/dist/db/migrations/v34.js +1 -1
- package/dist/db/migrations/v35.js +3 -4
- package/dist/db/migrations/v36.js +3 -4
- package/dist/db/migrations/v37.js +5 -5
- package/dist/db/migrations/v38.js +7 -8
- package/dist/db/migrations/v39.js +1 -1
- package/dist/db/migrations/v40.js +4 -16
- package/dist/db/migrations/v41.js +3 -4
- package/dist/db/migrations/v42.js +3 -4
- package/dist/db/migrations/v45.js +1 -1
- package/dist/db/migrations/v46.js +1 -1
- package/dist/db/migrations/v47.js +1 -1
- package/dist/db/migrations/v48.js +1 -1
- package/dist/decisions.d.ts +2 -2
- package/dist/decisions.js +97 -80
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +129 -110
- package/dist/dormant.js +1 -4
- package/dist/embedding-provider.d.ts +4 -8
- package/dist/embedding-provider.js +4 -8
- package/dist/embeddings.js +55 -47
- package/dist/env.d.ts +1 -1
- package/dist/env.js +12 -12
- package/dist/escape.d.ts +5 -0
- package/dist/escape.js +10 -0
- package/dist/eval-stats.d.ts +1 -2
- package/dist/eval-stats.js +1 -2
- package/dist/eval-suite.js +27 -21
- package/dist/extract.js +4 -9
- package/dist/failure-log.d.ts +3 -3
- package/dist/failure-log.js +1 -1
- package/dist/forward-claim-detector.d.ts +2 -4
- package/dist/forward-claim-detector.js +6 -11
- package/dist/goals.d.ts +3 -3
- package/dist/goals.js +103 -91
- package/dist/graph/read.d.ts +2 -2
- package/dist/graph/read.js +5 -6
- package/dist/graph/types.d.ts +8 -8
- package/dist/graph/write.d.ts +7 -14
- package/dist/graph/write.js +16 -23
- package/dist/graph-extract.d.ts +7 -8
- package/dist/graph-extract.js +62 -72
- package/dist/graph-recall.d.ts +2 -2
- package/dist/graph-recall.js +55 -49
- package/dist/graph-stream.d.ts +5 -6
- package/dist/graph-stream.js +66 -57
- package/dist/graph-view.d.ts +2 -2
- package/dist/graph-view.js +7 -7
- package/dist/half-life-migration.d.ts +1 -2
- package/dist/half-life-migration.js +2 -3
- package/dist/hooks/codex-session.js +1 -1
- package/dist/hooks/codex-wrapper.d.ts +1 -1
- package/dist/hooks/codex-wrapper.js +3 -2
- package/dist/hooks/json-hooks.d.ts +2 -2
- package/dist/hooks/json-hooks.js +5 -4
- package/dist/hooks/opencode.d.ts +1 -1
- package/dist/hooks/opencode.js +5 -4
- package/dist/hooks/shared.d.ts +3 -7
- package/dist/hooks/shared.js +1 -8
- package/dist/http-util.d.ts +2 -3
- package/dist/http-util.js +3 -0
- package/dist/importers/core.d.ts +2 -9
- package/dist/importers/core.js +15 -30
- package/dist/importers/sources.js +2 -1
- package/dist/importers/vault.js +2 -20
- package/dist/incidents.d.ts +1 -1
- package/dist/incidents.js +46 -39
- package/dist/instruction-detect.d.ts +1 -1
- package/dist/instruction-detect.js +1 -1
- package/dist/invalidation.d.ts +3 -0
- package/dist/invalidation.js +160 -114
- package/dist/json.d.ts +5 -0
- package/dist/json.js +4 -0
- package/dist/judgment.js +1 -2
- package/dist/local-embedding.js +1 -1
- package/dist/mcp/admin-tools.js +7 -17
- package/dist/mcp/format.js +1 -1
- package/dist/mcp/framing.js +3 -6
- package/dist/mcp/protocol.d.ts +2 -5
- package/dist/mcp/protocol.js +1 -3
- package/dist/mcp/recall-tools.js +12 -15
- package/dist/mcp/request.js +4 -3
- package/dist/mcp/session-state.js +2 -3
- package/dist/mcp/stdio.js +2 -1
- package/dist/mcp/tools.js +9 -6
- package/dist/memory-value-weights.d.ts +5 -8
- package/dist/memory-value-weights.js +5 -8
- package/dist/memory-value.d.ts +13 -13
- package/dist/memory-value.js +26 -37
- package/dist/memory.d.ts +20 -22
- package/dist/memory.js +24 -48
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +3 -2
- package/dist/owner-validation.d.ts +4 -5
- package/dist/owner-validation.js +4 -5
- package/dist/physics.d.ts +4 -4
- package/dist/physics.js +7 -9
- package/dist/policies.d.ts +9 -10
- package/dist/policies.js +96 -81
- package/dist/postinstall.js +3 -6
- package/dist/predictions/planning-fallacy.d.ts +9 -14
- package/dist/predictions/planning-fallacy.js +10 -16
- package/dist/predictions/store.d.ts +15 -23
- package/dist/predictions/store.js +36 -33
- package/dist/processes.d.ts +2 -7
- package/dist/processes.js +88 -72
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +141 -118
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +28 -5
- package/dist/project-merge.js +213 -46
- package/dist/project-remote.d.ts +12 -0
- package/dist/project-remote.js +138 -0
- package/dist/prompt-recall.js +1 -2
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.js +1 -1
- package/dist/raw-archive.d.ts +9 -0
- package/dist/raw-archive.js +70 -53
- package/dist/recall-history.d.ts +19 -20
- package/dist/recall-history.js +24 -42
- package/dist/recall-pipeline.js +4 -28
- package/dist/recall-scope.d.ts +7 -8
- package/dist/recall-scope.js +7 -8
- package/dist/recall-trace.d.ts +5 -9
- package/dist/recall-trace.js +6 -10
- package/dist/refine-llm.d.ts +1 -1
- package/dist/refine-llm.js +2 -2
- package/dist/reject-flow.d.ts +3 -4
- package/dist/reject-flow.js +122 -117
- package/dist/rejection.d.ts +5 -6
- package/dist/rejection.js +7 -15
- package/dist/rerankers/clef.d.ts +1 -1
- package/dist/rerankers/jev.d.ts +1 -2
- package/dist/rerankers/jev.js +4 -5
- package/dist/rerankers/llm.d.ts +1 -2
- package/dist/rerankers/llm.js +1 -2
- package/dist/rerankers/types.d.ts +1 -2
- package/dist/rrf.d.ts +2 -2
- package/dist/rrf.js +2 -2
- package/dist/search/bm25-search.d.ts +1 -1
- package/dist/search/bm25-search.js +2 -1
- package/dist/search/boosts.js +2 -1
- package/dist/search/hybrid.d.ts +1 -1
- package/dist/search/hybrid.js +2 -1
- package/dist/search/physics-search.d.ts +1 -1
- package/dist/search/physics-search.js +2 -1
- package/dist/search/types.d.ts +2 -0
- package/dist/search/types.js +3 -1
- package/dist/secret-detect.d.ts +4 -5
- package/dist/secret-detect.js +6 -10
- package/dist/server/auth.js +5 -5
- package/dist/server/client-ip.js +1 -1
- package/dist/server/cursor.js +2 -1
- package/dist/server/mcp-http.js +4 -4
- package/dist/server/request.d.ts +3 -6
- package/dist/server/request.js +6 -7
- package/dist/server/routes/admin.js +5 -4
- package/dist/server/routes/customer-notes.js +6 -5
- package/dist/server/routes/decisions.js +4 -3
- package/dist/server/routes/incidents.js +7 -5
- package/dist/server/routes/memories.js +7 -7
- package/dist/server/routes/policies.js +3 -2
- package/dist/server/routes/predictions.js +12 -15
- package/dist/server/routes/processes.js +3 -2
- package/dist/server/routes/project-briefs.js +8 -7
- package/dist/server/routes/recall.js +95 -93
- package/dist/server/routes/skills.js +6 -5
- package/dist/server/types.d.ts +1 -1
- package/dist/server/validation.d.ts +1 -2
- package/dist/server/validation.js +7 -14
- package/dist/server-detect.js +72 -58
- package/dist/server.d.ts +2 -2
- package/dist/server.js +131 -117
- package/dist/shared.d.ts +26 -17
- package/dist/shared.js +102 -104
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +88 -72
- package/dist/store/audit-event.d.ts +2 -2
- package/dist/store/audit-event.js +1 -1
- package/dist/store/candidates.d.ts +2 -2
- package/dist/store/candidates.js +4 -3
- package/dist/store/conflicts.js +30 -22
- package/dist/store/delete-and-batch.d.ts +11 -14
- package/dist/store/delete-and-batch.js +40 -91
- package/dist/store/entry-reads.d.ts +17 -25
- package/dist/store/entry-reads.js +59 -40
- package/dist/store/entry-row.d.ts +6 -24
- package/dist/store/entry-row.js +6 -24
- package/dist/store/entry-writes.d.ts +6 -7
- package/dist/store/entry-writes.js +13 -11
- package/dist/store/handoffs.d.ts +1 -1
- package/dist/store/handoffs.js +7 -10
- package/dist/store/index-and-stats.d.ts +2 -6
- package/dist/store/index-and-stats.js +4 -10
- package/dist/store/mirrors.d.ts +6 -19
- package/dist/store/mirrors.js +14 -39
- package/dist/store/open.js +9 -31
- package/dist/store/rows.d.ts +5 -11
- package/dist/store/rows.js +6 -11
- package/dist/store/search-rows.d.ts +17 -34
- package/dist/store/search-rows.js +34 -56
- package/dist/store/sessions.d.ts +4 -5
- package/dist/store/sessions.js +5 -6
- package/dist/store/summaries.d.ts +13 -17
- package/dist/store/summaries.js +26 -70
- package/dist/support-bundle.js +4 -8
- package/dist/tenant.d.ts +1 -5
- package/dist/token-ledger.d.ts +1 -1
- package/dist/token-ledger.js +3 -5
- package/dist/trace.js +1 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +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/server.js
CHANGED
|
@@ -61,7 +61,6 @@ function isAddressInfo(a) {
|
|
|
61
61
|
// HTTP /health response uses this; reading package.json synchronously here
|
|
62
62
|
// would couple the daemon to its on-disk install path, which we want to
|
|
63
63
|
// avoid for tests that mkdtemp a hippoRoot.
|
|
64
|
-
// v1.3.1: source from src/version.ts so /health no longer reports stale 0.39.0.
|
|
65
64
|
const VERSION = PACKAGE_VERSION;
|
|
66
65
|
const LOOPBACK_HOSTS = new Set(['127.0.0.1', '::1', 'localhost']);
|
|
67
66
|
/** The /v1 routes in dispatch order; the first entry whose method and path match handles the request. */
|
|
@@ -159,7 +158,7 @@ async function dispatchV1Route(r, method, path) {
|
|
|
159
158
|
return false;
|
|
160
159
|
}
|
|
161
160
|
async function handleRequest(req, res, opts, startedAt, streamSlots, limiter) {
|
|
162
|
-
//
|
|
161
|
+
// Pre-decode raw-URL slash check. Catches `%2F` / `%2f` before
|
|
163
162
|
// Node's URL parser collapses them and they slip past the route table.
|
|
164
163
|
rejectEncodedSlash(req.url ?? '/');
|
|
165
164
|
const { method, path, query } = parseRequest(req);
|
|
@@ -215,126 +214,93 @@ function sendHealth(req, res, startedAt) {
|
|
|
215
214
|
sendJson(res, 200, { ok: true });
|
|
216
215
|
}
|
|
217
216
|
}
|
|
218
|
-
|
|
219
|
-
* Boot the HTTP daemon on host:port and write the pidfile under hippoRoot.
|
|
220
|
-
*
|
|
221
|
-
* Refuses non-loopback hosts at boot (Footgun #3 from the A1 plan) unless
|
|
222
|
-
* HIPPO_REQUIRE_AUTH=1 is set. The A5 v2 auth middleware (buildContextWithAuth /
|
|
223
|
-
* requireAuth) has shipped and every route checks it except GET /health
|
|
224
|
-
* (public by design for platform health checks) and the two connector
|
|
225
|
-
* webhooks in PUBLIC_ROUTES, which are HMAC-gated by their own signing
|
|
226
|
-
* secrets and 404 when those secrets are unset. But the loopback
|
|
227
|
-
* no-auth fallback inside buildContextWithAuth still admits unauthenticated
|
|
228
|
-
* requests from a loopback remote address (unless they carry Forwarded,
|
|
229
|
-
* X-Forwarded-For/-Host/-Proto, X-Real-IP, Cf-Connecting-Ip or True-Client-Ip, which mark a same-host proxy and get
|
|
230
|
-
* a 401 like any keyless remote request), so binding to a non-loopback host
|
|
231
|
-
* is only safe once that fallback is disabled with HIPPO_REQUIRE_AUTH=1,
|
|
232
|
-
* which forces every request (loopback or not) through Bearer-token
|
|
233
|
-
* validation. Without that env var set, a non-loopback bind would expose the
|
|
234
|
-
* DB to the network with no auth, so we fail fast instead.
|
|
235
|
-
*
|
|
236
|
-
* Use port: 0 in tests to bind to an ephemeral port and read the actual
|
|
237
|
-
* port back via server.address() after listen.
|
|
238
|
-
*/
|
|
239
|
-
export async function serve(opts) {
|
|
240
|
-
const host = opts.host ?? '127.0.0.1';
|
|
241
|
-
const requestedPort = opts.port ?? Number(envPort() ?? 6789);
|
|
217
|
+
function assertBindable(host) {
|
|
242
218
|
if (!LOOPBACK_HOSTS.has(host) && !envRequireAuth()) {
|
|
243
219
|
throw new Error(`Refusing to bind hippo serve to non-loopback host '${host}' without auth. ` +
|
|
244
220
|
`Set HIPPO_REQUIRE_AUTH=1 to bind non-loopback; every request then requires ` +
|
|
245
221
|
`a valid API key. Bind to 127.0.0.1 / ::1 / localhost otherwise.`);
|
|
246
222
|
}
|
|
247
|
-
|
|
223
|
+
}
|
|
224
|
+
async function assertNoLiveServer(hippoRoot) {
|
|
225
|
+
// Refuse to start if a live hippo server already serves this hippoRoot.
|
|
248
226
|
// detectServer probes the recorded /health — a stale pidfile is unlinked and
|
|
249
227
|
// ignored, but a live peer means a concurrent `hippo serve` would race for
|
|
250
228
|
// the port and clobber the pidfile.
|
|
251
|
-
const existing = await detectServer(
|
|
229
|
+
const existing = await detectServer(hippoRoot);
|
|
252
230
|
if (existing) {
|
|
253
231
|
throw new Error(`hippo serve: already running on port ${existing.port} (pid ${existing.pid}). ` +
|
|
254
232
|
`Stop that server before starting another on the same hippoRoot.`);
|
|
255
233
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
const startedAt = new Date().toISOString();
|
|
260
|
-
// E3: per-IP rate limiter for /v1/* and /mcp*. Built here (not at module scope) so
|
|
234
|
+
}
|
|
235
|
+
function bootRateLimiter() {
|
|
236
|
+
// Per-IP rate limiter for /v1/* and /mcp*. Built here (not at module scope) so
|
|
261
237
|
// HIPPO_V1_RPS is read at boot, matching HIPPO_PORT above and letting a test
|
|
262
238
|
// set the rate before serve(). A non-positive or non-finite value disables
|
|
263
239
|
// limiting (the opt-out knob).
|
|
264
240
|
const v1Rps = Number(envV1Rps() ?? 20);
|
|
265
|
-
|
|
241
|
+
return Number.isFinite(v1Rps) && v1Rps > 0
|
|
266
242
|
? createRateLimiter({ ratePerSec: v1Rps, burst: v1Rps * 2, idleEvictMs: 60000, maxKeys: 10000 })
|
|
267
243
|
: undefined;
|
|
268
|
-
|
|
269
|
-
|
|
244
|
+
}
|
|
245
|
+
function createStoreHolder(hippoRoot) {
|
|
270
246
|
// Handlers open and close their own connections; while this one is held, none of those closes is SQLite's last,
|
|
271
247
|
// which checkpoints and deletes the WAL. It opens only once the store exists, so serving never creates one.
|
|
272
248
|
let heldDb;
|
|
273
249
|
let stopHolding = false;
|
|
274
|
-
const
|
|
275
|
-
if (heldDb || stopHolding || !existsSync(getHippoDbPath(
|
|
250
|
+
const hold = () => {
|
|
251
|
+
if (heldDb || stopHolding || !existsSync(getHippoDbPath(hippoRoot)))
|
|
276
252
|
return;
|
|
277
253
|
try {
|
|
278
|
-
heldDb = openHippoDb(
|
|
254
|
+
heldDb = openHippoDb(hippoRoot);
|
|
279
255
|
}
|
|
280
256
|
catch (err) {
|
|
281
257
|
stopHolding = true;
|
|
282
258
|
log.warn(`serve: could not hold a store connection; requests still work, only slower: ${err instanceof Error ? err.message : String(err)}`);
|
|
283
259
|
}
|
|
284
260
|
};
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
// a prior response chunk gets closed by the server's default 5s
|
|
324
|
-
// keepAliveTimeout just as a client reuses it for the next request. Raising
|
|
325
|
-
// both timeouts shrinks that idle-close/reuse window ~13x. Keep
|
|
326
|
-
// headersTimeout ABOVE the EFFECTIVE keep-alive expiry, which is
|
|
327
|
-
// keepAliveTimeout + keepAliveTimeoutBuffer (the buffer defaults to
|
|
328
|
-
// 1,000ms on Node 22.19+/24.6+ — verified 1,000 on node 24.13, so the
|
|
329
|
-
// effective expiry here is 66s; codex review caught that a 66s
|
|
330
|
-
// headersTimeout would sit exactly ON that boundary and recreate the
|
|
331
|
-
// race). The headers timer also runs while a kept-alive socket waits for
|
|
332
|
-
// its next request, so a value at or below the effective expiry would
|
|
333
|
-
// itself close idle reused sockets, and Node would not flag it (no error
|
|
334
|
-
// or warning at listen time — verified empirically).
|
|
261
|
+
const release = () => {
|
|
262
|
+
stopHolding = true;
|
|
263
|
+
if (heldDb)
|
|
264
|
+
closeHippoDb(heldDb);
|
|
265
|
+
heldDb = undefined;
|
|
266
|
+
};
|
|
267
|
+
return { hold, release };
|
|
268
|
+
}
|
|
269
|
+
function replyWithFailure(req, res, err, requestId) {
|
|
270
|
+
const mapped = replyFor(err);
|
|
271
|
+
logRequestFailure(req, err, requestId, mapped.status);
|
|
272
|
+
if (res.headersSent) {
|
|
273
|
+
try {
|
|
274
|
+
res.end();
|
|
275
|
+
}
|
|
276
|
+
catch { /* socket already gone */ }
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (isSqliteBusy(err))
|
|
280
|
+
res.setHeader('Retry-After', '1');
|
|
281
|
+
if (mapped.status === 500) {
|
|
282
|
+
// The id lets an operator find the logged cause without the client seeing internal text.
|
|
283
|
+
sendJson(res, 500, { error: mapped.message, requestId });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// RecallContractError keeps the shared {error} shape and adds `code` so clients branch without parsing prose.
|
|
287
|
+
if (err instanceof RecallContractError) {
|
|
288
|
+
sendJson(res, 400, { error: err.message, code: err.code });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
sendError(res, mapped.status, mapped.message);
|
|
292
|
+
// readBody hit the 1 MB cap mid-stream, so drop the socket rather than drain unbounded bytes.
|
|
293
|
+
if (err instanceof BodyTooLargeError)
|
|
294
|
+
req.destroy();
|
|
295
|
+
}
|
|
296
|
+
function setKeepAliveTimeouts(server) {
|
|
297
|
+
// The default 5s keepAliveTimeout closes idle sockets just as clients reuse them (ECONNRESET).
|
|
298
|
+
// headersTimeout must stay ABOVE keepAliveTimeout + keepAliveTimeoutBuffer (1s), or it closes idle reused sockets itself.
|
|
335
299
|
server.keepAliveTimeout = 65_000;
|
|
336
300
|
server.headersTimeout = 70_000;
|
|
337
|
-
|
|
301
|
+
}
|
|
302
|
+
function listenOn(server, port, host) {
|
|
303
|
+
return new Promise((resolve, reject) => {
|
|
338
304
|
const onError = (err) => {
|
|
339
305
|
server.removeListener('listening', onListening);
|
|
340
306
|
reject(err);
|
|
@@ -345,17 +311,85 @@ export async function serve(opts) {
|
|
|
345
311
|
};
|
|
346
312
|
server.once('error', onError);
|
|
347
313
|
server.once('listening', onListening);
|
|
348
|
-
server.listen(
|
|
314
|
+
server.listen(port, host);
|
|
349
315
|
});
|
|
316
|
+
}
|
|
317
|
+
function installSignalHandlers(stop) {
|
|
318
|
+
let shuttingDown = false;
|
|
319
|
+
const gracefulShutdown = async (signal) => {
|
|
320
|
+
if (shuttingDown)
|
|
321
|
+
return;
|
|
322
|
+
shuttingDown = true;
|
|
323
|
+
log.warn(`received ${signal}, shutting down`);
|
|
324
|
+
try {
|
|
325
|
+
await stop();
|
|
326
|
+
process.exit(0);
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
log.error(`error during stop: ${err instanceof Error ? err.message : String(err)}`, errorFields(err));
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
process.once('SIGTERM', () => { void gracefulShutdown('SIGTERM'); });
|
|
334
|
+
process.once('SIGINT', () => { void gracefulShutdown('SIGINT'); });
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Boot the HTTP daemon on host:port and write the pidfile under hippoRoot.
|
|
338
|
+
*
|
|
339
|
+
* Refuses non-loopback hosts at boot unless
|
|
340
|
+
* HIPPO_REQUIRE_AUTH=1 is set. The auth middleware (buildContextWithAuth /
|
|
341
|
+
* requireAuth) has shipped and every route checks it except GET /health
|
|
342
|
+
* (public by design for platform health checks) and the two connector
|
|
343
|
+
* webhooks in PUBLIC_ROUTES, which are HMAC-gated by their own signing
|
|
344
|
+
* secrets and 404 when those secrets are unset. But the loopback
|
|
345
|
+
* no-auth fallback inside buildContextWithAuth still admits unauthenticated
|
|
346
|
+
* requests from a loopback remote address (unless they carry Forwarded,
|
|
347
|
+
* X-Forwarded-For/-Host/-Proto, X-Real-IP, Cf-Connecting-Ip or True-Client-Ip, which mark a same-host proxy and get
|
|
348
|
+
* a 401 like any keyless remote request), so binding to a non-loopback host
|
|
349
|
+
* is only safe once that fallback is disabled with HIPPO_REQUIRE_AUTH=1,
|
|
350
|
+
* which forces every request (loopback or not) through Bearer-token
|
|
351
|
+
* validation. Without that env var set, a non-loopback bind would expose the
|
|
352
|
+
* DB to the network with no auth, so we fail fast instead.
|
|
353
|
+
*
|
|
354
|
+
* Use port: 0 in tests to bind to an ephemeral port and read the actual
|
|
355
|
+
* port back via server.address() after listen.
|
|
356
|
+
*/
|
|
357
|
+
export async function serve(opts) {
|
|
358
|
+
const host = opts.host ?? '127.0.0.1';
|
|
359
|
+
const requestedPort = opts.port ?? Number(envPort() ?? 6789);
|
|
360
|
+
assertBindable(host);
|
|
361
|
+
await assertNoLiveServer(opts.hippoRoot);
|
|
362
|
+
// The server's start time. Single source of truth: it is returned by every
|
|
363
|
+
// GET /health response and (below) written into the pidfile, so detectServer
|
|
364
|
+
// can match the two and prove a pid-reusing impostor is not the real server.
|
|
365
|
+
const startedAt = new Date().toISOString();
|
|
366
|
+
const limiter = bootRateLimiter();
|
|
367
|
+
// Open /mcp/stream count per client key, so the cap is per server rather than per process.
|
|
368
|
+
const streamSlots = new Map();
|
|
369
|
+
const store = createStoreHolder(opts.hippoRoot);
|
|
370
|
+
const inflight = new Set();
|
|
371
|
+
const server = createServer((req, res) => {
|
|
372
|
+
res.once('finish', store.hold);
|
|
373
|
+
inflight.add(res);
|
|
374
|
+
res.once('close', () => inflight.delete(res));
|
|
375
|
+
const requestId = resolveRequestId(req.headers['x-request-id']);
|
|
376
|
+
requestIds.set(req, requestId);
|
|
377
|
+
res.setHeader('X-Request-Id', requestId);
|
|
378
|
+
withBusyWait(SERVER_DB_WAIT_MS, () => handleRequest(req, res, opts, startedAt, streamSlots, limiter)).catch((err) => {
|
|
379
|
+
replyWithFailure(req, res, err, requestId);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
setKeepAliveTimeouts(server);
|
|
383
|
+
await listenOn(server, requestedPort, host);
|
|
350
384
|
const address = server.address();
|
|
351
385
|
if (!isAddressInfo(address)) {
|
|
352
386
|
throw new Error('server.address() returned unexpected shape');
|
|
353
387
|
}
|
|
354
388
|
const addressInfo = address;
|
|
355
389
|
const actualPort = addressInfo.port;
|
|
356
|
-
const url = `http://${host}:${actualPort}`;
|
|
390
|
+
const url = `http://${host.includes(':') ? `[${host}]` : host}:${actualPort}`;
|
|
357
391
|
writePidfile(opts.hippoRoot, { port: actualPort, url, startedAt });
|
|
358
|
-
|
|
392
|
+
store.hold();
|
|
359
393
|
let stopping = false;
|
|
360
394
|
const stop = async () => {
|
|
361
395
|
if (stopping)
|
|
@@ -363,33 +397,13 @@ export async function serve(opts) {
|
|
|
363
397
|
stopping = true;
|
|
364
398
|
// Remove the pidfile only if it still names this server. A newer server
|
|
365
399
|
// may have started on this hippoRoot and rewritten the pidfile; an
|
|
366
|
-
// unconditional unlink here would orphan it.
|
|
400
|
+
// unconditional unlink here would orphan it.
|
|
367
401
|
removePidfileIfOwned(opts.hippoRoot, { pid: process.pid, startedAt });
|
|
368
402
|
await drainAndClose(server, inflight, opts.shutdownDrainMs ?? 5000);
|
|
369
|
-
|
|
370
|
-
if (heldDb)
|
|
371
|
-
closeHippoDb(heldDb);
|
|
372
|
-
heldDb = undefined;
|
|
403
|
+
store.release();
|
|
373
404
|
};
|
|
374
|
-
if (opts.handleSignals)
|
|
375
|
-
|
|
376
|
-
const gracefulShutdown = async (signal) => {
|
|
377
|
-
if (shuttingDown)
|
|
378
|
-
return;
|
|
379
|
-
shuttingDown = true;
|
|
380
|
-
log.warn(`received ${signal}, shutting down`);
|
|
381
|
-
try {
|
|
382
|
-
await stop();
|
|
383
|
-
process.exit(0);
|
|
384
|
-
}
|
|
385
|
-
catch (err) {
|
|
386
|
-
log.error(`error during stop: ${err instanceof Error ? err.message : String(err)}`, errorFields(err));
|
|
387
|
-
process.exit(1);
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
process.once('SIGTERM', () => { void gracefulShutdown('SIGTERM'); });
|
|
391
|
-
process.once('SIGINT', () => { void gracefulShutdown('SIGINT'); });
|
|
392
|
-
}
|
|
405
|
+
if (opts.handleSignals)
|
|
406
|
+
installSignalHandlers(stop);
|
|
393
407
|
return { port: actualPort, url, stop, server };
|
|
394
408
|
}
|
|
395
409
|
//# sourceMappingURL=server.js.map
|
package/dist/shared.d.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Local .hippo/ stores are per-project.
|
|
6
6
|
*/
|
|
7
7
|
import { MemoryEntry } from './memory.js';
|
|
8
|
-
import type
|
|
8
|
+
import { type SearchResult, type ResultCost } from './search/types.js';
|
|
9
|
+
import type { HybridVectorCandidates } from './search/vector.js';
|
|
9
10
|
import type { DatabaseSyncLike } from './db.js';
|
|
10
11
|
/**
|
|
11
12
|
* Returns the path to the global Hippo store.
|
|
@@ -55,10 +56,10 @@ export interface HybridSearchOptions extends SearchOptions {
|
|
|
55
56
|
asOf?: string;
|
|
56
57
|
/** Budget cost per result, spent the same way in each store and in the merged list. */
|
|
57
58
|
cost?: ResultCost;
|
|
58
|
-
/**
|
|
59
|
+
/** Propagated to underlying hybridSearch calls.
|
|
59
60
|
* Per-call > env HIPPO_SUMMARY_DEBOOST > 0.85 default. */
|
|
60
61
|
summaryDeboost?: number;
|
|
61
|
-
/**
|
|
62
|
+
/** Propagated. Default true (1.05 boost if rebuilt within 7d). */
|
|
62
63
|
summaryFreshness?: boolean;
|
|
63
64
|
/** v39 memory scope isolation: optional admission predicate applied to the
|
|
64
65
|
* loaded candidate entries of BOTH stores BEFORE ranking, cross-store
|
|
@@ -66,7 +67,7 @@ export interface HybridSearchOptions extends SearchOptions {
|
|
|
66
67
|
* its admitted duplicate in the dedupe pass, or saturate the budget.
|
|
67
68
|
* Default undefined = unchanged behavior (recall paths never set it). */
|
|
68
69
|
entryFilter?: (entry: MemoryEntry) => boolean;
|
|
69
|
-
/**
|
|
70
|
+
/** Recall-mode scope filter, consumed by `searchBothHybrid` only.
|
|
70
71
|
* ABSENT (undefined) is the only unfiltered mode: both stores load via
|
|
71
72
|
* `loadSearchEntries` unchanged (background pipelines / eval callers).
|
|
72
73
|
* PRESENT switches the internal loads to `loadRecallSearchEntries` (SQL
|
|
@@ -93,6 +94,14 @@ export interface HybridSearchOptions extends SearchOptions {
|
|
|
93
94
|
* Async version of searchBoth that calls hybridSearch instead of search.
|
|
94
95
|
*/
|
|
95
96
|
export declare function searchBothHybrid(query: string, localRoot: string, globalRoot: string, options?: HybridSearchOptions): Promise<SearchResult[]>;
|
|
97
|
+
/** Hybrid ranking of rows already loaded from each store: the local bump, one copy per text, then the shared budget. */
|
|
98
|
+
export declare function rankBothStores(query: string, roots: {
|
|
99
|
+
local: string;
|
|
100
|
+
global: string;
|
|
101
|
+
}, entries: {
|
|
102
|
+
local: MemoryEntry[];
|
|
103
|
+
global: MemoryEntry[];
|
|
104
|
+
}, vectorCandidates: HybridVectorCandidates, options?: HybridSearchOptions): Promise<SearchResult[]>;
|
|
96
105
|
/** Tags whose rows only a hand-run share or promote may copy to the global store; derived rows inherit them. */
|
|
97
106
|
export declare const NEVER_AUTO_SHARE_TAGS: ReadonlySet<string>;
|
|
98
107
|
export declare function neverAutoShareTags(sources: readonly MemoryEntry[]): string[];
|
|
@@ -117,7 +126,7 @@ export declare function shareMemory(localRoot: string, id: string, options?: {
|
|
|
117
126
|
* List all projects that have contributed memories to the global store.
|
|
118
127
|
* Parses the source field for 'shared:<project>:' or 'promoted:<path>' patterns.
|
|
119
128
|
*
|
|
120
|
-
*
|
|
129
|
+
* `tenantId` is optional. When provided, the global entries
|
|
121
130
|
* are filtered to that tenant before aggregation — matches every other
|
|
122
131
|
* read path's default-safe behaviour. When undefined, host-wide (back-compat
|
|
123
132
|
* for legacy callers like CLI standalone + dashboard internal use). Operators
|
|
@@ -129,25 +138,28 @@ export declare function listPeers(globalRoot?: string, tenantId?: string): Array
|
|
|
129
138
|
count: number;
|
|
130
139
|
latest: string;
|
|
131
140
|
}>;
|
|
141
|
+
type AutoShareStats = {
|
|
142
|
+
secretSkipped: number;
|
|
143
|
+
rejectedSkipped?: number;
|
|
144
|
+
neverAutoShareSkipped?: number;
|
|
145
|
+
};
|
|
132
146
|
/**
|
|
133
147
|
* Auto-share: local memories with high transfer scores, not already global, no NEVER_AUTO_SHARE_TAGS tag.
|
|
134
148
|
* Returns the list of shared entries.
|
|
135
149
|
*
|
|
136
|
-
*
|
|
150
|
+
* `options.tenantId` is opt-in. When provided, the LOCAL-entries read is
|
|
137
151
|
* scoped to that tenant. When undefined, the local read is host-wide (current
|
|
138
152
|
* behaviour). The GLOBAL-entries read is always unioned — the global root IS
|
|
139
|
-
* the cross-tenant aggregate by design.
|
|
140
|
-
*
|
|
141
|
-
* passes options without tenantId because `sleep` is host-wide by intent;
|
|
142
|
-
* see `src/api.ts:2073-2077` for the cross-tenant dedup rationale.
|
|
153
|
+
* the cross-tenant aggregate by design. `api.sleep` passes no tenantId
|
|
154
|
+
* because `sleep` is host-wide by intent.
|
|
143
155
|
*
|
|
144
|
-
*
|
|
156
|
+
* `options.stats` is an opt-in out-param. When provided,
|
|
145
157
|
* `stats.secretSkipped` is incremented once per row that passed every OTHER
|
|
146
158
|
* admission gate (transfer score, not-already-global) and was withheld SOLELY
|
|
147
159
|
* by the secret veto — i.e. it counts shares actually prevented, not secret
|
|
148
160
|
* rows merely present. Filled identically under `dryRun`.
|
|
149
161
|
*
|
|
150
|
-
*
|
|
162
|
+
* `stats.rejectedSkipped` (optional) is incremented once per candidate
|
|
151
163
|
* refused by the GLOBAL store's rejection tombstone (RejectedValueError from
|
|
152
164
|
* shareMemory -> writeEntry). Unlike secretSkipped, this can only be
|
|
153
165
|
* detected by attempting the write — `dryRun` returns candidates before the
|
|
@@ -159,11 +171,7 @@ export declare function autoShare(localRoot: string, options?: {
|
|
|
159
171
|
minScore?: number;
|
|
160
172
|
dryRun?: boolean;
|
|
161
173
|
tenantId?: string;
|
|
162
|
-
stats?:
|
|
163
|
-
secretSkipped: number;
|
|
164
|
-
rejectedSkipped?: number;
|
|
165
|
-
neverAutoShareSkipped?: number;
|
|
166
|
-
};
|
|
174
|
+
stats?: AutoShareStats;
|
|
167
175
|
}): MemoryEntry[];
|
|
168
176
|
/**
|
|
169
177
|
* Copy all global memories into the local store.
|
|
@@ -173,4 +181,5 @@ export declare function autoShare(localRoot: string, options?: {
|
|
|
173
181
|
export declare function syncGlobalToLocal(localRoot: string, globalRoot: string, opts?: {
|
|
174
182
|
includeCrossProject?: boolean;
|
|
175
183
|
}): number;
|
|
184
|
+
export {};
|
|
176
185
|
//# sourceMappingURL=shared.d.ts.map
|