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/invalidation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { loadAllEntries,
|
|
1
|
+
import { writeEntryOn } from './store/entry-writes.js';
|
|
2
|
+
import { loadAllEntries, selectEntriesByIds } from './store/entry-reads.js';
|
|
3
|
+
import { openStore } from './store/open.js';
|
|
3
4
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
4
5
|
import { CHURN_STALE_TAG } from './memory.js';
|
|
5
6
|
import { GitReadError, gitLsFilesAtHead, fetchChurnWindowLog, gitGrepPresence, resolveCommitBefore, packageScriptsAt, } from './churn-git.js';
|
|
@@ -73,6 +74,7 @@ export function invalidateMatching(hippoRoot, target, tenantId, options) {
|
|
|
73
74
|
dryRun,
|
|
74
75
|
preview: [],
|
|
75
76
|
};
|
|
77
|
+
const weakened = [];
|
|
76
78
|
for (const entry of entries) {
|
|
77
79
|
if (options?.onlyId !== undefined) {
|
|
78
80
|
if (entry.id !== options.onlyId)
|
|
@@ -106,10 +108,24 @@ export function invalidateMatching(hippoRoot, target, tenantId, options) {
|
|
|
106
108
|
if (!entry.tags.includes('invalidated')) {
|
|
107
109
|
entry.tags.push('invalidated');
|
|
108
110
|
}
|
|
109
|
-
|
|
111
|
+
weakened.push(entry);
|
|
110
112
|
}
|
|
113
|
+
writeEntriesOnOneHandle(hippoRoot, weakened);
|
|
111
114
|
return result;
|
|
112
115
|
}
|
|
116
|
+
/** Each row commits alone, as a run of writeEntry calls would, but the store opens once. */
|
|
117
|
+
function writeEntriesOnOneHandle(hippoRoot, entries) {
|
|
118
|
+
if (entries.length === 0)
|
|
119
|
+
return;
|
|
120
|
+
const db = openStore(hippoRoot);
|
|
121
|
+
try {
|
|
122
|
+
for (const entry of entries)
|
|
123
|
+
writeEntryOn(db, hippoRoot, entry);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
closeHippoDb(db);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
113
129
|
const STOPWORDS = new Set([
|
|
114
130
|
'the', 'an', 'is', 'it', 'in', 'on', 'at', 'to', 'for', 'of', 'by',
|
|
115
131
|
'and', 'or', 'but', 'not', 'with', 'from', 'that', 'this', 'was', 'are',
|
|
@@ -225,16 +241,11 @@ function emptyChurnResult(dryRun, skippedPinned = [], error) {
|
|
|
225
241
|
result.error = error;
|
|
226
242
|
return result;
|
|
227
243
|
}
|
|
228
|
-
|
|
229
|
-
export function detectChurnStale(hippoRoot, repoRoot, opts) {
|
|
230
|
-
const dryRun = opts.dryRun === true;
|
|
231
|
-
if (!opts.projectName)
|
|
232
|
-
return emptyChurnResult(dryRun);
|
|
233
|
-
const entries = loadAllEntries(hippoRoot, opts.tenantId);
|
|
244
|
+
function collectChurnCandidates(entries, opts) {
|
|
234
245
|
const skippedPinned = [];
|
|
235
246
|
const candidates = [];
|
|
236
247
|
for (const entry of entries) {
|
|
237
|
-
if (!entry.origin_project || entry.origin_project !== opts.projectName)
|
|
248
|
+
if (!entry.origin_project || (entry.origin_project !== opts.projectName && entry.origin_project !== opts.legacyName))
|
|
238
249
|
continue;
|
|
239
250
|
if (entry.superseded_by)
|
|
240
251
|
continue;
|
|
@@ -249,6 +260,135 @@ export function detectChurnStale(hippoRoot, repoRoot, opts) {
|
|
|
249
260
|
continue;
|
|
250
261
|
candidates.push({ entry, refs: extractChurnRefs(entry.content) });
|
|
251
262
|
}
|
|
263
|
+
return { candidates, skippedPinned };
|
|
264
|
+
}
|
|
265
|
+
function loadChurnGitView(repoRoot, projectName, candidates, anchorOf, needs) {
|
|
266
|
+
const headFiles = gitLsFilesAtHead(repoRoot);
|
|
267
|
+
let windowLog = [];
|
|
268
|
+
if (needs.paths) {
|
|
269
|
+
const minAnchor = candidates.reduce((min, c) => {
|
|
270
|
+
const a = anchorOf(c.entry);
|
|
271
|
+
return min === '' || a < min ? a : min;
|
|
272
|
+
}, '');
|
|
273
|
+
windowLog = fetchChurnWindowLog(repoRoot, minAnchor);
|
|
274
|
+
}
|
|
275
|
+
const touchedInWindow = new Set();
|
|
276
|
+
for (const c of windowLog)
|
|
277
|
+
for (const f of c.files)
|
|
278
|
+
touchedInWindow.add(f.path);
|
|
279
|
+
const trackedIndex = buildTrackedIndex(new Set([...headFiles, ...touchedInWindow]));
|
|
280
|
+
const headIndex = buildTrackedIndex(headFiles);
|
|
281
|
+
const presentAtHeadSymbols = needs.symbols
|
|
282
|
+
? gitGrepPresence(repoRoot, [...new Set(candidates.flatMap((c) => c.refs.symbols))], 'HEAD')
|
|
283
|
+
: new Set();
|
|
284
|
+
const anchorCommitCache = new Map();
|
|
285
|
+
const resolveAnchorCommit = (anchorIso) => {
|
|
286
|
+
if (!anchorCommitCache.has(anchorIso)) {
|
|
287
|
+
anchorCommitCache.set(anchorIso, resolveCommitBefore(repoRoot, anchorIso));
|
|
288
|
+
}
|
|
289
|
+
return anchorCommitCache.get(anchorIso) ?? null;
|
|
290
|
+
};
|
|
291
|
+
const symbolsAtCommit = new Map();
|
|
292
|
+
const symbolsPresentAt = (hash, symbols) => {
|
|
293
|
+
const known = symbolsAtCommit.get(hash) ?? new Set();
|
|
294
|
+
const missing = symbols.filter((s) => !known.has(s));
|
|
295
|
+
if (missing.length > 0) {
|
|
296
|
+
for (const s of gitGrepPresence(repoRoot, missing, hash))
|
|
297
|
+
known.add(s);
|
|
298
|
+
symbolsAtCommit.set(hash, known);
|
|
299
|
+
}
|
|
300
|
+
return known;
|
|
301
|
+
};
|
|
302
|
+
const headScripts = needs.scripts ? packageScriptsAt(repoRoot, 'HEAD') : null;
|
|
303
|
+
const scriptsAtCommit = new Map();
|
|
304
|
+
const scriptsAt = (hash) => {
|
|
305
|
+
if (!scriptsAtCommit.has(hash)) {
|
|
306
|
+
scriptsAtCommit.set(hash, packageScriptsAt(repoRoot, hash));
|
|
307
|
+
}
|
|
308
|
+
return scriptsAtCommit.get(hash);
|
|
309
|
+
};
|
|
310
|
+
return { repoRoot, projectName, windowLog, trackedIndex, headIndex, presentAtHeadSymbols, headScripts, resolveAnchorCommit, symbolsPresentAt, scriptsAt };
|
|
311
|
+
}
|
|
312
|
+
function pathEvidence(git, paths, anchorTime) {
|
|
313
|
+
for (const rawPath of paths) {
|
|
314
|
+
const resolved = resolveTrackedPath(rawPath, git.repoRoot, git.projectName, git.trackedIndex);
|
|
315
|
+
if (!resolved)
|
|
316
|
+
continue;
|
|
317
|
+
if (isTrackedPath(git.headIndex, resolved)) {
|
|
318
|
+
const changed = git.windowLog.some((c) => new Date(c.date).getTime() > anchorTime && c.files.some((f) => f.path === resolved));
|
|
319
|
+
if (changed)
|
|
320
|
+
return `file-changed: ${resolved}`;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
// --no-renames means a rename shows as a D + A pair, so this also fires for renames.
|
|
324
|
+
const deleted = git.windowLog.some((c) => new Date(c.date).getTime() > anchorTime &&
|
|
325
|
+
c.files.some((f) => f.status === 'D' && f.path === resolved));
|
|
326
|
+
if (deleted)
|
|
327
|
+
return `file-deleted: ${resolved}`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
function symbolEvidence(git, symbols, anchor) {
|
|
333
|
+
const absent = symbols.filter((s) => !git.presentAtHeadSymbols.has(s));
|
|
334
|
+
if (absent.length === 0)
|
|
335
|
+
return null;
|
|
336
|
+
const anchorCommit = git.resolveAnchorCommit(anchor);
|
|
337
|
+
if (!anchorCommit)
|
|
338
|
+
return null;
|
|
339
|
+
const presentAtAnchor = git.symbolsPresentAt(anchorCommit, absent);
|
|
340
|
+
const hit = absent.find((s) => presentAtAnchor.has(s));
|
|
341
|
+
return hit ? `symbol-gone: \`${hit}\`` : null;
|
|
342
|
+
}
|
|
343
|
+
function scriptEvidence(git, scripts, anchor, headScripts) {
|
|
344
|
+
const anchorCommit = git.resolveAnchorCommit(anchor);
|
|
345
|
+
if (!anchorCommit)
|
|
346
|
+
return null;
|
|
347
|
+
const anchorScripts = git.scriptsAt(anchorCommit);
|
|
348
|
+
const hit = anchorScripts
|
|
349
|
+
? scripts.find((s) => anchorScripts[s] !== undefined && headScripts[s] === undefined)
|
|
350
|
+
: undefined;
|
|
351
|
+
return hit ? `script-gone: ${hit}` : null;
|
|
352
|
+
}
|
|
353
|
+
/** The first churn evidence against one candidate: a changed or deleted file, then a gone symbol, then a gone script. */
|
|
354
|
+
function churnEvidence(git, refs, anchor) {
|
|
355
|
+
let evidence = pathEvidence(git, refs.paths, new Date(anchor).getTime());
|
|
356
|
+
if (!evidence && refs.symbols.length > 0)
|
|
357
|
+
evidence = symbolEvidence(git, refs.symbols, anchor);
|
|
358
|
+
if (!evidence && refs.scripts.length > 0 && git.headScripts !== null) {
|
|
359
|
+
evidence = scriptEvidence(git, refs.scripts, anchor, git.headScripts);
|
|
360
|
+
}
|
|
361
|
+
return evidence;
|
|
362
|
+
}
|
|
363
|
+
function tagChurnStale(hippoRoot, tenantId, toTag, confirmedAt) {
|
|
364
|
+
// The git calls above can take seconds; a good outcome landing meanwhile moves the anchor past the evidence.
|
|
365
|
+
const confirmedNow = queryConfirmedAt(hippoRoot, tenantId);
|
|
366
|
+
const unconfirmed = toTag.filter((stale) => confirmedNow.get(stale.id) === confirmedAt.get(stale.id));
|
|
367
|
+
if (unconfirmed.length === 0)
|
|
368
|
+
return;
|
|
369
|
+
const db = openStore(hippoRoot);
|
|
370
|
+
try {
|
|
371
|
+
const live = selectEntriesByIds(db, unconfirmed.map((stale) => stale.id), tenantId);
|
|
372
|
+
for (const stale of unconfirmed) {
|
|
373
|
+
const entry = live.get(stale.id);
|
|
374
|
+
if (!entry || entry.tags.includes(CHURN_STALE_TAG))
|
|
375
|
+
continue;
|
|
376
|
+
const tagged = { ...entry, tags: [...entry.tags, CHURN_STALE_TAG] };
|
|
377
|
+
writeEntryOn(db, hippoRoot, tagged);
|
|
378
|
+
live.set(stale.id, tagged); // a repeated id is already tagged, as a fresh read would show
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
closeHippoDb(db);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/** Flags memories whose named file/symbol/script changed or disappeared since storage (or last confirmation); only adds/removes CHURN_STALE_TAG, never confidence/half-life/strength. */
|
|
386
|
+
export function detectChurnStale(hippoRoot, repoRoot, opts) {
|
|
387
|
+
const dryRun = opts.dryRun === true;
|
|
388
|
+
if (!opts.projectName)
|
|
389
|
+
return emptyChurnResult(dryRun);
|
|
390
|
+
const entries = loadAllEntries(hippoRoot, opts.tenantId);
|
|
391
|
+
const { candidates, skippedPinned } = collectChurnCandidates(entries, opts);
|
|
252
392
|
if (candidates.length === 0)
|
|
253
393
|
return emptyChurnResult(dryRun, skippedPinned);
|
|
254
394
|
const confirmedAt = queryConfirmedAt(hippoRoot, opts.tenantId);
|
|
@@ -258,108 +398,24 @@ export function detectChurnStale(hippoRoot, repoRoot, opts) {
|
|
|
258
398
|
const confirmedMs = confirmed ? Date.parse(confirmed) : NaN;
|
|
259
399
|
return confirmedMs > Date.parse(entry.created) ? new Date(confirmedMs).toISOString() : new Date(entry.created).toISOString();
|
|
260
400
|
};
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
401
|
+
const needs = {
|
|
402
|
+
paths: candidates.some((c) => c.refs.paths.length > 0),
|
|
403
|
+
symbols: candidates.some((c) => c.refs.symbols.length > 0),
|
|
404
|
+
scripts: candidates.some((c) => c.refs.scripts.length > 0),
|
|
405
|
+
};
|
|
406
|
+
if (!needs.paths && !needs.symbols && !needs.scripts) {
|
|
265
407
|
return { checked: candidates.length, marked: 0, alreadyMarked: 0, skippedPinned, dryRun, preview: [] };
|
|
266
408
|
}
|
|
267
409
|
const result = { checked: 0, marked: 0, alreadyMarked: 0, skippedPinned, dryRun, preview: [] };
|
|
268
410
|
try {
|
|
269
|
-
const
|
|
270
|
-
let windowLog = [];
|
|
271
|
-
if (needsPaths) {
|
|
272
|
-
const minAnchor = candidates.reduce((min, c) => {
|
|
273
|
-
const a = anchorOf(c.entry);
|
|
274
|
-
return min === '' || a < min ? a : min;
|
|
275
|
-
}, '');
|
|
276
|
-
windowLog = fetchChurnWindowLog(repoRoot, minAnchor);
|
|
277
|
-
}
|
|
278
|
-
const touchedInWindow = new Set();
|
|
279
|
-
for (const c of windowLog)
|
|
280
|
-
for (const f of c.files)
|
|
281
|
-
touchedInWindow.add(f.path);
|
|
282
|
-
const trackedIndex = buildTrackedIndex(new Set([...headFiles, ...touchedInWindow]));
|
|
283
|
-
const headIndex = buildTrackedIndex(headFiles);
|
|
284
|
-
const presentAtHeadSymbols = needsSymbols
|
|
285
|
-
? gitGrepPresence(repoRoot, [...new Set(candidates.flatMap((c) => c.refs.symbols))], 'HEAD')
|
|
286
|
-
: new Set();
|
|
287
|
-
const anchorCommitCache = new Map();
|
|
288
|
-
const resolveAnchorCommit = (anchorIso) => {
|
|
289
|
-
if (!anchorCommitCache.has(anchorIso)) {
|
|
290
|
-
anchorCommitCache.set(anchorIso, resolveCommitBefore(repoRoot, anchorIso));
|
|
291
|
-
}
|
|
292
|
-
return anchorCommitCache.get(anchorIso) ?? null;
|
|
293
|
-
};
|
|
294
|
-
const symbolsAtCommit = new Map();
|
|
295
|
-
const symbolsPresentAt = (hash, symbols) => {
|
|
296
|
-
const known = symbolsAtCommit.get(hash) ?? new Set();
|
|
297
|
-
const missing = symbols.filter((s) => !known.has(s));
|
|
298
|
-
if (missing.length > 0) {
|
|
299
|
-
for (const s of gitGrepPresence(repoRoot, missing, hash))
|
|
300
|
-
known.add(s);
|
|
301
|
-
symbolsAtCommit.set(hash, known);
|
|
302
|
-
}
|
|
303
|
-
return known;
|
|
304
|
-
};
|
|
305
|
-
const headScripts = needsScripts ? packageScriptsAt(repoRoot, 'HEAD') : null;
|
|
306
|
-
const scriptsAtCommit = new Map();
|
|
411
|
+
const git = loadChurnGitView(repoRoot, opts.legacyName ?? opts.projectName, candidates, anchorOf, needs);
|
|
307
412
|
// Collected here, written only after every candidate's evidence is
|
|
308
413
|
// computed: a GitReadError thrown mid-loop must never leave an earlier
|
|
309
414
|
// candidate tagged while a later one aborts the run untagged.
|
|
310
415
|
const toTag = [];
|
|
311
416
|
for (const { entry, refs } of candidates) {
|
|
312
417
|
result.checked++;
|
|
313
|
-
const
|
|
314
|
-
const anchorTime = new Date(anchor).getTime();
|
|
315
|
-
let evidence = null;
|
|
316
|
-
for (const rawPath of refs.paths) {
|
|
317
|
-
const resolved = resolveTrackedPath(rawPath, repoRoot, opts.projectName, trackedIndex);
|
|
318
|
-
if (!resolved)
|
|
319
|
-
continue;
|
|
320
|
-
if (isTrackedPath(headIndex, resolved)) {
|
|
321
|
-
const changed = windowLog.some((c) => new Date(c.date).getTime() > anchorTime && c.files.some((f) => f.path === resolved));
|
|
322
|
-
if (changed) {
|
|
323
|
-
evidence = `file-changed: ${resolved}`;
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
// --no-renames means a rename shows as a D + A pair, so this also fires for renames.
|
|
329
|
-
const deleted = windowLog.some((c) => new Date(c.date).getTime() > anchorTime &&
|
|
330
|
-
c.files.some((f) => f.status === 'D' && f.path === resolved));
|
|
331
|
-
if (deleted) {
|
|
332
|
-
evidence = `file-deleted: ${resolved}`;
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
if (!evidence && refs.symbols.length > 0) {
|
|
338
|
-
const absent = refs.symbols.filter((s) => !presentAtHeadSymbols.has(s));
|
|
339
|
-
if (absent.length > 0) {
|
|
340
|
-
const anchorCommit = resolveAnchorCommit(anchor);
|
|
341
|
-
if (anchorCommit) {
|
|
342
|
-
const presentAtAnchor = symbolsPresentAt(anchorCommit, absent);
|
|
343
|
-
const hit = absent.find((s) => presentAtAnchor.has(s));
|
|
344
|
-
if (hit)
|
|
345
|
-
evidence = `symbol-gone: \`${hit}\``;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (!evidence && refs.scripts.length > 0 && headScripts !== null) {
|
|
350
|
-
const anchorCommit = resolveAnchorCommit(anchor);
|
|
351
|
-
if (anchorCommit) {
|
|
352
|
-
if (!scriptsAtCommit.has(anchorCommit)) {
|
|
353
|
-
scriptsAtCommit.set(anchorCommit, packageScriptsAt(repoRoot, anchorCommit));
|
|
354
|
-
}
|
|
355
|
-
const anchorScripts = scriptsAtCommit.get(anchorCommit);
|
|
356
|
-
const hit = anchorScripts
|
|
357
|
-
? refs.scripts.find((s) => anchorScripts[s] !== undefined && headScripts[s] === undefined)
|
|
358
|
-
: undefined;
|
|
359
|
-
if (hit)
|
|
360
|
-
evidence = `script-gone: ${hit}`;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
418
|
+
const evidence = churnEvidence(git, refs, anchorOf(entry));
|
|
363
419
|
if (!evidence)
|
|
364
420
|
continue;
|
|
365
421
|
const headline = entry.content.replace(/\s+/g, ' ').slice(0, 60);
|
|
@@ -372,18 +428,8 @@ export function detectChurnStale(hippoRoot, repoRoot, opts) {
|
|
|
372
428
|
result.preview.push({ id: entry.id, headline, evidence, already: false });
|
|
373
429
|
toTag.push(entry);
|
|
374
430
|
}
|
|
375
|
-
if (!dryRun && toTag.length > 0)
|
|
376
|
-
|
|
377
|
-
const confirmedNow = queryConfirmedAt(hippoRoot, opts.tenantId);
|
|
378
|
-
for (const stale of toTag) {
|
|
379
|
-
if (confirmedNow.get(stale.id) !== confirmedAt.get(stale.id))
|
|
380
|
-
continue;
|
|
381
|
-
const entry = readEntry(hippoRoot, stale.id, opts.tenantId);
|
|
382
|
-
if (!entry || entry.tags.includes(CHURN_STALE_TAG))
|
|
383
|
-
continue;
|
|
384
|
-
writeEntry(hippoRoot, { ...entry, tags: [...entry.tags, CHURN_STALE_TAG] });
|
|
385
|
-
}
|
|
386
|
-
}
|
|
431
|
+
if (!dryRun && toTag.length > 0)
|
|
432
|
+
tagChurnStale(hippoRoot, opts.tenantId, toTag, confirmedAt);
|
|
387
433
|
}
|
|
388
434
|
catch (err) {
|
|
389
435
|
if (err instanceof GitReadError)
|
package/dist/json.d.ts
ADDED
package/dist/json.js
ADDED
package/dist/judgment.js
CHANGED
|
@@ -38,8 +38,7 @@ const KINDS = ['error', 'decision', 'convention', 'preference', 'trivia'];
|
|
|
38
38
|
const VALENCES = ['critical', 'negative', 'positive', 'neutral'];
|
|
39
39
|
/** Absent key means hippo keeps its pre-Jev behaviour and makes no HTTP call. */
|
|
40
40
|
export function judgmentApiKey() {
|
|
41
|
-
|
|
42
|
-
return key ? key : undefined;
|
|
41
|
+
return envTypesafeApiKey();
|
|
43
42
|
}
|
|
44
43
|
function oneOf(value, allowed) {
|
|
45
44
|
return allowed.find((option) => option === value) ?? null;
|
package/dist/local-embedding.js
CHANGED
|
@@ -104,7 +104,7 @@ async function loadPipeline(model) {
|
|
|
104
104
|
if (!pipelineFn)
|
|
105
105
|
return null;
|
|
106
106
|
// The offline bundle used in egress-blocked sandboxes ships only the FP32 model, so use whichever file is on disk.
|
|
107
|
-
const cacheRoot = envModelCache()
|
|
107
|
+
const cacheRoot = envModelCache();
|
|
108
108
|
const quantized = !cacheRoot
|
|
109
109
|
|| fs.existsSync(path.join(cacheRoot, model, 'onnx', 'model_quantized.onnx'));
|
|
110
110
|
try {
|
package/dist/mcp/admin-tools.js
CHANGED
|
@@ -5,13 +5,10 @@ import { loadStrengthRows } from '../store/candidates.js';
|
|
|
5
5
|
import { listMemoryConflicts, resolveConflict } from '../store/conflicts.js';
|
|
6
6
|
import { shareMemory, listPeers } from '../shared.js';
|
|
7
7
|
import { computePredictionBaserate } from '../predictions/store.js';
|
|
8
|
-
import { isJsonString } from '
|
|
8
|
+
import { isJsonString } from '../json.js';
|
|
9
9
|
export function runPredictBaserateTool({ args, ctx, hippoRoot, tenantId }) {
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// MCP tool convention (no structured JSON over the wire). Direct call
|
|
13
|
-
// to computePredictionBaserate; helper opens its own db + emits audit
|
|
14
|
-
// (single source of truth, no caller-site drift).
|
|
10
|
+
// Text-only reply, matching the other MCP tools; the helper opens its own db
|
|
11
|
+
// and emits the audit, so call sites cannot drift.
|
|
15
12
|
const classTag = String(args.class_tag || '').trim();
|
|
16
13
|
if (!classTag)
|
|
17
14
|
return 'No class_tag provided. Usage: pass class_tag matching a class used in past predictions (e.g. "migration-effort").';
|
|
@@ -68,8 +65,7 @@ export function runResolveTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
68
65
|
const conflictId = Number(args.conflict_id);
|
|
69
66
|
const keepId = String(args.keep || '');
|
|
70
67
|
const forget = Boolean(args.forget);
|
|
71
|
-
//
|
|
72
|
-
// resolveConflict's opts (plan §5 — mirrors the CLI's --reject-loser).
|
|
68
|
+
// Optional rejectLoser + reason pass straight to resolveConflict, as the CLI's --reject-loser does.
|
|
73
69
|
const rejectLoser = Boolean(args.rejectLoser);
|
|
74
70
|
const reason = isJsonString(args.reason) ? args.reason : undefined;
|
|
75
71
|
if (isNaN(conflictId) || !keepId)
|
|
@@ -77,12 +73,8 @@ export function runResolveTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
77
73
|
const result = resolveConflict(hippoRoot, conflictId, keepId, forget, tenantId, {
|
|
78
74
|
rejectLoserValue: rejectLoser,
|
|
79
75
|
reason,
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// rejected_by AND the conflict_resolve audit's actor both landed as
|
|
83
|
-
// 'cli' even though the caller was MCP. ctx.actor carries the
|
|
84
|
-
// auth-resolved actor for HTTP-MCP (see McpContext above); stdio
|
|
85
|
-
// callers pass no ctx, so 'mcp' is the honest fallback there.
|
|
76
|
+
// rejectedBy defaults to 'cli', so name the real actor for the tombstone and audit:
|
|
77
|
+
// ctx.actor for HTTP-MCP, 'mcp' for stdio callers, which pass no ctx.
|
|
86
78
|
rejectedBy: ctx?.actor ?? 'mcp',
|
|
87
79
|
});
|
|
88
80
|
if (!result)
|
|
@@ -105,9 +97,7 @@ export function runShareTool({ args, hippoRoot, tenantId }) {
|
|
|
105
97
|
return `Shared [${shared.id}] to global store. Source: ${shared.source}`;
|
|
106
98
|
}
|
|
107
99
|
export function runPeersTool({ tenantId }) {
|
|
108
|
-
//
|
|
109
|
-
// tenantId is the caller's tenant (matches hippo_share above);
|
|
110
|
-
// passing undefined would restore the pre-D4 host-wide behaviour.
|
|
100
|
+
// Tenant-scope peer discovery to the caller, as hippo_share does; undefined would list host-wide.
|
|
111
101
|
const peers = listPeers(undefined, tenantId);
|
|
112
102
|
if (peers.length === 0)
|
|
113
103
|
return 'No peers found.';
|
package/dist/mcp/format.js
CHANGED
|
@@ -120,7 +120,7 @@ export function tailSection(rows) {
|
|
|
120
120
|
}
|
|
121
121
|
return '\n' + lines.join('\n');
|
|
122
122
|
}
|
|
123
|
-
//
|
|
123
|
+
// The hint depends on the query alone, so api.retrieve's copy is the one shown; JSON.stringify fences the phrase.
|
|
124
124
|
export function planningSection(r) {
|
|
125
125
|
if (r.planningFallacyHint) {
|
|
126
126
|
const h = r.planningFallacyHint;
|
package/dist/mcp/framing.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
// MCP stdio transport framing
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// https://modelcontextprotocol.io/specification/.../basic/transports#stdio
|
|
5
|
-
// We also accept legacy LSP-style `Content-Length` framing so the
|
|
6
|
-
// printf-and-pipe smoke test from issue #13 still works.
|
|
1
|
+
// MCP stdio transport framing: newline-delimited JSON-RPC, no embedded newlines
|
|
2
|
+
// (https://modelcontextprotocol.io/specification/.../basic/transports#stdio). Legacy LSP-style
|
|
3
|
+
// `Content-Length` framing is also accepted so a printf-and-pipe smoke test still works.
|
|
7
4
|
const HEADER_DELIM = Buffer.from('\r\n\r\n');
|
|
8
5
|
export function parseFrame(buffer) {
|
|
9
6
|
// Strip leading whitespace/newlines between messages.
|
package/dist/mcp/protocol.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import type { JsonValue } from '../json.js';
|
|
4
5
|
/** Same bounded walk as the CLI (ends at home, so HIPPO_HOME wins over ~/.hippo); cwd/opts are the test seam. */
|
|
5
6
|
export declare function findHippoRoot(cwd?: string, opts?: ResolveProjectIdentityOpts): string | null;
|
|
6
7
|
interface McpRequest {
|
|
@@ -44,7 +45,7 @@ export interface McpContext {
|
|
|
44
45
|
* assuming admin, or a member key over HTTP-MCP would act as admin.
|
|
45
46
|
*/
|
|
46
47
|
role?: 'admin' | 'member';
|
|
47
|
-
/**
|
|
48
|
+
/** Scope grants for the HTTP-MCP caller's key. Absent for stdio (admin, needs none). */
|
|
48
49
|
scopes?: readonly string[];
|
|
49
50
|
viaAuthResolver?: true;
|
|
50
51
|
/** Set by the HTTP transport for the host's operator; a context without a role is in-process and implies it. */
|
|
@@ -63,10 +64,6 @@ export interface McpContext {
|
|
|
63
64
|
* a member key never acts as admin through MCP.
|
|
64
65
|
*/
|
|
65
66
|
export declare function mcpActor(ctx: McpContext | undefined): ApiActor;
|
|
66
|
-
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
67
|
-
[key: string]: JsonValue;
|
|
68
|
-
};
|
|
69
|
-
export declare function isJsonString(v: JsonValue | undefined): v is string;
|
|
70
67
|
export declare function isJsonBoolean(v: JsonValue | undefined): v is boolean;
|
|
71
68
|
export declare function isJsonObjectRecord(v: JsonValue | undefined): v is {
|
|
72
69
|
[key: string]: JsonValue;
|
package/dist/mcp/protocol.js
CHANGED
|
@@ -43,9 +43,7 @@ export function mcpActor(ctx) {
|
|
|
43
43
|
actor.hostAdmin = true;
|
|
44
44
|
return actor;
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
return typeof v === 'string';
|
|
48
|
-
}
|
|
46
|
+
// ── JSON-ish domain type for untrusted MCP tool-call arguments ──
|
|
49
47
|
export function isJsonBoolean(v) {
|
|
50
48
|
return typeof v === 'boolean';
|
|
51
49
|
}
|
package/dist/mcp/recall-tools.js
CHANGED
|
@@ -11,9 +11,11 @@ import { detectAvailabilityBias } from '../availability.js';
|
|
|
11
11
|
import { openHippoDb, closeHippoDb } from '../db.js';
|
|
12
12
|
import { estimateTokens } from '../token-ledger.js';
|
|
13
13
|
import { assembleCost, assembleText, drillCost, drillText } from '../context-render.js';
|
|
14
|
-
import { mcpActor,
|
|
14
|
+
import { mcpActor, isJsonBoolean } from './protocol.js';
|
|
15
15
|
import { sessionRecallHistoryMcp, lastRecalledIds, resolveClientKey } from './session-state.js';
|
|
16
16
|
import { formatContinuityBlock, formatMemories, memoryCost, memoriesReserve, snapshotPiece, handoffPiece, trailPiece, contextCost, tailSection, planningSection, } from './format.js';
|
|
17
|
+
import { isJsonString } from '../json.js';
|
|
18
|
+
import { MAX_ID_LEN } from '../http-util.js';
|
|
17
19
|
function parseRecallArgs(args, defaultBudget) {
|
|
18
20
|
const query = String(args.query || '');
|
|
19
21
|
const budget = Number(args.budget) || defaultBudget;
|
|
@@ -35,9 +37,9 @@ function parseRecallArgs(args, defaultBudget) {
|
|
|
35
37
|
const scorerWindow = args.scorer_window === undefined
|
|
36
38
|
? undefined
|
|
37
39
|
: Number(args.scorer_window);
|
|
38
|
-
// session_id drives the goal-stack boost inside api.retrieve; same trim and
|
|
40
|
+
// session_id drives the goal-stack boost inside api.retrieve; same trim and MAX_ID_LEN cap as fresh_tail_session_id.
|
|
39
41
|
const sessionIdRaw = isJsonString(args.session_id) ? args.session_id.trim() : '';
|
|
40
|
-
const sessionId = sessionIdRaw.length > 0 && sessionIdRaw.length <=
|
|
42
|
+
const sessionId = sessionIdRaw.length > 0 && sessionIdRaw.length <= MAX_ID_LEN
|
|
41
43
|
? sessionIdRaw
|
|
42
44
|
: undefined;
|
|
43
45
|
const recallExtra = {};
|
|
@@ -71,7 +73,7 @@ function recallPresenter(budget, includeContinuity, anchorRing, queryHash, out)
|
|
|
71
73
|
const showTail = tailRows.length > 0 && pays(tailSection(tailRows));
|
|
72
74
|
const continuityPiece = includeContinuity && apiResult.continuity ? `\n\n${formatContinuityBlock(apiResult.continuity)}` : '';
|
|
73
75
|
const showContinuity = continuityPiece !== '' && pays(continuityPiece);
|
|
74
|
-
//
|
|
76
|
+
// The hints and Cutoff block describe the list MCP shows, not the window band in apiResult.
|
|
75
77
|
const render = (cut) => {
|
|
76
78
|
const list = dropHeldCopies(cut, (r) => r.entry); // after every cut, so a merged row cut here never hides its sources
|
|
77
79
|
const anchoring = anchorRing ? detectAnchoring(snapshotRing(anchorRing), queryHash, list[0]?.entry.id ?? null) : null;
|
|
@@ -168,11 +170,8 @@ function auditRecallHints(call, query, anchorRing, queryHash, rendered) {
|
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
else {
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
// for prompt hashing (NOT hashQueryText which is FNV-1a 32-bit
|
|
174
|
-
// for recall matching; brute-force trivial for low-entropy
|
|
175
|
-
// queries). Codex round-2 P2 catch.
|
|
173
|
+
// No sessionId, so no ring. Hash the prompt with SHA-256/16 as the recall audit does (api.ts:854):
|
|
174
|
+
// hashQueryText is FNV-1a 32-bit, trivial to brute-force on low-entropy queries.
|
|
176
175
|
appendRecallAudit(hippoRoot, {
|
|
177
176
|
tenantId,
|
|
178
177
|
actor: ctx?.actor ?? 'mcp',
|
|
@@ -278,10 +277,8 @@ export function runDrillTool({ args, ctx, hippoRoot, tenantId }) {
|
|
|
278
277
|
drillExtra.depth = depth;
|
|
279
278
|
const r = apiDrillDown(apiCtx, summaryId, { ...drillExtra, cost: drillCost });
|
|
280
279
|
if ('failure' in r) {
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
// (codex round 3 P1: distinguishing scope_blocked would leak
|
|
284
|
-
// private-row existence on this surface).
|
|
280
|
+
// Only not_drillable is caller-actionable. not_found merges cross-tenant, scope-blocked and
|
|
281
|
+
// missing, because telling scope_blocked apart would leak private-row existence.
|
|
285
282
|
if (r.failure === 'not_drillable') {
|
|
286
283
|
return `Id ${summaryId} is a leaf row, not a level-2+ summary; nothing to drill into.`;
|
|
287
284
|
}
|
|
@@ -303,12 +300,12 @@ export async function runContextTool({ args, ctx, hippoRoot, config, tenantId })
|
|
|
303
300
|
? args.scope
|
|
304
301
|
: undefined;
|
|
305
302
|
// 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.
|
|
306
|
-
const storeProject = resolveProjectIdentity(path.dirname(path.resolve(hippoRoot)))
|
|
303
|
+
const storeProject = resolveProjectIdentity(path.dirname(path.resolve(hippoRoot)));
|
|
307
304
|
const result = await apiGetContext({ hippoRoot, tenantId, actor: mcpActor(ctx) }, {
|
|
308
305
|
q: autoDetectContext(),
|
|
309
306
|
budget,
|
|
310
307
|
exactScope,
|
|
311
|
-
currentProject: storeProject !== '' ? storeProject : resolveProjectIdentity(process.cwd())
|
|
308
|
+
currentProject: storeProject.name !== '' ? storeProject : resolveProjectIdentity(process.cwd()),
|
|
312
309
|
cost: contextCost,
|
|
313
310
|
});
|
|
314
311
|
lastRecalledIds.set(resolveClientKey(ctx), result.entries.map((r) => r.entry.id));
|
package/dist/mcp/request.js
CHANGED
|
@@ -7,11 +7,12 @@ import { openHippoDb, closeHippoDb } from '../db.js';
|
|
|
7
7
|
import { estimateTokens, recordTokenUse } from '../token-ledger.js';
|
|
8
8
|
import { PACKAGE_VERSION } from '../version.js';
|
|
9
9
|
import { validateToolArgs } from './tool-args.js';
|
|
10
|
-
import { findHippoRoot,
|
|
10
|
+
import { findHippoRoot, isJsonObjectRecord } from './protocol.js';
|
|
11
11
|
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 { isJsonString } from '../json.js';
|
|
15
16
|
/**
|
|
16
17
|
* Zero-install first run (`npx -y hippo-memory mcp` with no store anywhere):
|
|
17
18
|
* create the global store instead of failing every tool call, and say so on
|
|
@@ -24,7 +25,7 @@ function createGlobalStoreOnFirstRun() {
|
|
|
24
25
|
log.warn(`no memory store found; created the global store at ${root}. Run \`hippo init\` in a project for a project store.`);
|
|
25
26
|
return root;
|
|
26
27
|
}
|
|
27
|
-
// ── Token ledger
|
|
28
|
+
// ── Token ledger ──
|
|
28
29
|
const MCP_TOKEN_SURFACES = new Map([
|
|
29
30
|
['hippo_recall', 'mcp_recall'],
|
|
30
31
|
['hippo_context', 'mcp_context'],
|
|
@@ -83,7 +84,7 @@ async function executeTool(name, args, ctx) {
|
|
|
83
84
|
// resolve tenant from HIPPO_TENANT.
|
|
84
85
|
const hippoRoot = ctx?.hippoRoot ?? findHippoRoot() ?? createGlobalStoreOnFirstRun();
|
|
85
86
|
const config = loadConfig(hippoRoot);
|
|
86
|
-
//
|
|
87
|
+
// Every store read in this server returns to the caller and is
|
|
87
88
|
// tenant-isolated. Resolved once per tool call: prefer the transport's
|
|
88
89
|
// ctx.tenantId so an HTTP Bearer for tenant B doesn't drop to HIPPO_TENANT.
|
|
89
90
|
const tenantId = ctx?.tenantId ?? resolveTenantId({});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Per-process state the MCP tools share across calls: recall rings, last recalled ids, auto-sleep runs.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// architecture (per-pipeline rings; no IPC).
|
|
2
|
+
// Per-(tenant, session) recall-history rings for the MCP pipeline, kept apart from
|
|
3
|
+
// the CLI/HTTP rings: each pipeline owns its rings, with no IPC between them.
|
|
5
4
|
export const sessionRecallHistoryMcp = new Map();
|
|
6
5
|
/** Test-only: reset the module-level recall-history Map. Call from beforeEach. */
|
|
7
6
|
export function __resetSessionRecallHistoryMcp() {
|
package/dist/mcp/stdio.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Stdio transport: newline-delimited JSON-RPC frames in on stdin, replies out on stdout.
|
|
2
2
|
import { errorFields, log } from '../log.js';
|
|
3
3
|
import { parseFrame } from './framing.js';
|
|
4
|
-
import { mcpErrorResponse, isJsonObjectRecord
|
|
4
|
+
import { mcpErrorResponse, isJsonObjectRecord } from './protocol.js';
|
|
5
5
|
import { handleMcpRequest } from './request.js';
|
|
6
|
+
import { isJsonString } from '../json.js';
|
|
6
7
|
// MCP stdio transport spec: messages are newline-delimited JSON-RPC, no embedded newlines.
|
|
7
8
|
// https://modelcontextprotocol.io/specification/.../basic/transports#stdio
|
|
8
9
|
function send(msg) {
|