hippo-memory 1.63.2 → 1.64.1
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 +11 -5
- package/dist/agent-memories/claude-code.js +3 -3
- package/dist/ambient-store.d.ts +2 -0
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +2 -0
- package/dist/api/assemble.js +11 -7
- package/dist/api/auth.d.ts +15 -11
- package/dist/api/auth.js +116 -22
- package/dist/api/context-select.d.ts +5 -3
- package/dist/api/context-select.js +6 -11
- package/dist/api/context-types.d.ts +2 -1
- package/dist/api/context.d.ts +1 -1
- package/dist/api/context.js +79 -55
- package/dist/api/dormant.d.ts +5 -5
- package/dist/api/dormant.js +13 -9
- package/dist/api/drill-down.d.ts +2 -0
- package/dist/api/drill-down.js +12 -7
- package/dist/api/forget.d.ts +1 -1
- package/dist/api/forget.js +8 -4
- package/dist/api/learn.js +5 -3
- package/dist/api/outcome.d.ts +1 -1
- package/dist/api/outcome.js +2 -1
- package/dist/api/promote.js +8 -5
- package/dist/api/recall-types.d.ts +4 -2
- package/dist/api/recall.js +51 -36
- package/dist/api/remember.d.ts +7 -0
- package/dist/api/remember.js +26 -2
- package/dist/api/sleep-run.d.ts +28 -0
- package/dist/api/sleep-run.js +267 -0
- package/dist/api/sleep.d.ts +5 -45
- package/dist/api/sleep.js +5 -252
- package/dist/api/types.d.ts +5 -0
- package/dist/api/types.js +4 -0
- package/dist/audit-prune.js +1 -1
- package/dist/audit.d.ts +1 -3
- package/dist/audit.js +7 -103
- package/dist/auth.d.ts +48 -4
- package/dist/auth.js +111 -42
- package/dist/autolearn.js +2 -2
- package/dist/capture/caller-session.d.ts +16 -0
- package/dist/capture/caller-session.js +58 -0
- package/dist/capture/command.d.ts +19 -1
- package/dist/capture/command.js +28 -21
- package/dist/capture/compact-resume-caller.d.ts +11 -0
- package/dist/capture/compact-resume-caller.js +35 -0
- package/dist/capture/compact.d.ts +0 -14
- package/dist/capture/compact.js +7 -160
- package/dist/capture/compaction-items-caller.d.ts +16 -0
- package/dist/capture/compaction-items-caller.js +32 -0
- package/dist/capture/extract.d.ts +2 -14
- package/dist/capture/extract.js +135 -346
- package/dist/capture/failure-caller.d.ts +21 -0
- package/dist/capture/failure-caller.js +74 -0
- package/dist/capture/failure-reading.d.ts +44 -0
- package/dist/capture/failure-reading.js +78 -0
- package/dist/capture/pre-compact-caller.d.ts +16 -0
- package/dist/capture/pre-compact-caller.js +39 -0
- package/dist/capture/session-end-caller.d.ts +21 -0
- package/dist/capture/session-end-caller.js +30 -0
- package/dist/capture/session-texts.d.ts +14 -0
- package/dist/capture/session-texts.js +42 -0
- package/dist/capture/transcript.d.ts +6 -1
- package/dist/capture/transcript.js +20 -16
- package/dist/capture/working-state.d.ts +21 -0
- package/dist/capture/working-state.js +154 -0
- package/dist/capture-error.d.ts +8 -22
- package/dist/capture-error.js +9 -61
- package/dist/cli/audit.js +25 -0
- package/dist/cli/auth.js +4 -3
- package/dist/cli/context.js +13 -158
- package/dist/cli/continuity.js +2 -1
- package/dist/cli/init.js +5 -3
- package/dist/cli/last-sleep.d.ts +6 -0
- package/dist/cli/last-sleep.js +68 -0
- package/dist/cli/maintenance.d.ts +2 -1
- package/dist/cli/maintenance.js +17 -11
- package/dist/cli/quality-repair-once.d.ts +3 -0
- package/dist/cli/quality-repair-once.js +36 -0
- package/dist/cli/recall.js +2 -1
- package/dist/cli/remember.d.ts +2 -0
- package/dist/cli/remember.js +4 -1
- package/dist/cli/session-hooks.d.ts +0 -3
- package/dist/cli/session-hooks.js +12 -49
- package/dist/cli/setup.js +10 -2
- package/dist/cli/shared.d.ts +7 -14
- package/dist/cli/shared.js +19 -78
- package/dist/cli/sleep.js +6 -3
- package/dist/cli/usage.js +18 -9
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -2
- package/dist/client.js +1 -1
- package/dist/compaction-record.d.ts +19 -1
- package/dist/compaction-record.js +61 -132
- package/dist/compaction-spool.d.ts +45 -0
- package/dist/compaction-spool.js +454 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +49 -0
- package/dist/connectors/github/deletion.d.ts +1 -1
- package/dist/connectors/github/deletion.js +5 -17
- package/dist/consolidate/conflicts.js +4 -0
- package/dist/consolidate/flush-units.d.ts +9 -0
- package/dist/consolidate/flush-units.js +63 -0
- package/dist/consolidate/llm-passes.js +5 -4
- package/dist/consolidate/merge.js +5 -1
- package/dist/consolidate/run.d.ts +2 -1
- package/dist/consolidate/sleep.d.ts +2 -0
- package/dist/consolidate/sleep.js +82 -15
- package/dist/context-render.d.ts +14 -0
- package/dist/context-render.js +28 -0
- package/dist/dag.js +6 -0
- package/dist/dashboard-actions.js +3 -2
- package/dist/dashboard.js +4 -3
- package/dist/db/backup.d.ts +6 -0
- package/dist/db/backup.js +29 -0
- package/dist/db/busy.d.ts +9 -0
- package/dist/db/busy.js +35 -0
- package/dist/db/continuity.d.ts +1 -0
- package/dist/db/continuity.js +31 -4
- package/dist/db/meta.d.ts +2 -0
- package/dist/db/meta.js +7 -0
- package/dist/db/migrate.js +2 -3
- package/dist/db/migrations/index.d.ts +1 -1
- package/dist/db/migrations/index.js +5 -1
- package/dist/db/migrations/v39.js +2 -7
- package/dist/db/migrations/v53.d.ts +3 -0
- package/dist/db/migrations/v53.js +16 -0
- package/dist/db/migrations/v54.d.ts +3 -0
- package/dist/db/migrations/v54.js +25 -0
- package/dist/db/open.d.ts +11 -0
- package/dist/db/open.js +27 -0
- package/dist/db/tables.d.ts +2 -0
- package/dist/db/tables.js +6 -2
- package/dist/db.d.ts +2 -2
- package/dist/db.js +2 -2
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -15
- package/dist/dormant.d.ts +16 -20
- package/dist/dormant.js +24 -30
- package/dist/embedding-provider.js +4 -3
- package/dist/entry/json-hooks.d.ts +3 -0
- package/dist/entry/json-hooks.js +4 -0
- package/dist/entry/project-identity.d.ts +2 -0
- package/dist/entry/project-identity.js +3 -0
- package/dist/entry/session-text.d.ts +7 -0
- package/dist/entry/session-text.js +8 -0
- package/dist/extract.js +6 -0
- package/dist/failure-log.d.ts +10 -2
- package/dist/failure-log.js +19 -2
- package/dist/gated-write.js +1 -1
- package/dist/graph/delta.d.ts +44 -0
- package/dist/graph/delta.js +109 -0
- package/dist/graph/types.d.ts +5 -0
- package/dist/graph/write.d.ts +20 -20
- package/dist/graph/write.js +103 -36
- package/dist/graph-extract.d.ts +43 -9
- package/dist/graph-extract.js +140 -157
- package/dist/graph-recall.d.ts +1 -0
- package/dist/graph-recall.js +2 -2
- package/dist/graph-view.d.ts +1 -0
- package/dist/graph-view.js +25 -2
- package/dist/half-life-migration.js +5 -1
- package/dist/handoff-evidence.d.ts +5 -0
- package/dist/handoff-evidence.js +30 -0
- package/dist/home-path.d.ts +5 -0
- package/dist/home-path.js +24 -0
- package/dist/hooks/json-hooks.d.ts +13 -2
- package/dist/hooks/json-hooks.js +246 -117
- package/dist/hooks/opencode.js +1 -1
- package/dist/hooks/shared.d.ts +2 -0
- package/dist/hooks/shared.js +6 -1
- package/dist/http-util.d.ts +16 -7
- package/dist/http-util.js +65 -20
- package/dist/importers/vault.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/json.d.ts +2 -0
- package/dist/json.js +7 -0
- package/dist/ledger-db.d.ts +10 -0
- package/dist/ledger-db.js +42 -0
- package/dist/local-embedding.d.ts +2 -0
- package/dist/local-embedding.js +20 -4
- package/dist/mcp/admin-tools.d.ts +2 -2
- package/dist/mcp/admin-tools.js +47 -5
- package/dist/mcp/memory-tools.js +7 -2
- package/dist/mcp/protocol.d.ts +6 -0
- package/dist/mcp/protocol.js +4 -2
- package/dist/mcp/recall-tools.js +19 -6
- package/dist/mcp/request.js +4 -0
- package/dist/mcp/session-state.d.ts +4 -0
- package/dist/mcp/session-state.js +15 -6
- package/dist/mcp/shared-gate.d.ts +4 -0
- package/dist/mcp/shared-gate.js +22 -0
- package/dist/mcp/tools.js +1 -0
- package/dist/memory-quality.d.ts +35 -0
- package/dist/memory-quality.js +191 -0
- package/dist/memory.d.ts +4 -4
- package/dist/physics-state.js +1 -1
- package/dist/pilot-arm.d.ts +14 -3
- package/dist/pilot-arm.js +32 -10
- package/dist/postinstall.js +2 -2
- package/dist/project-briefs.js +4 -4
- package/dist/project-identity.d.ts +14 -10
- package/dist/project-identity.js +29 -11
- package/dist/project-merge.d.ts +0 -2
- package/dist/project-merge.js +9 -13
- package/dist/prompt-hook.d.ts +65 -0
- package/dist/prompt-hook.js +213 -0
- package/dist/prompt-recall.js +1 -1
- package/dist/quality-repair.d.ts +27 -0
- package/dist/quality-repair.js +220 -0
- package/dist/rate-limit.d.ts +1 -0
- package/dist/rate-limit.js +5 -3
- package/dist/raw-archive.d.ts +1 -1
- package/dist/raw-archive.js +6 -16
- package/dist/recall-scope.d.ts +28 -5
- package/dist/recall-scope.js +52 -7
- package/dist/recall-trace.js +1 -1
- package/dist/reject-flow.d.ts +2 -0
- package/dist/reject-flow.js +20 -6
- package/dist/secret-detect.js +7 -8
- package/dist/server/auth.d.ts +8 -6
- package/dist/server/auth.js +85 -38
- package/dist/server/client-ip.d.ts +2 -0
- package/dist/server/client-ip.js +16 -1
- package/dist/server/mcp-http.d.ts +9 -3
- package/dist/server/mcp-http.js +56 -13
- package/dist/server/request.d.ts +1 -1
- package/dist/server/request.js +5 -5
- package/dist/server/routes/admin.js +12 -6
- package/dist/server/routes/memories.js +9 -1
- package/dist/server/routes/recall.js +14 -2
- package/dist/server/types.d.ts +42 -5
- package/dist/server/validation.d.ts +5 -2
- package/dist/server/validation.js +19 -2
- package/dist/server.d.ts +21 -4
- package/dist/server.js +207 -49
- package/dist/session-digest.d.ts +0 -1
- package/dist/session-digest.js +3 -12
- package/dist/session-owners.d.ts +7 -0
- package/dist/session-owners.js +54 -0
- package/dist/share-scrub.d.ts +3 -0
- package/dist/share-scrub.js +8 -0
- package/dist/shared.d.ts +1 -0
- package/dist/shared.js +26 -18
- package/dist/store/candidates.d.ts +14 -3
- package/dist/store/candidates.js +48 -26
- package/dist/store/conflicts.d.ts +15 -0
- package/dist/store/conflicts.js +68 -44
- package/dist/store/delete-and-batch.d.ts +25 -1
- package/dist/store/delete-and-batch.js +121 -69
- package/dist/store/entry-reads.d.ts +5 -6
- package/dist/store/entry-reads.js +24 -12
- package/dist/store/entry-row.d.ts +7 -10
- package/dist/store/entry-row.js +40 -18
- package/dist/store/entry-writes.d.ts +3 -3
- package/dist/store/entry-writes.js +9 -23
- package/dist/store/handoffs.d.ts +9 -6
- package/dist/store/handoffs.js +72 -59
- package/dist/store/index-and-stats.js +2 -2
- package/dist/store/open.d.ts +3 -1
- package/dist/store/open.js +3 -3
- package/dist/store/search-rows.d.ts +8 -5
- package/dist/store/search-rows.js +15 -18
- package/dist/store/sessions.d.ts +25 -6
- package/dist/store/sessions.js +61 -19
- package/dist/store/summaries.d.ts +1 -1
- package/dist/store/summaries.js +3 -18
- package/dist/store/tenant-lookup.d.ts +7 -3
- package/dist/store/tenant-lookup.js +9 -9
- package/dist/store-port.d.ts +13 -0
- package/dist/store-port.js +20 -0
- package/dist/version.d.ts +5 -1
- package/dist/version.js +5 -1
- package/dist/working-memory.js +1 -1
- package/dist/write-budget.d.ts +15 -0
- package/dist/write-budget.js +15 -0
- 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 +8 -4
package/dist/http-util.js
CHANGED
|
@@ -11,50 +11,95 @@ export function isHeaderString(value) {
|
|
|
11
11
|
// 1 MB body cap. The CLI never sends payloads near this; anything bigger is
|
|
12
12
|
// almost certainly a misconfigured client or a deliberate memory-blowup attempt.
|
|
13
13
|
const MAX_BODY_BYTES = 1024 * 1024;
|
|
14
|
+
function sizeLabel(bytes) {
|
|
15
|
+
return bytes % (1024 * 1024) === 0 ? `${bytes / (1024 * 1024)}MB` : `${Math.ceil(bytes / 1024)}KB`;
|
|
16
|
+
}
|
|
17
|
+
// How long a refused upload is still read and discarded after its reply, so a client that never stops cannot hold the socket.
|
|
18
|
+
const REFUSED_UPLOAD_LINGER_MS = 2000;
|
|
14
19
|
// Cap for id-shaped request fields (ids, tenant, session, scope, class): far above real values, small enough to bound logs and indexes.
|
|
15
20
|
export const MAX_ID_LEN = 256;
|
|
16
21
|
export const JSON_HEADERS = { 'content-type': 'application/json' };
|
|
17
22
|
export class HttpError extends Error {
|
|
18
23
|
status;
|
|
19
|
-
|
|
24
|
+
/** Sent as the Retry-After header, so a refused caller knows when to come back. */
|
|
25
|
+
retryAfterSec;
|
|
26
|
+
constructor(status, message, retryAfterSec) {
|
|
20
27
|
super(message);
|
|
21
28
|
this.status = status;
|
|
29
|
+
this.retryAfterSec = retryAfterSec;
|
|
22
30
|
}
|
|
23
31
|
}
|
|
24
32
|
export class BodyTooLargeError extends Error {
|
|
25
33
|
}
|
|
34
|
+
/** The body did not arrive in time. Its own class so mapApiError answers 408 and the server drops the socket. */
|
|
35
|
+
export class BodyTimeoutError extends Error {
|
|
36
|
+
}
|
|
26
37
|
export const INTERNAL_ERROR_MESSAGE = 'internal server error';
|
|
27
38
|
/** Maps by class so rewording a message never moves a status; an untyped error is a 500 whose text stays in the server log. */
|
|
28
39
|
export function mapApiError(err) {
|
|
40
|
+
// An add-on can build an HttpError from any number, and writeHead throws on one outside 100-999.
|
|
41
|
+
if (err instanceof HttpError && !(Number.isInteger(err.status) && err.status >= 100 && err.status <= 999)) {
|
|
42
|
+
return { status: 500, message: INTERNAL_ERROR_MESSAGE };
|
|
43
|
+
}
|
|
29
44
|
if (err instanceof HttpError || err instanceof ApiError)
|
|
30
45
|
return { status: err.status, message: err.message };
|
|
31
46
|
if (err instanceof BodyTooLargeError)
|
|
32
47
|
return { status: 413, message: err.message };
|
|
48
|
+
if (err instanceof BodyTimeoutError)
|
|
49
|
+
return { status: 408, message: err.message };
|
|
33
50
|
return { status: 500, message: INTERNAL_ERROR_MESSAGE };
|
|
34
51
|
}
|
|
52
|
+
/** Serialises before the head goes out, so a body that is not JSON still reaches the caller's error reply. */
|
|
35
53
|
export function sendJson(res, status, body) {
|
|
54
|
+
const text = JSON.stringify(body);
|
|
55
|
+
if (text === undefined)
|
|
56
|
+
throw new Error('response body is not JSON');
|
|
36
57
|
res.writeHead(status, JSON_HEADERS);
|
|
37
|
-
res.end(
|
|
58
|
+
res.end(text);
|
|
38
59
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
/** The body as text, refused mid-stream past maxBytes and past deadlineMs, so an oversized or slow sender cannot tie up the server. */
|
|
61
|
+
export function readBody(req, { maxBytes = MAX_BODY_BYTES, deadlineMs } = {}) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const chunks = [];
|
|
64
|
+
let total = 0;
|
|
65
|
+
let timer;
|
|
66
|
+
const refuse = (err) => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
req.off('data', onData);
|
|
69
|
+
chunks.length = 0;
|
|
70
|
+
reject(err);
|
|
71
|
+
};
|
|
72
|
+
// Not for-await: leaving that loop early destroys the request, so the socket stops reading and the 413 caller cannot close it cleanly.
|
|
73
|
+
const onData = (chunk) => {
|
|
74
|
+
total += chunk.length;
|
|
75
|
+
if (total <= maxBytes) {
|
|
76
|
+
chunks.push(chunk);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
refuse(new BodyTooLargeError(`request body exceeds ${sizeLabel(maxBytes)}`));
|
|
80
|
+
};
|
|
81
|
+
if (deadlineMs !== undefined) {
|
|
82
|
+
timer = setTimeout(() => refuse(new BodyTimeoutError(`request body not received within ${deadlineMs} ms`)), deadlineMs);
|
|
54
83
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
84
|
+
req.on('data', onData);
|
|
85
|
+
req.once('end', () => {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
88
|
+
});
|
|
89
|
+
req.on('error', (err) => {
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
reject(err);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// A full close with request bytes unread sends a TCP reset, which discards the reply before the client reads it (RFC 9112 9.6).
|
|
96
|
+
export function closeAfterReply(req) {
|
|
97
|
+
const socket = req.socket;
|
|
98
|
+
req.resume();
|
|
99
|
+
socket.end();
|
|
100
|
+
const timer = setTimeout(() => socket.destroy(), REFUSED_UPLOAD_LINGER_MS);
|
|
101
|
+
timer.unref();
|
|
102
|
+
socket.once('close', () => clearTimeout(timer));
|
|
58
103
|
}
|
|
59
104
|
// Any other Host on a loopback socket is DNS rebinding: a hostile page resolved to 127.0.0.1.
|
|
60
105
|
export const LOOPBACK_HOST_HEADER = /^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i;
|
package/dist/importers/vault.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createHash } from 'node:crypto';
|
|
|
4
4
|
import { createMemory } from '../memory.js';
|
|
5
5
|
import { initStore } from '../store/open.js';
|
|
6
6
|
import { remember, archiveRaw, isPrivateScope } from '../api.js';
|
|
7
|
+
import { assertClientScope } from '../recall-scope.js';
|
|
7
8
|
import { openHippoDb, closeHippoDb } from '../db.js';
|
|
8
9
|
import { RejectedValueError, checkRejectionGuard } from '../rejection.js';
|
|
9
10
|
import { loadConfig } from '../config.js';
|
|
@@ -118,6 +119,7 @@ function vaultIdentityOrThrow(options) {
|
|
|
118
119
|
// isPrivateScope as the single source of truth: reject a scope that names a
|
|
119
120
|
// `private` segment yet is NOT a valid `<source>:private:*` (catches `private`,
|
|
120
121
|
// `private:x`, and `vault:private` with a missing trailing segment).
|
|
122
|
+
assertClientScope(scope);
|
|
121
123
|
if (scope !== null && scope.split(':').includes('private') && !isPrivateScope(scope)) {
|
|
122
124
|
throw new Error(`vault scope '${scope}' is not recognized as private by recall (only '<source>:private:*' scopes are default-denied). Use a source-prefixed scope such as 'vault:private:${vaultName}'.`);
|
|
123
125
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { loadAllEntries, readEntry } from './store/entry-reads.js';
|
|
|
22
22
|
export { deleteEntry } from './store/delete-and-batch.js';
|
|
23
23
|
export { loadSearchEntries, loadRecallSearchEntries } from './store/search-rows.js';
|
|
24
24
|
export { loadIndex, rebuildIndex, loadSessionDecayContext, SessionDecayContext } from './store/index-and-stats.js';
|
|
25
|
-
export { saveActiveTaskSnapshot, loadActiveTaskSnapshot, loadFreshActiveTaskSnapshot, SNAPSHOT_AMBIENT_MAX_AGE_MS, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, } from './store/sessions.js';
|
|
25
|
+
export { saveActiveTaskSnapshot, loadActiveTaskSnapshot, loadFreshActiveTaskSnapshot, SNAPSHOT_AMBIENT_MAX_AGE_MS, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, type ContinuityKey, } from './store/sessions.js';
|
|
26
26
|
export { listMemoryConflicts, replaceDetectedConflicts, resolveConflict } from './store/conflicts.js';
|
|
27
27
|
export { saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOutcome, writeSessionEndHandoff, } from './store/handoffs.js';
|
|
28
28
|
export { createCard, loadCard, listCards, loadCardDeps, loadCardRuns, loadCardComments, claimCard, heartbeatCard, blockCard, reviewCard, completeCard, reclaimExpiredCards, addCardComment, loadLatestHandoffForCard, } from './store-cards.js';
|
|
@@ -30,6 +30,7 @@ export { SessionHandoff, HandoffOutcome, HandoffEvidence, isHandoffOutcome } fro
|
|
|
30
30
|
export { Card, CardStatus, CardRun, CardComment, CardTransitions, isCardStatus, CARD_TRANSITIONS, CARD_LEASE_MS } from './card.js';
|
|
31
31
|
export { consolidate } from './consolidate/sleep.js';
|
|
32
32
|
export { ConsolidationResult } from './consolidate/run.js';
|
|
33
|
+
export { sleep, type SleepOpts, type SleepResult } from './api/sleep.js';
|
|
33
34
|
export { strengthBucket } from './dedupe.js';
|
|
34
35
|
export { isEmbeddingAvailable, getEmbedding } from './local-embedding.js';
|
|
35
36
|
export { cosineSimilarity, loadEmbeddingIndex, saveEmbeddingIndex, embedMemory, embedAll, } from './embeddings.js';
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ export { isHandoffOutcome } from './handoff.js';
|
|
|
32
32
|
// W2a: work-queue cards
|
|
33
33
|
export { isCardStatus, CARD_TRANSITIONS, CARD_LEASE_MS } from './card.js';
|
|
34
34
|
export { consolidate } from './consolidate/sleep.js';
|
|
35
|
+
export { sleep } from './api/sleep.js';
|
|
35
36
|
// Announced public in CHANGELOG 1.26.3 but never re-exported; the rest of dedupe.js stays internal.
|
|
36
37
|
export { strengthBucket } from './dedupe.js';
|
|
37
38
|
// Feature 1: Embedding search
|
package/dist/json.d.ts
CHANGED
|
@@ -2,4 +2,6 @@ export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
|
2
2
|
[key: string]: JsonValue;
|
|
3
3
|
};
|
|
4
4
|
export declare function isJsonString(value: JsonValue | undefined): value is string;
|
|
5
|
+
/** Windows PowerShell 5.1 saves JSON with a UTF-8 byte order mark that JSON.parse rejects, so one leading mark is dropped. */
|
|
6
|
+
export declare function readJsonFile(file: string): JsonValue;
|
|
5
7
|
//# sourceMappingURL=json.d.ts.map
|
package/dist/json.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
// Leaf module: the one value space JSON.parse can produce, so boundary guards narrow a shared type.
|
|
2
|
+
import * as fs from 'fs';
|
|
1
3
|
export function isJsonString(value) {
|
|
2
4
|
return typeof value === 'string';
|
|
3
5
|
}
|
|
6
|
+
/** Windows PowerShell 5.1 saves JSON with a UTF-8 byte order mark that JSON.parse rejects, so one leading mark is dropped. */
|
|
7
|
+
export function readJsonFile(file) {
|
|
8
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
9
|
+
return JSON.parse(text.codePointAt(0) === 0xfeff ? text.slice(1) : text);
|
|
10
|
+
}
|
|
4
11
|
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { openHippoDb } from './db.js';
|
|
2
|
+
export interface LedgerRootOpts {
|
|
3
|
+
/** The store serves many people, so the global store of the user running it belongs to none of them. */
|
|
4
|
+
readonly sharedStore?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** `hippoRoot` when it holds a store, else the global store (the prompt hook fires where no local one exists), else null. */
|
|
7
|
+
export declare function ledgerRoot(hippoRoot: string, opts?: LedgerRootOpts): string | null;
|
|
8
|
+
/** Runs `fn` on ledgerRoot's store. Best-effort: undefined on any failure, because a ledger failure must not break context or recall. */
|
|
9
|
+
export declare function withLedgerDb<T>(hippoRoot: string, fn: (db: ReturnType<typeof openHippoDb>) => T, opts?: LedgerRootOpts): T | undefined;
|
|
10
|
+
//# sourceMappingURL=ledger-db.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// The store the token ledger writes to for a caller's root; context, recall and the session hooks share it.
|
|
2
|
+
import { closeHippoDb, isSqliteBusy, noteStoreBusy, openHippoDb } from './db.js';
|
|
3
|
+
import { getGlobalRoot } from './shared.js';
|
|
4
|
+
import { isInitialized } from './store/open.js';
|
|
5
|
+
/** `hippoRoot` when it holds a store, else the global store (the prompt hook fires where no local one exists), else null. */
|
|
6
|
+
export function ledgerRoot(hippoRoot, opts) {
|
|
7
|
+
if (isInitialized(hippoRoot))
|
|
8
|
+
return hippoRoot;
|
|
9
|
+
if (opts?.sharedStore)
|
|
10
|
+
return null;
|
|
11
|
+
const globalRoot = getGlobalRoot();
|
|
12
|
+
return isInitialized(globalRoot) ? globalRoot : null;
|
|
13
|
+
}
|
|
14
|
+
/** Runs `fn` on ledgerRoot's store. Best-effort: undefined on any failure, because a ledger failure must not break context or recall. */
|
|
15
|
+
export function withLedgerDb(hippoRoot, fn, opts) {
|
|
16
|
+
let root = null;
|
|
17
|
+
try {
|
|
18
|
+
root = ledgerRoot(hippoRoot, opts);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// An unreadable store root means no ledger write; the ledger must never break context or recall.
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
if (root === null)
|
|
25
|
+
return undefined;
|
|
26
|
+
let db;
|
|
27
|
+
try {
|
|
28
|
+
db = openHippoDb(root);
|
|
29
|
+
return fn(db);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
// Best effort, but a busy store is the one failure an operator can act on, so it warns once.
|
|
33
|
+
if (isSqliteBusy(error))
|
|
34
|
+
noteStoreBusy('token ledger row skipped');
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
if (db)
|
|
39
|
+
closeHippoDb(db);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=ledger-db.js.map
|
|
@@ -7,6 +7,8 @@ export type EmbeddingRole = 'query' | 'passage';
|
|
|
7
7
|
export declare function prefixFor(model: string, role?: EmbeddingRole): string;
|
|
8
8
|
/** Check (synchronously) if @xenova/transformers or @huggingface/transformers is installed. */
|
|
9
9
|
export declare function isEmbeddingAvailable(): boolean;
|
|
10
|
+
/** Throws, with the reason, when the model's pipeline cannot load: a provider-level failure, not a per-item skip. */
|
|
11
|
+
export declare function requireLocalPipeline(model: string): Promise<void>;
|
|
10
12
|
export declare function resolveEmbeddingModel(hippoRoot: string, explicitModel?: string): string;
|
|
11
13
|
/** Embeds `text` with the local model, or returns `[]` when Transformers.js is missing or fails; `role` adds e5-style prefixes. */
|
|
12
14
|
export declare function getEmbedding(text: string, model?: string, role?: EmbeddingRole): Promise<number[]>;
|
package/dist/local-embedding.js
CHANGED
|
@@ -10,6 +10,7 @@ let _embeddingAvailable = null;
|
|
|
10
10
|
// A pipeline is expensive to load, so one instance per model is kept for the process.
|
|
11
11
|
const _pipelineInstances = new Map();
|
|
12
12
|
const _pipelineLoading = new Map();
|
|
13
|
+
const _pipelineErrors = new Map();
|
|
13
14
|
export const DEFAULT_EMBEDDING_MODEL = 'Xenova/all-MiniLM-L6-v2';
|
|
14
15
|
/** BGE was trained with CLS pooling; everything else gets mean, the sentence-transformers default, since CLS degrades mean-trained models. */
|
|
15
16
|
export function poolingFor(model) {
|
|
@@ -79,8 +80,10 @@ async function loadPipeline(model) {
|
|
|
79
80
|
const loading = (async () => {
|
|
80
81
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
82
|
const pkg = resolveTransformersPackage();
|
|
82
|
-
if (!pkg)
|
|
83
|
+
if (!pkg) {
|
|
84
|
+
_pipelineErrors.set(model, 'no transformers package is installed');
|
|
83
85
|
return null;
|
|
86
|
+
}
|
|
84
87
|
let pipelineFn = null;
|
|
85
88
|
try {
|
|
86
89
|
// SAFETY: the resolved module's shape is untyped by design (optional peer
|
|
@@ -98,11 +101,16 @@ async function loadPipeline(model) {
|
|
|
98
101
|
pipelineFn = mod.pipeline ?? mod.default?.pipeline;
|
|
99
102
|
}
|
|
100
103
|
catch (err) {
|
|
101
|
-
|
|
104
|
+
// String(err) keeps a Node error's [ERR_...] code, which callers match on.
|
|
105
|
+
const reason = `transformers import failed (${pkg}): ${String(err)}`;
|
|
106
|
+
log.debug(reason);
|
|
107
|
+
_pipelineErrors.set(model, reason);
|
|
102
108
|
return null;
|
|
103
109
|
}
|
|
104
|
-
if (!pipelineFn)
|
|
110
|
+
if (!pipelineFn) {
|
|
111
|
+
_pipelineErrors.set(model, `${pkg} exports no pipeline function`);
|
|
105
112
|
return null;
|
|
113
|
+
}
|
|
106
114
|
// The offline bundle used in egress-blocked sandboxes ships only the FP32 model, so use whichever file is on disk.
|
|
107
115
|
const cacheRoot = envModelCache();
|
|
108
116
|
const quantized = !cacheRoot
|
|
@@ -113,7 +121,9 @@ async function loadPipeline(model) {
|
|
|
113
121
|
return instance;
|
|
114
122
|
}
|
|
115
123
|
catch (err) {
|
|
116
|
-
|
|
124
|
+
const reason = `embedding pipeline load failed (${model}): ${String(err)}`;
|
|
125
|
+
log.debug(reason);
|
|
126
|
+
_pipelineErrors.set(model, reason);
|
|
117
127
|
return null;
|
|
118
128
|
}
|
|
119
129
|
finally {
|
|
@@ -123,6 +133,12 @@ async function loadPipeline(model) {
|
|
|
123
133
|
_pipelineLoading.set(model, loading);
|
|
124
134
|
return loading;
|
|
125
135
|
}
|
|
136
|
+
/** Throws, with the reason, when the model's pipeline cannot load: a provider-level failure, not a per-item skip. */
|
|
137
|
+
export async function requireLocalPipeline(model) {
|
|
138
|
+
if (await loadPipeline(model))
|
|
139
|
+
return;
|
|
140
|
+
throw new Error(`local embedding model did not load: ${_pipelineErrors.get(model) ?? 'unknown reason'}`);
|
|
141
|
+
}
|
|
126
142
|
export function resolveEmbeddingModel(hippoRoot, explicitModel) {
|
|
127
143
|
const direct = explicitModel?.trim();
|
|
128
144
|
if (direct)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ToolCall } from './protocol.js';
|
|
2
2
|
export declare function runPredictBaserateTool({ args, ctx, hippoRoot, tenantId }: ToolCall): string;
|
|
3
3
|
export declare function runStatusTool({ hippoRoot, config, tenantId }: ToolCall): string;
|
|
4
|
-
export declare function runConflictsTool(
|
|
4
|
+
export declare function runConflictsTool(call: ToolCall): string;
|
|
5
5
|
export declare function runResolveTool({ args, ctx, hippoRoot, tenantId }: ToolCall): string;
|
|
6
|
-
export declare function runShareTool({ args, hippoRoot, tenantId }: ToolCall): string;
|
|
6
|
+
export declare function runShareTool({ args, ctx, hippoRoot, tenantId }: ToolCall): string;
|
|
7
7
|
export declare function runPeersTool({ tenantId }: ToolCall): string;
|
|
8
8
|
//# sourceMappingURL=admin-tools.d.ts.map
|
package/dist/mcp/admin-tools.js
CHANGED
|
@@ -2,10 +2,28 @@
|
|
|
2
2
|
import { calculateStrength } from '../memory.js';
|
|
3
3
|
import { evalNow } from '../ablation.js';
|
|
4
4
|
import { loadStrengthRows } from '../store/candidates.js';
|
|
5
|
-
import { listMemoryConflicts, resolveConflict } from '../store/conflicts.js';
|
|
5
|
+
import { listMemoryConflicts, listTouchableConflicts, resolveConflict } from '../store/conflicts.js';
|
|
6
6
|
import { shareMemory, listPeers } from '../shared.js';
|
|
7
7
|
import { computePredictionBaserate } from '../predictions/store.js';
|
|
8
|
+
import { closeHippoDb, openHippoDb } from '../db.js';
|
|
9
|
+
import { NotFoundError } from '../api-errors.js';
|
|
10
|
+
import { classifyOriginProject } from '../project-identity.js';
|
|
11
|
+
import { canTouchScope, passesScopeFilterForRecall, personalScopeOf } from '../recall-scope.js';
|
|
12
|
+
import { selectEntriesByIds } from '../store/entry-reads.js';
|
|
13
|
+
import { selectMemoryReach } from '../store/tenant-lookup.js';
|
|
14
|
+
import { mcpActor } from './protocol.js';
|
|
8
15
|
import { isJsonString } from '../json.js';
|
|
16
|
+
const NOT_RESOLVED = 'Could not resolve. Check the conflict ID and --keep value.';
|
|
17
|
+
/** The scope of memory `id`, null when it has none or does not exist. */
|
|
18
|
+
function memoryScope(hippoRoot, id) {
|
|
19
|
+
const db = openHippoDb(hippoRoot);
|
|
20
|
+
try {
|
|
21
|
+
return selectMemoryReach(db, id)?.scope ?? null;
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
closeHippoDb(db);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
9
27
|
export function runPredictBaserateTool({ args, ctx, hippoRoot, tenantId }) {
|
|
10
28
|
// Text-only reply, matching the other MCP tools; the helper opens its own db
|
|
11
29
|
// and emits the audit, so call sites cannot drift.
|
|
@@ -55,8 +73,27 @@ export function runStatusTool({ hippoRoot, config, tenantId }) {
|
|
|
55
73
|
`Half-life default: ${config.defaultHalfLifeDays}d`,
|
|
56
74
|
].join('\n');
|
|
57
75
|
}
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
/** Pairs whose two rows the caller could recall: its repo or user-global, and no scope it was not asked for. */
|
|
77
|
+
function recallablePairs(call, conflicts, project) {
|
|
78
|
+
const own = personalScopeOf(mcpActor(call.ctx));
|
|
79
|
+
const db = openHippoDb(call.hippoRoot);
|
|
80
|
+
try {
|
|
81
|
+
const rows = selectEntriesByIds(db, conflicts.flatMap((c) => [c.memory_a_id, c.memory_b_id]), call.tenantId);
|
|
82
|
+
const shown = (id) => {
|
|
83
|
+
const row = rows.get(id);
|
|
84
|
+
return row !== undefined && classifyOriginProject(row.origin_project, project) !== 'cross-project'
|
|
85
|
+
&& passesScopeFilterForRecall(row.scope ?? null, undefined, own);
|
|
86
|
+
};
|
|
87
|
+
return conflicts.filter((c) => shown(c.memory_a_id) && shown(c.memory_b_id));
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
closeHippoDb(db);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function runConflictsTool(call) {
|
|
94
|
+
const { ctx, hippoRoot, tenantId } = call;
|
|
95
|
+
const touchable = listTouchableConflicts(hippoRoot, 'open', tenantId, mcpActor(ctx));
|
|
96
|
+
const conflicts = ctx?.project ? recallablePairs(call, touchable, ctx.project) : touchable;
|
|
60
97
|
if (conflicts.length === 0)
|
|
61
98
|
return 'No open conflicts.';
|
|
62
99
|
return conflicts.map((c) => `conflict_${c.id}: ${c.memory_a_id} <-> ${c.memory_b_id} (score=${c.score.toFixed(2)}) — ${c.reason}`).join('\n');
|
|
@@ -70,6 +107,8 @@ export function runResolveTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
70
107
|
const reason = isJsonString(args.reason) ? args.reason : undefined;
|
|
71
108
|
if (isNaN(conflictId) || !keepId)
|
|
72
109
|
return 'Required: conflict_id and keep.';
|
|
110
|
+
if (!listTouchableConflicts(hippoRoot, 'open', tenantId, mcpActor(ctx)).some((c) => c.id === conflictId))
|
|
111
|
+
return NOT_RESOLVED;
|
|
73
112
|
const result = resolveConflict(hippoRoot, conflictId, keepId, forget, tenantId, {
|
|
74
113
|
rejectLoserValue: rejectLoser,
|
|
75
114
|
reason,
|
|
@@ -78,15 +117,18 @@ export function runResolveTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
78
117
|
rejectedBy: ctx?.actor ?? 'mcp',
|
|
79
118
|
});
|
|
80
119
|
if (!result)
|
|
81
|
-
return
|
|
120
|
+
return NOT_RESOLVED;
|
|
82
121
|
const action = rejectLoser ? 'rejected (tombstoned) and removed' : forget ? 'deleted' : 'weakened';
|
|
83
122
|
return `Resolved conflict ${conflictId}: kept ${keepId}, ${action} ${result.loserId}`;
|
|
84
123
|
}
|
|
85
|
-
export function runShareTool({ args, hippoRoot, tenantId }) {
|
|
124
|
+
export function runShareTool({ args, ctx, hippoRoot, tenantId }) {
|
|
86
125
|
const shareId = String(args.id || '');
|
|
87
126
|
if (!shareId)
|
|
88
127
|
return 'Required: id (memory ID to share).';
|
|
89
128
|
const force = Boolean(args.force);
|
|
129
|
+
// Checked before shareMemory, whose personal-row refusal would tell another person the id exists.
|
|
130
|
+
if (!canTouchScope(mcpActor(ctx), memoryScope(hippoRoot, shareId)))
|
|
131
|
+
throw new NotFoundError(`Memory not found: ${shareId}`);
|
|
90
132
|
// Pass tenantId so shareMemory's readEntry filters by tenant. Without
|
|
91
133
|
// this, a Bearer for tenant A could call hippo_share with tenant B's
|
|
92
134
|
// id and copy the row to the global store. The 'Memory not found'
|
package/dist/mcp/memory-tools.js
CHANGED
|
@@ -25,15 +25,19 @@ export function runRememberTool({ args, ctx, hippoRoot, config, tenantId }) {
|
|
|
25
25
|
hippoRoot,
|
|
26
26
|
tenantId,
|
|
27
27
|
actor: mcpActor(ctx),
|
|
28
|
+
store: ctx?.store,
|
|
28
29
|
};
|
|
29
30
|
const result = apiRemember(apiCtx, {
|
|
30
31
|
content: text,
|
|
31
32
|
tags,
|
|
33
|
+
personal: args.personal === true,
|
|
34
|
+
project: ctx?.project,
|
|
32
35
|
});
|
|
33
36
|
const entry = readEntry(hippoRoot, result.id, tenantId);
|
|
34
37
|
// Auto-sleep: one run per store at a time, triggered by what arrived since the last one.
|
|
35
38
|
// Consolidation is host-wide, so only the host tenant's writes may start it.
|
|
36
|
-
if (
|
|
39
|
+
if (ctx?.autoSleep !== false &&
|
|
40
|
+
config.autoSleep.enabled &&
|
|
37
41
|
tenantId === resolveTenantId({}) &&
|
|
38
42
|
!autoSleepInFlight.has(hippoRoot) &&
|
|
39
43
|
countCreatedSinceLastSleep(hippoRoot, tenantId) >= config.autoSleep.threshold) {
|
|
@@ -64,13 +68,14 @@ export function runOutcomeTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
64
68
|
hippoRoot,
|
|
65
69
|
tenantId,
|
|
66
70
|
actor: mcpActor(ctx),
|
|
71
|
+
store: ctx?.store,
|
|
67
72
|
};
|
|
68
73
|
const { applied } = apiOutcome(apiCtx, ids, good);
|
|
69
74
|
return `Applied ${good ? 'positive' : 'negative'} outcome to ${applied} memories`;
|
|
70
75
|
}
|
|
71
76
|
export function runLearnTool({ args, ctx, hippoRoot, tenantId }) {
|
|
72
77
|
const days = Number(args.days) || 7;
|
|
73
|
-
const result = apiLearn({ hippoRoot, tenantId, actor: mcpActor(ctx) }, { repoPath: process.cwd(), days, profile: MCP_LEARN });
|
|
78
|
+
const result = apiLearn({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, { repoPath: process.cwd(), days, profile: MCP_LEARN });
|
|
74
79
|
if (result.status === 'not-a-repo')
|
|
75
80
|
return 'No git history found.';
|
|
76
81
|
if (result.status === 'no-commits')
|
package/dist/mcp/protocol.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { loadConfig } from '../config.js';
|
|
|
2
2
|
import type { Actor as ApiActor } from '../api.js';
|
|
3
3
|
import { type ResolveProjectIdentityOpts } from '../project-identity.js';
|
|
4
4
|
import type { JsonValue } from '../json.js';
|
|
5
|
+
import type { CallerProject } from '../prompt-hook.js';
|
|
6
|
+
import type { HippoStore } from '../store-port.js';
|
|
5
7
|
/** Same bounded walk as the CLI (ends at home, so HIPPO_HOME wins over ~/.hippo); cwd/opts are the test seam. */
|
|
6
8
|
export declare function findHippoRoot(cwd?: string, opts?: ResolveProjectIdentityOpts): string | null;
|
|
7
9
|
interface McpRequest {
|
|
@@ -50,6 +52,10 @@ export interface McpContext {
|
|
|
50
52
|
viaAuthResolver?: true;
|
|
51
53
|
/** Set by the HTTP transport for the host's operator; a context without a role is in-process and implies it. */
|
|
52
54
|
hostAdmin?: true;
|
|
55
|
+
owner?: string;
|
|
56
|
+
project?: CallerProject;
|
|
57
|
+
store?: HippoStore;
|
|
58
|
+
autoSleep?: false;
|
|
53
59
|
/**
|
|
54
60
|
* Per-client key for state isolation under HTTP-MCP. For stdio: 'stdio-${pid}'
|
|
55
61
|
* (one process = one client). For HTTP-SSE / HTTP MCP: hash(bearer + remoteAddr)
|
package/dist/mcp/protocol.js
CHANGED
|
@@ -5,7 +5,7 @@ import { INTERNAL_ERROR_MESSAGE, mapApiError } from '../http-util.js';
|
|
|
5
5
|
import { log } from '../log.js';
|
|
6
6
|
import { getGlobalRoot } from '../shared.js';
|
|
7
7
|
import { findHippoStoreDir } from '../project-identity.js';
|
|
8
|
-
import {
|
|
8
|
+
import { isStoreBusy, STORE_BUSY_MESSAGE } from '../db.js';
|
|
9
9
|
// ── Find hippo root ──
|
|
10
10
|
/** Same bounded walk as the CLI (ends at home, so HIPPO_HOME wins over ~/.hippo); cwd/opts are the test seam. */
|
|
11
11
|
export function findHippoRoot(cwd = process.cwd(), opts) {
|
|
@@ -18,7 +18,7 @@ export function findHippoRoot(cwd = process.cwd(), opts) {
|
|
|
18
18
|
}
|
|
19
19
|
/** JSON-RPC reply for a request that threw: typed API errors keep their text; anything else is logged and answered generically. */
|
|
20
20
|
export function mcpErrorResponse(id, err, requestId = randomUUID()) {
|
|
21
|
-
if (
|
|
21
|
+
if (isStoreBusy(err))
|
|
22
22
|
return { jsonrpc: '2.0', id, error: { code: -32603, message: STORE_BUSY_MESSAGE } };
|
|
23
23
|
const { status, message } = mapApiError(err);
|
|
24
24
|
if (status !== 500)
|
|
@@ -41,6 +41,8 @@ export function mcpActor(ctx) {
|
|
|
41
41
|
actor.viaAuthResolver = true;
|
|
42
42
|
if (ctx?.role === undefined || ctx.hostAdmin)
|
|
43
43
|
actor.hostAdmin = true;
|
|
44
|
+
if (ctx?.owner !== undefined)
|
|
45
|
+
actor.owner = ctx.owner;
|
|
44
46
|
return actor;
|
|
45
47
|
}
|
|
46
48
|
// ── JSON-ish domain type for untrusted MCP tool-call arguments ──
|
package/dist/mcp/recall-tools.js
CHANGED
|
@@ -5,6 +5,7 @@ import { dropHeldCopies, duplicateKey, storedTextKeys } from '../same-text.js';
|
|
|
5
5
|
import { retrieve as apiRetrieve, drillDown as apiDrillDown, assemble as apiAssemble, getContext as apiGetContext, buildSuppressionSummary } from '../api.js';
|
|
6
6
|
import { autoDetectContext } from '../context-auto.js';
|
|
7
7
|
import { resolveProjectIdentity } from '../project-identity.js';
|
|
8
|
+
import { isSharedStore } from '../config.js';
|
|
8
9
|
import { appendAuditEvent, auditQueryFields } from '../audit.js';
|
|
9
10
|
import { detectAnchoring, hashQueryText, biasHintEnabled, buildSessionKey, getOrCreateRing, appendRecall, snapshotRing, } from '../recall-history.js';
|
|
10
11
|
import { detectAvailabilityBias } from '../availability.js';
|
|
@@ -201,6 +202,7 @@ export async function runRecallTool(call) {
|
|
|
201
202
|
hippoRoot,
|
|
202
203
|
tenantId,
|
|
203
204
|
actor: mcpActor(ctx),
|
|
205
|
+
store: ctx?.store,
|
|
204
206
|
};
|
|
205
207
|
const anchorRing = biasHintEnabled('anchoring') && sessionId
|
|
206
208
|
? getOrCreateRing(sessionRecallHistoryMcp, buildSessionKey(tenantId, sessionId))
|
|
@@ -217,6 +219,7 @@ export async function runRecallTool(call) {
|
|
|
217
219
|
// The hint is computed below over the list MCP shows; the window band's copy would emit its audit row twice.
|
|
218
220
|
suppressAvailabilityHint: true,
|
|
219
221
|
keepHeldCopies: true,
|
|
222
|
+
project: ctx?.project,
|
|
220
223
|
...recallExtra,
|
|
221
224
|
showRanked: recallPresenter(budget, includeContinuity, anchorRing, queryHash, out),
|
|
222
225
|
});
|
|
@@ -237,6 +240,7 @@ export function runAssembleTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
237
240
|
hippoRoot,
|
|
238
241
|
tenantId,
|
|
239
242
|
actor: mcpActor(ctx),
|
|
243
|
+
store: ctx?.store,
|
|
240
244
|
};
|
|
241
245
|
const explicitScope = isJsonString(args.scope) && args.scope.length > 0
|
|
242
246
|
? args.scope
|
|
@@ -250,6 +254,7 @@ export function runAssembleTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
250
254
|
assembleExtra.scope = explicitScope;
|
|
251
255
|
const r = apiAssemble(apiCtx, sessionId, {
|
|
252
256
|
summarizeOlder,
|
|
257
|
+
project: ctx?.project,
|
|
253
258
|
...assembleExtra,
|
|
254
259
|
cost: assembleCost(sessionId),
|
|
255
260
|
});
|
|
@@ -267,6 +272,7 @@ export function runDrillTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
267
272
|
hippoRoot,
|
|
268
273
|
tenantId,
|
|
269
274
|
actor: mcpActor(ctx),
|
|
275
|
+
store: ctx?.store,
|
|
270
276
|
};
|
|
271
277
|
const drillExtra = {};
|
|
272
278
|
if (Number.isFinite(limit) && limit > 0)
|
|
@@ -275,7 +281,7 @@ export function runDrillTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
275
281
|
drillExtra.budget = budget;
|
|
276
282
|
if (depth !== undefined)
|
|
277
283
|
drillExtra.depth = depth;
|
|
278
|
-
const r = apiDrillDown(apiCtx, summaryId, { ...drillExtra, cost: drillCost });
|
|
284
|
+
const r = apiDrillDown(apiCtx, summaryId, { ...drillExtra, project: ctx?.project, cost: drillCost });
|
|
279
285
|
if ('failure' in r) {
|
|
280
286
|
// Only not_drillable is caller-actionable. not_found merges cross-tenant, scope-blocked and
|
|
281
287
|
// missing, because telling scope_blocked apart would leak private-row existence.
|
|
@@ -286,7 +292,15 @@ export function runDrillTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
286
292
|
}
|
|
287
293
|
return drillText(r);
|
|
288
294
|
}
|
|
295
|
+
// The served store names the project (an HTTP daemon runs from anywhere); the global root names none, so stdio falls back to its launch cwd.
|
|
296
|
+
function localProject(hippoRoot) {
|
|
297
|
+
const storeProject = resolveProjectIdentity(path.dirname(path.resolve(hippoRoot)));
|
|
298
|
+
return storeProject.name !== '' ? storeProject : resolveProjectIdentity(process.cwd());
|
|
299
|
+
}
|
|
289
300
|
export async function runContextTool({ args, ctx, hippoRoot, config, tenantId }) {
|
|
301
|
+
// Stdio names no project, and the store's folder or the launch cwd is no caller's; HTTP sends X-Hippo-Project.
|
|
302
|
+
if (isSharedStore(hippoRoot) && ctx?.project === undefined)
|
|
303
|
+
return 'hippo_context needs a project on a shared store; use hippo_recall';
|
|
290
304
|
const budget = args.budget === undefined
|
|
291
305
|
? config.defaultContextBudget
|
|
292
306
|
: Number(args.budget);
|
|
@@ -299,13 +313,12 @@ export async function runContextTool({ args, ctx, hippoRoot, config, tenantId })
|
|
|
299
313
|
const exactScope = isJsonString(args.scope) && args.scope.length > 0
|
|
300
314
|
? args.scope
|
|
301
315
|
: undefined;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
q: autoDetectContext(),
|
|
316
|
+
const result = await apiGetContext({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, {
|
|
317
|
+
// The server's git state is no caller's, so a shared-store caller gets its project's rows without a query.
|
|
318
|
+
q: ctx?.project ? undefined : autoDetectContext(),
|
|
306
319
|
budget,
|
|
307
320
|
exactScope,
|
|
308
|
-
currentProject:
|
|
321
|
+
currentProject: ctx?.project ?? localProject(hippoRoot),
|
|
309
322
|
cost: contextCost,
|
|
310
323
|
});
|
|
311
324
|
lastRecalledIds.set(resolveClientKey(ctx), result.entries.map((r) => r.entry.id));
|
package/dist/mcp/request.js
CHANGED
|
@@ -12,6 +12,7 @@ import { TOOLS, TOOLS_BY_NAME, ARGS_CHECKED_BY_API } from './tools.js';
|
|
|
12
12
|
import { runRecallTool, runAssembleTool, runDrillTool, runContextTool } from './recall-tools.js';
|
|
13
13
|
import { runRememberTool, runOutcomeTool, runLearnTool } from './memory-tools.js';
|
|
14
14
|
import { runPredictBaserateTool, runStatusTool, runConflictsTool, runResolveTool, runShareTool, runPeersTool } from './admin-tools.js';
|
|
15
|
+
import { sharedStoreRefusal } from './shared-gate.js';
|
|
15
16
|
import { isJsonString } from '../json.js';
|
|
16
17
|
/**
|
|
17
18
|
* Zero-install first run (`npx -y hippo-memory mcp` with no store anywhere):
|
|
@@ -126,6 +127,9 @@ export async function handleMcpRequest(req, ctx) {
|
|
|
126
127
|
if (!tool) {
|
|
127
128
|
return { jsonrpc: '2.0', id, error: { code: -32602, message: `Unknown tool: ${toolName.slice(0, 128)}` } };
|
|
128
129
|
}
|
|
130
|
+
const refusal = sharedStoreRefusal(toolName, ctx);
|
|
131
|
+
if (refusal !== undefined)
|
|
132
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: refusal }], isError: true } };
|
|
129
133
|
const argumentsValue = params?.arguments;
|
|
130
134
|
if (argumentsValue !== undefined && argumentsValue !== null && !isJsonObjectRecord(argumentsValue)) {
|
|
131
135
|
return { jsonrpc: '2.0', id, error: { code: -32602, message: `${toolName}: arguments must be an object` } };
|
|
@@ -2,10 +2,14 @@ import type { RingBuffer } from '../recall-history.js';
|
|
|
2
2
|
export declare const sessionRecallHistoryMcp: Map<string, RingBuffer>;
|
|
3
3
|
/** Test-only: reset the module-level recall-history Map. Call from beforeEach. */
|
|
4
4
|
export declare function __resetSessionRecallHistoryMcp(): void;
|
|
5
|
+
export declare const MAX_RECALL_CLIENTS = 4096;
|
|
5
6
|
export declare const lastRecalledIds: Map<string, string[]>;
|
|
6
7
|
export declare const autoSleepInFlight: Set<string>;
|
|
7
8
|
export declare function resolveClientKey(ctx: {
|
|
8
9
|
clientKey?: string;
|
|
9
10
|
tenantId: string;
|
|
11
|
+
project?: {
|
|
12
|
+
name: string;
|
|
13
|
+
};
|
|
10
14
|
} | undefined): string;
|
|
11
15
|
//# sourceMappingURL=session-state.d.ts.map
|