@bcmyguest/openmem 0.0.0-stage → 0.8.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/.agents/skills/openmem/SKILL.md +34 -0
- package/LICENSE +190 -0
- package/README.md +140 -2
- package/bin/hindsight-project-index +530 -0
- package/cli/coding-agent/access-policy.mjs +38 -0
- package/cli/coding-agent/agents.mjs +34 -0
- package/cli/coding-agent/bank-from-cwd.mjs +140 -0
- package/cli/coding-agent/capabilities.mjs +34 -0
- package/cli/coding-agent/context-store.mjs +81 -0
- package/cli/coding-agent/context-trim.mjs +897 -0
- package/cli/coding-agent/file-io.mjs +72 -0
- package/cli/coding-agent/hook-harnesses.mjs +88 -0
- package/cli/coding-agent/hook-runner.mjs +284 -0
- package/cli/coding-agent/install-access.mjs +119 -0
- package/cli/coding-agent/install-context-proxy.mjs +333 -0
- package/cli/coding-agent/install-rtk.mjs +60 -0
- package/cli/coding-agent/install-shim.mjs +201 -0
- package/cli/coding-agent/opencode-config.mjs +107 -0
- package/cli/coding-agent/opencode-plugin.mjs +132 -0
- package/cli/coding-agent/opencode2-plugin/index.js +299 -0
- package/cli/coding-agent/recall-frame.mjs +29 -0
- package/cli/coding-agent/remove-upstream-access.mjs +122 -0
- package/cli/coding-agent/runtime.mjs +40 -0
- package/cli/coding-agent/session-db.mjs +626 -0
- package/cli/coding-agent/session-facade.mjs +86 -0
- package/cli/coding-agent/toml-edit.mjs +161 -0
- package/cli/cognee-mcp.mjs +260 -0
- package/cli/context-management.mjs +254 -0
- package/cli/feed-sync.mjs +27 -0
- package/cli/install-coding-agent.mjs +298 -0
- package/cli/install-memory-mcp.mjs +426 -0
- package/cli/is-main.mjs +18 -0
- package/cli/mcp/context-sql-runner.mjs +58 -0
- package/cli/mcp/context-store.mjs +670 -0
- package/cli/mcp/context.mjs +61 -0
- package/cli/mcp/export/cognee-dataset-name.mjs +12 -0
- package/cli/mcp/export/cognee.mjs +119 -0
- package/cli/mcp/export/hindsight.mjs +48 -0
- package/cli/mcp/export/index.mjs +26 -0
- package/cli/mcp/export/mem0.mjs +31 -0
- package/cli/mcp/knowledge.mjs +333 -0
- package/cli/mcp/llm-synthesize.mjs +50 -0
- package/cli/mcp/mem0-scope-id.mjs +22 -0
- package/cli/mcp/memory-facade.mjs +521 -0
- package/cli/mcp/memory-reflect-consolidate.mjs +120 -0
- package/cli/mcp/notebooklm-banks.mjs +21 -0
- package/cli/mcp/notebooklm-hindsight-rest.mjs +45 -0
- package/cli/mcp/notebooklm-provider.mjs +225 -0
- package/cli/mcp/pmm-records.mjs +23 -0
- package/cli/mcp/portability.mjs +158 -0
- package/cli/mcp/stdio.mjs +65 -0
- package/cli/mcp/unified-mcp-install.mjs +65 -0
- package/cli/mem0-mcp.mjs +108 -0
- package/cli/notebooklm-mcp.mjs +236 -0
- package/cli/openmem-mcp.mjs +38 -0
- package/cli/openmem.mjs +222 -0
- package/cli/roots.mjs +85 -0
- package/cli/schedule.mjs +176 -0
- package/cli/stack.mjs +438 -0
- package/cli/switch-memory-provider.mjs +358 -0
- package/cli/wiring-report.mjs +125 -0
- package/compose.bench.yaml +12 -0
- package/compose.yaml +227 -0
- package/config/coding-agent.json +13 -0
- package/config/hindsight.env.example +26 -0
- package/config/index.yml +54 -0
- package/config/litellm.lemond.yaml +40 -0
- package/config/litellm.ollama.yaml +33 -0
- package/config/notebooklm.yml +33 -0
- package/config/openmem.toml.example +124 -0
- package/docs/OPENMEM.md +23 -0
- package/docs/architecture.md +245 -0
- package/docs/assets/README.md +10 -0
- package/docs/assets/setup-demo.gif +0 -0
- package/docs/coding-agent-hooks.md +75 -0
- package/docs/coding-agent-scopes.md +22 -0
- package/docs/coding-agents.md +71 -0
- package/docs/cognee.md +37 -0
- package/docs/compatibility.md +80 -0
- package/docs/concepts.md +135 -0
- package/docs/context-management.md +255 -0
- package/docs/context-proxy.md +115 -0
- package/docs/context-research.md +215 -0
- package/docs/context-store.md +277 -0
- package/docs/hindsight.md +77 -0
- package/docs/index-sources-roadmap.md +38 -0
- package/docs/index-sources.md +39 -0
- package/docs/indexer-sinks.md +122 -0
- package/docs/indexing.md +57 -0
- package/docs/manual-stack-setup.md +74 -0
- package/docs/mcp-install-guide.md +4 -0
- package/docs/mcp-setup.md +104 -0
- package/docs/mem0.md +141 -0
- package/docs/memory-portability.md +73 -0
- package/docs/memory-providers.md +246 -0
- package/docs/meridian-litellm.md +198 -0
- package/docs/model-configuration.md +106 -0
- package/docs/notebooklm.md +83 -0
- package/docs/npx-install.md +172 -0
- package/docs/openmem-config.md +194 -0
- package/docs/provider-wizard-hooks.md +222 -0
- package/docs/quickstart.md +107 -0
- package/docs/setup-wizard.md +117 -0
- package/docs/sources-feeds.md +39 -0
- package/docs/sources-readwise.md +23 -0
- package/docs/sources-zotero.md +22 -0
- package/glama.json +4 -0
- package/indexer/Dockerfile +38 -0
- package/indexer/code_outline.py +146 -0
- package/indexer/feed_sync.py +107 -0
- package/indexer/index_config.py +290 -0
- package/indexer/pytest.ini +5 -0
- package/indexer/sinks/__init__.py +148 -0
- package/indexer/sinks/_http.py +44 -0
- package/indexer/sinks/cognee.py +329 -0
- package/indexer/sinks/hindsight.py +107 -0
- package/indexer/sinks/mem0.py +247 -0
- package/indexer/watch.py +240 -0
- package/package.json +96 -4
- package/providers/_template/compose.yaml +52 -0
- package/providers/cognee/OPERATOR.md +82 -0
- package/providers/cognee/compose.yaml +89 -0
- package/providers/cognee/lib/clack.mjs +157 -0
- package/providers/cognee/lib/coding-agent.mjs +26 -0
- package/providers/cognee/lib/cognee-mcp-install.mjs +153 -0
- package/providers/cognee/lib/config.mjs +98 -0
- package/providers/cognee/lib/env-file.mjs +194 -0
- package/providers/cognee/lib/env-setup.mjs +83 -0
- package/providers/cognee/lib/llm-env.mjs +382 -0
- package/providers/cognee/lib/ports.mjs +25 -0
- package/providers/cognee/lib/smoke.mjs +246 -0
- package/providers/cognee/lib/stack-env.mjs +134 -0
- package/providers/cognee/lib/state.mjs +101 -0
- package/providers/cognee/lib/summary.mjs +56 -0
- package/providers/cognee/lib/validate.mjs +97 -0
- package/providers/cognee/manifest.json +56 -0
- package/providers/cognee/wizard.mjs +134 -0
- package/providers/graphiti/manifest.json +15 -0
- package/providers/hindsight/lib/clack.mjs +347 -0
- package/providers/hindsight/lib/coding-agent.mjs +204 -0
- package/providers/hindsight/lib/config.mjs +79 -0
- package/providers/hindsight/lib/env-setup.mjs +72 -0
- package/providers/hindsight/lib/litellm-routes.mjs +196 -0
- package/providers/hindsight/lib/meridian.mjs +307 -0
- package/providers/hindsight/lib/model-backend.mjs +243 -0
- package/providers/hindsight/lib/notebooklm-mcp-install.mjs +127 -0
- package/providers/hindsight/lib/ollama-compose.mjs +51 -0
- package/providers/hindsight/lib/ports.mjs +30 -0
- package/providers/hindsight/lib/probe.mjs +54 -0
- package/providers/hindsight/lib/stack-env.mjs +125 -0
- package/providers/hindsight/lib/state.mjs +80 -0
- package/providers/hindsight/lib/summary.mjs +57 -0
- package/providers/hindsight/lib/validate.mjs +81 -0
- package/providers/hindsight/manifest.json +58 -0
- package/providers/hindsight/wizard.mjs +150 -0
- package/providers/letta/manifest.json +29 -0
- package/providers/mem0/OPERATOR.md +143 -0
- package/providers/mem0/compose.yaml +79 -0
- package/providers/mem0/lib/coding-agent.mjs +58 -0
- package/providers/mem0/lib/config.mjs +45 -0
- package/providers/mem0/lib/configure.mjs +201 -0
- package/providers/mem0/lib/dotenv.mjs +71 -0
- package/providers/mem0/lib/env-file.mjs +110 -0
- package/providers/mem0/lib/http.mjs +69 -0
- package/providers/mem0/lib/litellm.mjs +457 -0
- package/providers/mem0/lib/mcp-install.mjs +165 -0
- package/providers/mem0/lib/secret.mjs +30 -0
- package/providers/mem0/lib/smoke.mjs +179 -0
- package/providers/mem0/lib/stack-env.mjs +255 -0
- package/providers/mem0/lib/state.mjs +67 -0
- package/providers/mem0/lib/summary.mjs +88 -0
- package/providers/mem0/lib/url.mjs +106 -0
- package/providers/mem0/lib/validate.mjs +158 -0
- package/providers/mem0/manifest.json +47 -0
- package/providers/mem0/wizard.mjs +146 -0
- package/providers/zep/manifest.json +29 -0
- package/proxy/config.yaml +33 -0
- package/proxy/openmem_context/__init__.py +6 -0
- package/proxy/openmem_context/adapters.py +504 -0
- package/proxy/openmem_context/compress.py +53 -0
- package/proxy/openmem_context/core.py +606 -0
- package/proxy/openmem_context/embed.py +212 -0
- package/proxy/openmem_context/recall.py +49 -0
- package/proxy/openmem_context/session.py +70 -0
- package/proxy/openmem_context/sessiondb.py +276 -0
- package/proxy/openmem_context/settings.py +134 -0
- package/proxy/openmem_context/store.py +129 -0
- package/proxy/openmem_context/tokens.py +123 -0
- package/proxy/openmem_context/ttl.py +78 -0
- package/proxy/openmem_context_hook.py +34 -0
- package/scripts/check-pack.mjs +219 -0
- package/scripts/gen-masks.mjs +83 -0
- package/scripts/gen-masks.sh +4 -0
- package/scripts/promotion-provider-smoke.mjs +34 -0
- package/scripts/run-setup.sh +30 -0
- package/scripts/setup.mjs +107 -0
- package/scripts/setup.sh +5 -0
- package/scripts/spin-up.sh +45 -0
- package/scripts/wizard.sh +5 -0
- package/server.json +34 -0
- package/sync/Dockerfile +27 -0
- package/sync/memory_backend.py +87 -0
- package/sync/notebooklm_sync.py +664 -0
- package/wizard/bin/pm.mjs +97 -0
- package/wizard/bin/setup.mjs +33 -0
- package/wizard/lib/compose-version.mjs +32 -0
- package/wizard/lib/compose.mjs +70 -0
- package/wizard/lib/context-step.mjs +140 -0
- package/wizard/lib/curl.mjs +103 -0
- package/wizard/lib/docker-build.mjs +148 -0
- package/wizard/lib/exec.mjs +41 -0
- package/wizard/lib/indexer.mjs +6 -0
- package/wizard/lib/litellm-env.mjs +55 -0
- package/wizard/lib/load-machine-env.mjs +4 -0
- package/wizard/lib/log.mjs +32 -0
- package/wizard/lib/notebooklm-agent-access.mjs +92 -0
- package/wizard/lib/notebooklm.mjs +214 -0
- package/wizard/lib/parse-args.mjs +87 -0
- package/wizard/lib/paths.mjs +39 -0
- package/wizard/lib/pm-config.mjs +371 -0
- package/wizard/lib/ports.mjs +94 -0
- package/wizard/lib/prompts.mjs +85 -0
- package/wizard/lib/provider-wizard/helpers.mjs +98 -0
- package/wizard/lib/provider-wizard/run-steps.mjs +129 -0
- package/wizard/lib/providers/registry.mjs +262 -0
- package/wizard/lib/run-clack.mjs +108 -0
- package/wizard/lib/run-wizard.mjs +750 -0
- package/wizard/lib/validate-setup.mjs +62 -0
- package/wizard/lib/wizard-action.mjs +34 -0
- package/wizard/lib/wizard-conf.mjs +71 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Same mapping as indexer/sinks/mem0.py scope_id (user_id / run_id). */
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
|
|
4
|
+
export function mem0ScopeId(value) {
|
|
5
|
+
const s = String(value ?? "");
|
|
6
|
+
if (!/\s/.test(s)) return s;
|
|
7
|
+
let out = "";
|
|
8
|
+
let inSpace = false;
|
|
9
|
+
for (const ch of s) {
|
|
10
|
+
if (ch.trim() === "") {
|
|
11
|
+
if (!inSpace) {
|
|
12
|
+
out += "_";
|
|
13
|
+
inSpace = true;
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
out += ch;
|
|
17
|
+
inSpace = false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const digest = crypto.createHash("sha256").update(s, "utf8").digest("hex").slice(0, 8);
|
|
21
|
+
return `${out}~${digest}`;
|
|
22
|
+
}
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { search as cogneeSearch } from "../cognee-mcp.mjs";
|
|
2
|
+
import { callMemoryConsolidate, callMemoryReflect } from "./memory-reflect-consolidate.mjs";
|
|
3
|
+
import { memoryExport, memoryImport } from "./portability.mjs";
|
|
4
|
+
import { KNOWLEDGE_MCP_TOOLS, KNOWLEDGE_TOOL_NAMES, callKnowledgeTool } from "./knowledge.mjs";
|
|
5
|
+
import { CONTEXT_STORE_MCP_TOOLS, CONTEXT_STORE_TOOL_NAMES, callContextStoreTool } from "./context-store.mjs";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { parseDocument } from "yaml";
|
|
10
|
+
import { bankFromCwd } from "../coding-agent/bank-from-cwd.mjs";
|
|
11
|
+
import { accessAllowed, accessConfigPath, readAccessConfig } from "../coding-agent/access-policy.mjs";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
15
|
+
|
|
16
|
+
export const UNIFIED_MCP_TOOLS = [
|
|
17
|
+
{
|
|
18
|
+
name: "memory_project_scope",
|
|
19
|
+
title: "Resolve project bank",
|
|
20
|
+
description:
|
|
21
|
+
"Resolve the project bank (user_id / dataset) and access policy for a workspace path. " +
|
|
22
|
+
"Call it instead of guessing a bank name. Local mapping only.",
|
|
23
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
cwd: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Absolute workspace path (defaults to the MCP process working directory)",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
additionalProperties: false,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "memory_add",
|
|
37
|
+
title: "Store a memory",
|
|
38
|
+
description:
|
|
39
|
+
"Store durable facts, decisions or session notes in the active provider. " +
|
|
40
|
+
"Pass bank from memory_project_scope for project work. Hindsight ingests async by default.",
|
|
41
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
content: { type: "string", description: "Plain text to store as a durable memory" },
|
|
46
|
+
bank: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Hindsight bank or mem0 user_id / Cognee dataset scope (default: openmem-mcp)",
|
|
49
|
+
},
|
|
50
|
+
metadata: { type: "object", description: "Optional provider-specific metadata object" },
|
|
51
|
+
async: {
|
|
52
|
+
type: "boolean",
|
|
53
|
+
description:
|
|
54
|
+
"Hindsight only: true (default) returns after enqueueing ingest; false waits for extraction (may hit MCP client timeouts).",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
required: ["content"],
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "memory_search",
|
|
63
|
+
title: "Search memories",
|
|
64
|
+
description:
|
|
65
|
+
"Return ranked raw hits from stored memories. memory_recall suits questions; memory_reflect synthesizes an answer.",
|
|
66
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
query: { type: "string", description: "Search query text" },
|
|
71
|
+
bank: { type: "string", description: "Optional bank / user_id / dataset scope" },
|
|
72
|
+
limit: { type: "number", description: "Max hits where the provider supports it" },
|
|
73
|
+
},
|
|
74
|
+
required: ["query"],
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "memory_recall",
|
|
80
|
+
title: "Recall context for a question",
|
|
81
|
+
description:
|
|
82
|
+
"Retrieve stored context for a natural-language question (Hindsight recall; search on mem0 and Cognee).",
|
|
83
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
query: { type: "string", description: "Question or topic to recall context for" },
|
|
88
|
+
bank: { type: "string", description: "Optional bank / user_id / dataset scope" },
|
|
89
|
+
limit: { type: "number", description: "Max items where supported" },
|
|
90
|
+
},
|
|
91
|
+
required: ["query"],
|
|
92
|
+
additionalProperties: false,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "memory_forget",
|
|
97
|
+
title: "Delete memories",
|
|
98
|
+
description:
|
|
99
|
+
"Delete memories: mem0 needs memory_id; on Hindsight a bank without memory_id drops the whole bank. " +
|
|
100
|
+
"Confirm with the user first. Not available on Cognee.",
|
|
101
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: "object",
|
|
104
|
+
properties: {
|
|
105
|
+
memory_id: { type: "string", description: "Provider memory id (required for mem0 deletes)" },
|
|
106
|
+
bank: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "Hindsight bank to drop (when memory_id omitted), or mem0 user_id scope",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
additionalProperties: false,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "memory_export",
|
|
116
|
+
title: "Export memories (PMM)",
|
|
117
|
+
description:
|
|
118
|
+
"Export memories as PMM v1 JSON or JSONL for backup or migration. Pass bank for large stores.",
|
|
119
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: {
|
|
123
|
+
format: { type: "string", enum: ["json", "jsonl"], description: "Serialization format (default json)" },
|
|
124
|
+
bank: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description:
|
|
127
|
+
"Hindsight bank, mem0 user_id, or indexer bank for Cognee. Omit on Hindsight to export all banks.",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
additionalProperties: false,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "memory_import",
|
|
135
|
+
title: "Import memories (PMM)",
|
|
136
|
+
description:
|
|
137
|
+
"Import a PMM v1 payload from memory_export. Content and scope carry over; embeddings and graphs do not.",
|
|
138
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
data: { type: "string", description: "Serialized PMM export (json or jsonl text)" },
|
|
143
|
+
merge_strategy: {
|
|
144
|
+
type: "string",
|
|
145
|
+
enum: ["skip_duplicates", "overwrite"],
|
|
146
|
+
description: "How to treat records that already exist (default skip_duplicates)",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
required: ["data"],
|
|
150
|
+
additionalProperties: false,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "memory_provider_status",
|
|
155
|
+
title: "Provider status",
|
|
156
|
+
description:
|
|
157
|
+
"Report the active provider id and backend URL.",
|
|
158
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
159
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "memory_reflect",
|
|
163
|
+
title: "Reflect / synthesize an answer",
|
|
164
|
+
description:
|
|
165
|
+
"Synthesize a reasoned answer from stored memories, with evidence. The answer is not stored.",
|
|
166
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
167
|
+
inputSchema: {
|
|
168
|
+
type: "object",
|
|
169
|
+
properties: {
|
|
170
|
+
query: { type: "string", description: "Question to answer from memory" },
|
|
171
|
+
bank: { type: "string", description: "Optional bank / user_id / dataset scope" },
|
|
172
|
+
limit: { type: "number", description: "Max evidence items where supported" },
|
|
173
|
+
},
|
|
174
|
+
required: ["query"],
|
|
175
|
+
additionalProperties: false,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "memory_consolidate",
|
|
180
|
+
title: "Consolidate / cognify",
|
|
181
|
+
description:
|
|
182
|
+
"Rebuild the provider's graph or index after bulk ingest (Cognee cognify; no-op on mem0).",
|
|
183
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
184
|
+
inputSchema: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
bank: { type: "string", description: "Bank / dataset to consolidate (required for Cognee cognify)" },
|
|
188
|
+
},
|
|
189
|
+
additionalProperties: false,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
...KNOWLEDGE_MCP_TOOLS,
|
|
193
|
+
...CONTEXT_STORE_MCP_TOOLS,
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
export function textResult(value, isError = false) {
|
|
197
|
+
const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
198
|
+
return { content: [{ type: "text", text }], isError };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {string} url
|
|
203
|
+
* @param {RequestInit} init
|
|
204
|
+
* @param {typeof fetch} [fetchFn]
|
|
205
|
+
*/
|
|
206
|
+
async function httpJson(url, init, fetchFn = fetch) {
|
|
207
|
+
const res = await fetchFn(url, init);
|
|
208
|
+
const text = await res.text();
|
|
209
|
+
let json;
|
|
210
|
+
try {
|
|
211
|
+
json = text ? JSON.parse(text) : null;
|
|
212
|
+
} catch {
|
|
213
|
+
json = text;
|
|
214
|
+
}
|
|
215
|
+
if (!res.ok) {
|
|
216
|
+
const detail = typeof json === "string" ? json : JSON.stringify(json);
|
|
217
|
+
throw new Error(`${init.method || "GET"} ${url} → ${res.status}: ${String(detail).slice(0, 400)}`);
|
|
218
|
+
}
|
|
219
|
+
return json;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const DEFAULT_BANK = "openmem-mcp";
|
|
223
|
+
|
|
224
|
+
/** Hindsight ingest: default async so Cursor/Claude MCP clients do not time out on extraction. */
|
|
225
|
+
function hindsightMemoryAddAsync(args) {
|
|
226
|
+
if (args.async === true) return true;
|
|
227
|
+
if (args.async === false) return false;
|
|
228
|
+
const env = String(process.env.PM_MEMORY_ADD_ASYNC || "").trim().toLowerCase();
|
|
229
|
+
if (env === "0" || env === "false" || env === "no") return false;
|
|
230
|
+
if (env === "1" || env === "true" || env === "yes") return true;
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @param {ReturnType<import("./context.mjs").resolveMcpContext>} ctx
|
|
236
|
+
* @param {string} name
|
|
237
|
+
* @param {Record<string, unknown>} [args]
|
|
238
|
+
* @param {typeof fetch} [fetchFn]
|
|
239
|
+
*/
|
|
240
|
+
export async function callUnifiedTool(ctx, name, args = {}, fetchFn = fetch) {
|
|
241
|
+
if (name === "memory_project_scope") {
|
|
242
|
+
const cwd = args.cwd === undefined ? process.cwd() : args.cwd;
|
|
243
|
+
if (typeof cwd !== "string" || !cwd.trim() || !path.isAbsolute(cwd)) {
|
|
244
|
+
return textResult("cwd must be an absolute workspace path", true);
|
|
245
|
+
}
|
|
246
|
+
const home = process.env.HOME || os.homedir();
|
|
247
|
+
const accessConfig = readAccessConfig(home);
|
|
248
|
+
const indexPath = process.env.HINDSIGHT_INDEX_CONFIG
|
|
249
|
+
? path.resolve(process.env.HINDSIGHT_INDEX_CONFIG.replace(/^~(?=$|\/)/, home))
|
|
250
|
+
: fs.existsSync(path.join(home, ".config", "hindsight", "index.yml"))
|
|
251
|
+
? path.join(home, ".config", "hindsight", "index.yml")
|
|
252
|
+
: path.join(PKG_ROOT, "config", "index.yml");
|
|
253
|
+
let indexBanks;
|
|
254
|
+
try {
|
|
255
|
+
const doc = parseDocument(fs.readFileSync(indexPath, "utf8"), { uniqueKeys: true, version: "1.1" });
|
|
256
|
+
if (doc.errors.length) throw doc.errors[0];
|
|
257
|
+
indexBanks = doc.toJS()?.banks;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
return textResult(`Unable to read active index.yml (${indexPath}): ${err.message}`, true);
|
|
260
|
+
}
|
|
261
|
+
const permitted = accessAllowed(cwd, accessConfig);
|
|
262
|
+
return textResult({
|
|
263
|
+
cwd: path.resolve(cwd),
|
|
264
|
+
bank: bankFromCwd(cwd, { home, env: process.env, banks: indexBanks || {}, mapPathToBank: accessConfig?.mapPathToBank }),
|
|
265
|
+
accessPermitted: permitted,
|
|
266
|
+
accessConfigPath: accessConfigPath(home),
|
|
267
|
+
personalMemoryMapping: accessConfig?.mapPathToBank || {},
|
|
268
|
+
indexConfigPath: indexPath,
|
|
269
|
+
indexBanks: indexBanks || {},
|
|
270
|
+
policyConfigValid: accessConfig !== null,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
if (name === "memory_export") {
|
|
274
|
+
const out = await memoryExport(ctx, args, fetchFn);
|
|
275
|
+
return textResult(typeof out === "string" ? out : out);
|
|
276
|
+
}
|
|
277
|
+
if (name === "memory_import") {
|
|
278
|
+
const data = String(args.data || "");
|
|
279
|
+
if (!data.trim()) return textResult("data is required", true);
|
|
280
|
+
const summary = await memoryImport(ctx, data, args, fetchFn, callUnifiedTool);
|
|
281
|
+
return textResult(summary);
|
|
282
|
+
}
|
|
283
|
+
if (name === "memory_provider_status") {
|
|
284
|
+
return textResult({
|
|
285
|
+
provider: ctx.provider,
|
|
286
|
+
hindsightUrl: ctx.hindsightUrl,
|
|
287
|
+
mem0BaseUrl: ctx.mem0?.baseUrl,
|
|
288
|
+
cogneeBaseUrl: ctx.cognee?.baseUrl,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
if (name === "memory_reflect") {
|
|
292
|
+
try {
|
|
293
|
+
const payload = await callMemoryReflect(ctx, args, (n, a, f) => callUnifiedTool(ctx, n, a, f), fetchFn);
|
|
294
|
+
return textResult(payload);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
return textResult(err instanceof Error ? err.message : String(err), true);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (name === "memory_consolidate") {
|
|
300
|
+
try {
|
|
301
|
+
return textResult(await callMemoryConsolidate(ctx, args, fetchFn));
|
|
302
|
+
} catch (err) {
|
|
303
|
+
return textResult(err instanceof Error ? err.message : String(err), true);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (CONTEXT_STORE_TOOL_NAMES.has(name)) {
|
|
308
|
+
try {
|
|
309
|
+
return textResult(await callContextStoreTool(name, args));
|
|
310
|
+
} catch (err) {
|
|
311
|
+
return textResult(err instanceof Error ? err.message : String(err), true);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (KNOWLEDGE_TOOL_NAMES.has(name)) {
|
|
316
|
+
try {
|
|
317
|
+
return textResult(await callKnowledgeTool(ctx, name, args, fetchFn));
|
|
318
|
+
} catch (err) {
|
|
319
|
+
return textResult(err instanceof Error ? err.message : String(err), true);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (ctx.provider === "hindsight") {
|
|
324
|
+
return callHindsight(ctx, name, args, fetchFn);
|
|
325
|
+
}
|
|
326
|
+
if (ctx.provider === "mem0") {
|
|
327
|
+
return callMem0(ctx, name, args, fetchFn);
|
|
328
|
+
}
|
|
329
|
+
if (ctx.provider === "cognee") {
|
|
330
|
+
return callCognee(ctx, name, args, fetchFn);
|
|
331
|
+
}
|
|
332
|
+
return textResult(`unsupported provider "${ctx.provider}"`, true);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* @param {ReturnType<import("./context.mjs").resolveMcpContext>} ctx
|
|
337
|
+
*/
|
|
338
|
+
async function callHindsight(ctx, name, args, fetchFn) {
|
|
339
|
+
const base = ctx.hindsightUrl;
|
|
340
|
+
if (!base) return textResult("Hindsight API URL not configured", true);
|
|
341
|
+
const bank = String(args.bank || DEFAULT_BANK).trim() || DEFAULT_BANK;
|
|
342
|
+
const encBank = encodeURIComponent(bank);
|
|
343
|
+
|
|
344
|
+
if (name === "memory_add") {
|
|
345
|
+
const content = String(args.content || "").trim();
|
|
346
|
+
if (!content) return textResult("content is required", true);
|
|
347
|
+
const json = await httpJson(
|
|
348
|
+
`${base}/v1/default/banks/${encBank}/memories`,
|
|
349
|
+
{
|
|
350
|
+
method: "POST",
|
|
351
|
+
headers: { "content-type": "application/json" },
|
|
352
|
+
body: JSON.stringify({ items: [{ content }], async: hindsightMemoryAddAsync(args) }),
|
|
353
|
+
},
|
|
354
|
+
fetchFn,
|
|
355
|
+
);
|
|
356
|
+
return textResult({ provider: "hindsight", bank, result: json });
|
|
357
|
+
}
|
|
358
|
+
if (name === "memory_search" || name === "memory_recall") {
|
|
359
|
+
const query = String(args.query || "").trim();
|
|
360
|
+
if (!query) return textResult("query is required", true);
|
|
361
|
+
const json = await httpJson(
|
|
362
|
+
`${base}/v1/default/banks/${encBank}/memories/recall`,
|
|
363
|
+
{
|
|
364
|
+
method: "POST",
|
|
365
|
+
headers: { "content-type": "application/json" },
|
|
366
|
+
body: JSON.stringify({ query, ...(args.budget ? { budget: args.budget } : {}),
|
|
367
|
+
...(args.maxTokens ? { max_tokens: args.maxTokens } : {}) }),
|
|
368
|
+
},
|
|
369
|
+
fetchFn,
|
|
370
|
+
);
|
|
371
|
+
return textResult({ provider: "hindsight", bank, query, result: json });
|
|
372
|
+
}
|
|
373
|
+
if (name === "memory_forget") {
|
|
374
|
+
const memoryId = String(args.memory_id || "").trim();
|
|
375
|
+
if (memoryId) {
|
|
376
|
+
return textResult(
|
|
377
|
+
"Hindsight per-memory delete is not wired in this MCP yet; pass bank to delete a test bank or use the Hindsight API/docs.",
|
|
378
|
+
true,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const targetBank = String(args.bank || "").trim();
|
|
382
|
+
if (!targetBank) return textResult("memory_id or bank is required for Hindsight", true);
|
|
383
|
+
await httpJson(
|
|
384
|
+
`${base}/v1/default/banks/${encodeURIComponent(targetBank)}`,
|
|
385
|
+
{ method: "DELETE" },
|
|
386
|
+
fetchFn,
|
|
387
|
+
);
|
|
388
|
+
return textResult({ provider: "hindsight", deleted_bank: targetBank });
|
|
389
|
+
}
|
|
390
|
+
return textResult(`unknown tool: ${name}`, true);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function callMem0(ctx, name, args, fetchFn) {
|
|
394
|
+
const base = ctx.mem0?.baseUrl;
|
|
395
|
+
if (!base) return textResult("mem0 API URL not configured", true);
|
|
396
|
+
const key = ctx.mem0?.apiKey;
|
|
397
|
+
/** @type {Record<string, string>} */
|
|
398
|
+
const headers = { "content-type": "application/json" };
|
|
399
|
+
if (key) headers["X-API-Key"] = key;
|
|
400
|
+
const userId = String(args.bank || DEFAULT_BANK).trim() || DEFAULT_BANK;
|
|
401
|
+
|
|
402
|
+
if (name === "memory_add") {
|
|
403
|
+
const content = String(args.content || "").trim();
|
|
404
|
+
if (!content) return textResult("content is required", true);
|
|
405
|
+
const json = await httpJson(
|
|
406
|
+
`${base}/memories`,
|
|
407
|
+
{
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers,
|
|
410
|
+
body: JSON.stringify({
|
|
411
|
+
messages: [{ role: "user", content }],
|
|
412
|
+
user_id: userId,
|
|
413
|
+
agent_id: "openmem-mcp",
|
|
414
|
+
infer: false,
|
|
415
|
+
}),
|
|
416
|
+
},
|
|
417
|
+
fetchFn,
|
|
418
|
+
);
|
|
419
|
+
return textResult({ provider: "mem0", user_id: userId, result: json });
|
|
420
|
+
}
|
|
421
|
+
if (name === "memory_search" || name === "memory_recall") {
|
|
422
|
+
const query = String(args.query || "").trim();
|
|
423
|
+
if (!query) return textResult("query is required", true);
|
|
424
|
+
const limit = Number(args.limit) > 0 ? Number(args.limit) : 10;
|
|
425
|
+
const json = await httpJson(
|
|
426
|
+
`${base}/search`,
|
|
427
|
+
{
|
|
428
|
+
method: "POST",
|
|
429
|
+
headers,
|
|
430
|
+
body: JSON.stringify({ query, filters: { user_id: userId }, top_k: limit }),
|
|
431
|
+
},
|
|
432
|
+
fetchFn,
|
|
433
|
+
);
|
|
434
|
+
return textResult({ provider: "mem0", user_id: userId, query, result: json });
|
|
435
|
+
}
|
|
436
|
+
if (name === "memory_forget") {
|
|
437
|
+
const memoryId = String(args.memory_id || "").trim();
|
|
438
|
+
if (memoryId) {
|
|
439
|
+
await httpJson(`${base}/memories/${encodeURIComponent(memoryId)}`, { method: "DELETE", headers }, fetchFn);
|
|
440
|
+
return textResult({ provider: "mem0", deleted: memoryId });
|
|
441
|
+
}
|
|
442
|
+
return textResult("mem0 bulk forget requires memory_id (use mem0 API or dashboard for scoped purge)", true);
|
|
443
|
+
}
|
|
444
|
+
return textResult(`unknown tool: ${name}`, true);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function callCognee(ctx, name, args, fetchFn) {
|
|
448
|
+
const base = ctx.cognee?.baseUrl;
|
|
449
|
+
if (!base) return textResult("Cognee API URL not configured", true);
|
|
450
|
+
const env = ctx.cognee?.apiKey ? { COGNEE_API_KEY: ctx.cognee.apiKey } : process.env;
|
|
451
|
+
|
|
452
|
+
if (name === "memory_add") {
|
|
453
|
+
const content = String(args.content || "").trim();
|
|
454
|
+
if (!content) return textResult("content is required", true);
|
|
455
|
+
const dataset = String(args.bank || DEFAULT_BANK).trim() || DEFAULT_BANK;
|
|
456
|
+
// Cognee 1.6.x add is multipart/form-urlencoded: JSON bodies ignore datasetName.
|
|
457
|
+
// Text goes in raw_data; file uploads use the data field (see Cognee OpenAPI).
|
|
458
|
+
const form = new FormData();
|
|
459
|
+
form.set("datasetName", dataset);
|
|
460
|
+
form.set("raw_data", content);
|
|
461
|
+
const headers = {};
|
|
462
|
+
if (ctx.cognee?.apiKey) headers["X-Api-Key"] = ctx.cognee.apiKey;
|
|
463
|
+
const json = await httpJson(`${base}/api/v1/add`, { method: "POST", headers, body: form }, fetchFn);
|
|
464
|
+
return textResult({
|
|
465
|
+
provider: "cognee",
|
|
466
|
+
dataset,
|
|
467
|
+
result: json,
|
|
468
|
+
note: "Run cognify on the dataset for graph search to see new material.",
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
if (name === "memory_search" || name === "memory_recall") {
|
|
472
|
+
const query = String(args.query || "").trim();
|
|
473
|
+
if (!query) return textResult("query is required", true);
|
|
474
|
+
const datasets = args.bank ? [String(args.bank)] : undefined;
|
|
475
|
+
const result = await cogneeSearch(base, { query, datasets }, fetchFn, env);
|
|
476
|
+
return textResult({ provider: "cognee", query, result });
|
|
477
|
+
}
|
|
478
|
+
if (name === "memory_forget") {
|
|
479
|
+
return textResult("Cognee delete is not exposed in unified MCP yet; use Cognee API or indexer.", true);
|
|
480
|
+
}
|
|
481
|
+
return textResult(`unknown tool: ${name}`, true);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* @param {Record<string, unknown>} msg
|
|
486
|
+
* @param {ReturnType<import("./context.mjs").resolveMcpContext>} ctx
|
|
487
|
+
* @param {typeof fetch} [fetchFn]
|
|
488
|
+
*/
|
|
489
|
+
export async function handleUnifiedMessage(msg, ctx, fetchFn = fetch) {
|
|
490
|
+
const { id, method, params } = msg;
|
|
491
|
+
if (method === "initialize") {
|
|
492
|
+
const protocolVersion =
|
|
493
|
+
typeof params?.protocolVersion === "string" && params.protocolVersion ? params.protocolVersion : "2024-11-05";
|
|
494
|
+
return {
|
|
495
|
+
jsonrpc: "2.0",
|
|
496
|
+
id,
|
|
497
|
+
result: {
|
|
498
|
+
protocolVersion,
|
|
499
|
+
capabilities: { tools: {} },
|
|
500
|
+
serverInfo: { name: "openmem", version: "0.6.4" },
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
if (method === "notifications/initialized" || method === "initialized") return null;
|
|
505
|
+
if (method === "tools/list") return { jsonrpc: "2.0", id, result: { tools: UNIFIED_MCP_TOOLS } };
|
|
506
|
+
if (method === "tools/call") {
|
|
507
|
+
try {
|
|
508
|
+
const result = await callUnifiedTool(ctx, String(params?.name || ""), params?.arguments || {}, fetchFn);
|
|
509
|
+
return { jsonrpc: "2.0", id, result };
|
|
510
|
+
} catch (err) {
|
|
511
|
+
return {
|
|
512
|
+
jsonrpc: "2.0",
|
|
513
|
+
id,
|
|
514
|
+
result: textResult(err instanceof Error ? err.message : String(err), true),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (method === "ping") return { jsonrpc: "2.0", id, result: {} };
|
|
519
|
+
if (id === undefined) return null;
|
|
520
|
+
return { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } };
|
|
521
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { resolveStackRoot } from "../stack.mjs";
|
|
4
|
+
import { cogneeDatasetName } from "./export/cognee-dataset-name.mjs";
|
|
5
|
+
import { resolveLlmApiKey, resolveLlmChatUrl, resolveLlmModel, synthesizeReflectAnswer } from "./llm-synthesize.mjs";
|
|
6
|
+
|
|
7
|
+
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
8
|
+
|
|
9
|
+
const DEFAULT_BANK = "openmem-mcp";
|
|
10
|
+
|
|
11
|
+
function hitsFromMem0(json) {
|
|
12
|
+
const rows = json?.results || json?.memories || [];
|
|
13
|
+
return rows.map((r) => r.memory ?? r.text ?? r.content ?? JSON.stringify(r)).join("\n---\n");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function hitsFromCognee(json) {
|
|
17
|
+
if (typeof json === "string") return json;
|
|
18
|
+
const rows = json?.search_result || json?.results || json?.data || json;
|
|
19
|
+
return typeof rows === "string" ? rows : JSON.stringify(rows, null, 2);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {ReturnType<import("./context.mjs").resolveMcpContext>} ctx
|
|
24
|
+
* @param {Record<string, unknown>} args
|
|
25
|
+
* @param {(name: string, args: Record<string, unknown>, fetchFn: typeof fetch) => Promise<{ content: { text: string }[], isError?: boolean }>} callTool
|
|
26
|
+
* @param {typeof fetch} fetchFn
|
|
27
|
+
*/
|
|
28
|
+
export async function callMemoryReflect(ctx, args, callTool, fetchFn = fetch) {
|
|
29
|
+
const query = String(args.query || "").trim();
|
|
30
|
+
if (!query) throw new Error("query is required");
|
|
31
|
+
const bank = String(args.bank || DEFAULT_BANK).trim() || DEFAULT_BANK;
|
|
32
|
+
const limit = Number(args.limit) > 0 ? Number(args.limit) : 12;
|
|
33
|
+
|
|
34
|
+
if (ctx.provider === "hindsight" && ctx.hindsightUrl) {
|
|
35
|
+
const base = ctx.hindsightUrl.replace(/\/+$/, "");
|
|
36
|
+
const enc = encodeURIComponent(bank);
|
|
37
|
+
const res = await fetchFn(`${base}/v1/default/banks/${enc}/reflect`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "content-type": "application/json" },
|
|
40
|
+
body: JSON.stringify({ query, budget: "low", max_tokens: 1024 }),
|
|
41
|
+
});
|
|
42
|
+
const text = await res.text();
|
|
43
|
+
if (!res.ok) throw new Error(`Hindsight reflect → ${res.status}: ${text.slice(0, 300)}`);
|
|
44
|
+
const result = JSON.parse(text);
|
|
45
|
+
return { provider: "hindsight", bank, query, mode: "hindsight_reflect", answer: result.text, result };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const searchOut = await callTool("memory_search", { query, bank, limit }, fetchFn);
|
|
49
|
+
const raw = searchOut.content?.[0]?.text || "";
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(raw);
|
|
53
|
+
} catch {
|
|
54
|
+
parsed = { text: raw };
|
|
55
|
+
}
|
|
56
|
+
const hits =
|
|
57
|
+
ctx.provider === "mem0"
|
|
58
|
+
? hitsFromMem0(parsed?.result ?? parsed)
|
|
59
|
+
: ctx.provider === "cognee"
|
|
60
|
+
? hitsFromCognee(parsed?.result ?? parsed)
|
|
61
|
+
: raw;
|
|
62
|
+
|
|
63
|
+
const home = process.env.HOME || "";
|
|
64
|
+
const stackRoot = resolveStackRoot({ pkgRoot: PKG_ROOT, home, env: process.env }).root;
|
|
65
|
+
const llmUrl = resolveLlmChatUrl({ home, stackRoot, env: process.env });
|
|
66
|
+
const apiKey = resolveLlmApiKey(process.env);
|
|
67
|
+
try {
|
|
68
|
+
const synthesized = await synthesizeReflectAnswer(
|
|
69
|
+
{ query, context: hits || "(no hits)", baseUrl: llmUrl, apiKey, model: resolveLlmModel() },
|
|
70
|
+
fetchFn,
|
|
71
|
+
);
|
|
72
|
+
return {
|
|
73
|
+
provider: ctx.provider,
|
|
74
|
+
bank,
|
|
75
|
+
query,
|
|
76
|
+
mode: "search_synthesize",
|
|
77
|
+
llm: llmUrl,
|
|
78
|
+
answer: synthesized,
|
|
79
|
+
};
|
|
80
|
+
} catch (err) {
|
|
81
|
+
return {
|
|
82
|
+
provider: ctx.provider,
|
|
83
|
+
bank,
|
|
84
|
+
query,
|
|
85
|
+
mode: "search_only",
|
|
86
|
+
note: `LLM synthesis unavailable (${err instanceof Error ? err.message : err}); returning raw hits`,
|
|
87
|
+
hits,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {ReturnType<import("./context.mjs").resolveMcpContext>} ctx
|
|
94
|
+
* @param {Record<string, unknown>} args
|
|
95
|
+
* @param {typeof fetch} fetchFn
|
|
96
|
+
*/
|
|
97
|
+
export async function callMemoryConsolidate(ctx, args, fetchFn = fetch) {
|
|
98
|
+
const bank = String(args.bank || DEFAULT_BANK).trim() || DEFAULT_BANK;
|
|
99
|
+
if (ctx.provider === "cognee" && ctx.cognee?.baseUrl) {
|
|
100
|
+
const base = ctx.cognee.baseUrl.replace(/\/+$/, "");
|
|
101
|
+
const dataset = cogneeDatasetName(bank);
|
|
102
|
+
const headers = { "content-type": "application/json", ...(ctx.cognee.apiKey ? { "X-Api-Key": ctx.cognee.apiKey } : {}) };
|
|
103
|
+
const res = await fetchFn(`${base}/api/v1/cognify`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers,
|
|
106
|
+
body: JSON.stringify({ datasets: [dataset], runInBackground: true }),
|
|
107
|
+
});
|
|
108
|
+
const text = await res.text();
|
|
109
|
+
if (!res.ok) throw new Error(`Cognee cognify → ${res.status}: ${text.slice(0, 300)}`);
|
|
110
|
+
return { provider: "cognee", bank, dataset, result: JSON.parse(text) };
|
|
111
|
+
}
|
|
112
|
+
if (ctx.provider === "hindsight" && ctx.hindsightUrl) {
|
|
113
|
+
return {
|
|
114
|
+
provider: "hindsight",
|
|
115
|
+
bank,
|
|
116
|
+
note: "Hindsight consolidation runs via the Hindsight API/LiteLLM memory-consolidate route during retain cycles; no separate MCP trigger yet.",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { provider: ctx.provider, bank, skipped: true, reason: "No consolidate equivalent for this provider" };
|
|
120
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const PREFIX = "notebooklm::";
|
|
2
|
+
|
|
3
|
+
export function isNotebooklmBank(bankId) {
|
|
4
|
+
return typeof bankId === "string" && bankId.startsWith(PREFIX);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function notebooklmSlug(bankId) {
|
|
8
|
+
return isNotebooklmBank(bankId) ? bankId.slice(PREFIX.length) : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function bankAllowed(bankId, allowlist) {
|
|
12
|
+
if (!isNotebooklmBank(bankId)) return false;
|
|
13
|
+
const list = Array.isArray(allowlist) && allowlist.length ? allowlist : ["*"];
|
|
14
|
+
if (list.includes("*")) return true;
|
|
15
|
+
const slug = notebooklmSlug(bankId);
|
|
16
|
+
return list.some((e) => e === bankId || e === slug);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function filterNotebooklmBanks(banks, allowlist) {
|
|
20
|
+
return (banks || []).filter((b) => bankAllowed(b.bank_id || b.id || b.name || "", allowlist));
|
|
21
|
+
}
|