docs: update memory tool list, CLI usage, and config file reading logic (#4861)
Co-authored-by: Livia Ellen <liviaellen@msn.com>
This commit is contained in:
@@ -4,6 +4,19 @@ description: "Release notes for the OpenClaw plugin and agent harness."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Update label="2026-04-20" description="v1.0.7">
|
||||
|
||||
**New Features:**
|
||||
- **Chat-Based Setup:** Added chat-based Platform setup flow — users can now configure the plugin conversationally instead of editing config files manually
|
||||
- **Installation Docs Rewrite:** Rewrote README and integration docs with chat-first setup, numbered manual steps.
|
||||
|
||||
**Improvements:**
|
||||
- **SDK Upgrade:** Bumped `mem0ai` dependency to 3.0.1 for V3 API compatibility
|
||||
- **Config Cleanup:** Dropped deprecated `orgId`, `projectId`, `enableGraph` config options; updated CLI prompts ([#4734](https://github.com/mem0ai/mem0/pull/4734), [#4764](https://github.com/mem0ai/mem0/pull/4764))
|
||||
- **Noise Filtering:** Expanded noise patterns in memory add tool; handle leading text in JSON extraction
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-11" description="v1.0.6">
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
+215
-42
@@ -14,16 +14,37 @@ Add long-term memory to [OpenClaw](https://github.com/openclaw/openclaw) agents
|
||||
The plugin provides:
|
||||
1. **Auto-Recall** — Before the agent responds, memories matching the current message are injected into context
|
||||
2. **Auto-Capture** — After the agent responds, the exchange is sent to Mem0 which decides what's worth keeping
|
||||
3. **Agent Tools** — Five tools for explicit memory operations during conversations
|
||||
3. **Agent Tools** — Eight tools for explicit memory operations during conversations
|
||||
|
||||
Both auto-recall and auto-capture run silently with no manual configuration required.
|
||||
|
||||
## Installation
|
||||
## Requirements
|
||||
|
||||
Check your OpenClaw version:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @mem0/openclaw-mem0
|
||||
openclaw --version
|
||||
# OpenClaw 2026.4.15 (041266a)
|
||||
```
|
||||
|
||||
| OpenClaw Version | Plugin Support |
|
||||
|------------------|----------------|
|
||||
| `>= 2026.4.15` | Fully supported |
|
||||
|
||||
## Installation
|
||||
|
||||
The fastest way is to install directly from your OpenClaw chat, no CLI or config editing needed.
|
||||
|
||||
**Copy and paste this into your OpenClaw chat**; Telegram, WhatsApp, default chat, or any channel where your agent lives:
|
||||
|
||||
```
|
||||
Setup Mem0 from mem0.ai/claw-setup
|
||||
```
|
||||
|
||||
OpenClaw installs the plugin, prompts you for your email, and connects your Mem0 account with OTP verification. See [Chat Setup](#option-1-chat-setup-recommended) below for the full walkthrough.
|
||||
|
||||
If you prefer the OpenClaw CLI, or are setting up self-hosted / open-source mode, see [Manual Config](#option-2-manual-config) and [Open-Source Mode](#open-source-mode-self-hosted) below.
|
||||
|
||||
## Setup and Configuration
|
||||
|
||||
### Understanding `userId`
|
||||
@@ -36,37 +57,113 @@ Pick any stable, unique identifier for the user. Common choices:
|
||||
- A UUID (e.g. `"550e8400-e29b-41d4-a716-446655440000"`)
|
||||
- A simple username (e.g. `"alice"`)
|
||||
|
||||
All memories are scoped to this `userId` — different values create separate memory namespaces. If you don't set it, it defaults to `"default"`, which means all users share the same memory space.
|
||||
All memories are scoped to this `userId` — different values create separate memory namespaces. If you don't set it, it defaults to your OS username.
|
||||
|
||||
<Tip>In a multi-user application, set `userId` dynamically per user (e.g. from your auth system) rather than hardcoding a single value.</Tip>
|
||||
|
||||
### Platform Mode (Mem0 Cloud)
|
||||
|
||||
<Note>Get your API key from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>.</Note>
|
||||
There are two ways to set up `@mem0/openclaw-mem0` on the Mem0 platform:
|
||||
|
||||
Add to your `openclaw.json`:
|
||||
- **Chat setup (recommended)** — run the setup inside any OpenClaw chat. No config editing, no API key handling.
|
||||
- **Manual config** — edit `openclaw.json` directly.
|
||||
|
||||
```json5
|
||||
// plugins.entries
|
||||
"openclaw-mem0": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"apiKey": "${MEM0_API_KEY}",
|
||||
"userId": "alice" // any unique identifier you choose for this user
|
||||
}
|
||||
}
|
||||
```
|
||||
#### Option 1: Chat Setup (Recommended)
|
||||
|
||||
You no longer need manual config editing to get started. Everything happens inside the OpenClaw chat itself.
|
||||
|
||||
<Steps>
|
||||
<Step title="Send the setup command to your OpenClaw agent">
|
||||
Open any OpenClaw channel — Telegram, WhatsApp, your default chat, wherever your agent lives. Paste and send this command:
|
||||
|
||||
```
|
||||
Setup Mem0 from mem0.ai/claw-setup
|
||||
```
|
||||
|
||||
OpenClaw responds with a Mem0 setup card and immediately asks:
|
||||
|
||||
> "What's your email address? I'll send you a verification code to connect your Mem0 account."
|
||||
</Step>
|
||||
|
||||
<Step title="Enter your email">
|
||||
Type your email address and send it. Mem0 sends back:
|
||||
|
||||
> "Check your email for a 6-digit code and paste it here."
|
||||
</Step>
|
||||
|
||||
<Step title="Paste the OTP">
|
||||
Copy the 6-digit code from your email inbox and paste it into the chat.
|
||||
|
||||
You'll see the confirmation:
|
||||
|
||||
> "Connected to Mem0."
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
That's it. No API key, no config file editing, no environment variables. The plugin is now active and auto-capture and auto-recall are running on every turn.
|
||||
|
||||
<Note>The chat flow uses the same underlying config as manual setup — it writes `apiKey` and `userId` into `openclaw.json` for you. You can still open the file to inspect or override values afterward.</Note>
|
||||
|
||||
#### Option 2: Manual Config
|
||||
|
||||
<Steps>
|
||||
<Step title="Install the plugin via the OpenClaw CLI">
|
||||
```bash
|
||||
openclaw plugins install @mem0/openclaw-mem0
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Get your API key">
|
||||
Get your API key from <a href="https://app.mem0.ai?utm_source=mem0-docs" rel="nofollow">app.mem0.ai</a>.
|
||||
</Step>
|
||||
|
||||
<Step title="Select the plugin as your memory backend in `openclaw.json`">
|
||||
Add the full config to your `openclaw.json`:
|
||||
|
||||
```json5
|
||||
{
|
||||
"plugins": {
|
||||
"slots": {
|
||||
"memory": "openclaw-mem0"
|
||||
},
|
||||
"entries": {
|
||||
"openclaw-mem0": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"apiKey": "${MEM0_API_KEY}",
|
||||
"userId": "alice" // any unique identifier you choose for this user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Warning>
|
||||
OpenClaw treats memory plugins as an exclusive slot. Installing the plugin alone does **not** activate it — you must also set `plugins.slots.memory` as shown above.
|
||||
</Warning>
|
||||
|
||||
### Open-Source Mode (Self-hosted)
|
||||
|
||||
No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings/LLM.
|
||||
|
||||
```json5
|
||||
"openclaw-mem0": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"mode": "open-source",
|
||||
"userId": "alice" // any unique identifier you choose for this user
|
||||
{
|
||||
"plugins": {
|
||||
"slots": {
|
||||
"memory": "openclaw-mem0"
|
||||
},
|
||||
"entries": {
|
||||
"openclaw-mem0": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"mode": "open-source",
|
||||
"userId": "alice" // any unique identifier you choose for this user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -74,13 +171,25 @@ No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings/LLM.
|
||||
Sensible defaults work out of the box. To customize the embedder, vector store, or LLM:
|
||||
|
||||
```json5
|
||||
"config": {
|
||||
"mode": "open-source",
|
||||
"userId": "your-user-id",
|
||||
"oss": {
|
||||
"embedder": { "provider": "openai", "config": { "model": "text-embedding-3-small" } },
|
||||
"vectorStore": { "provider": "qdrant", "config": { "host": "localhost", "port": 6333 } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-4o" } }
|
||||
{
|
||||
"plugins": {
|
||||
"slots": {
|
||||
"memory": "openclaw-mem0"
|
||||
},
|
||||
"entries": {
|
||||
"openclaw-mem0": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"mode": "open-source",
|
||||
"userId": "your-user-id",
|
||||
"oss": {
|
||||
"embedder": { "provider": "openai", "config": { "model": "text-embedding-3-small" } },
|
||||
"vectorStore": { "provider": "qdrant", "config": { "host": "localhost", "port": 6333 } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-4o" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -93,23 +202,26 @@ Memories are organized into two scopes:
|
||||
|
||||
- **Session (short-term)** — Auto-capture stores memories scoped to the current session via Mem0's `run_id` / `runId` parameter. These are contextual to the ongoing conversation.
|
||||
|
||||
- **User (long-term)** — The agent can explicitly store long-term memories using the `memory_store` tool (with `longTerm: true`, the default). These persist across all sessions for the user.
|
||||
- **User (long-term)** — The agent can explicitly store long-term memories using the `memory_add` tool (with `longTerm: true`, the default). These persist across all sessions for the user.
|
||||
|
||||
During **auto-recall**, the plugin searches both scopes and presents them separately — long-term memories first, then session memories — so the agent has full context.
|
||||
|
||||
## Agent Tools
|
||||
|
||||
The agent gets five tools it can call during conversations:
|
||||
The agent gets eight tools it can call during conversations:
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `memory_search` | Search memories by natural language |
|
||||
| `memory_list` | List all stored memories for a user |
|
||||
| `memory_store` | Explicitly save a fact |
|
||||
| `memory_get` | Retrieve a memory by ID |
|
||||
| `memory_forget` | Delete by ID or by query |
|
||||
| `memory_search` | Search memories by natural language query. Supports `scope`, `categories`, `filters`. |
|
||||
| `memory_add` | Store facts. Accepts `text` or `facts` array, `category`, `importance`, `metadata`. |
|
||||
| `memory_get` | Retrieve a single memory by ID |
|
||||
| `memory_list` | List all memories. Filter by `userId`, `agentId`, `scope`. |
|
||||
| `memory_update` | Update a memory's text in place. Preserves history. |
|
||||
| `memory_delete` | Delete by `memoryId`, `query` (search-and-delete), or `all: true`. |
|
||||
| `memory_event_list` | List recent background processing events (platform mode only). |
|
||||
| `memory_event_status` | Get status of a specific event by ID (platform mode only). |
|
||||
|
||||
The `memory_search` and `memory_list` tools accept a `scope` parameter (`"session"`, `"long-term"`, or `"all"`) to control which memories are queried. The `memory_store` tool accepts a `longTerm` boolean (default: `true`) to choose where to store.
|
||||
The `memory_search` and `memory_list` tools accept a `scope` parameter (`"session"`, `"long-term"`, or `"all"`) to control which memories are queried.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
@@ -123,8 +235,9 @@ openclaw mem0 search "what languages does the user know" --scope long-term
|
||||
# Search only session/short-term memories
|
||||
openclaw mem0 search "what languages does the user know" --scope session
|
||||
|
||||
# View stats
|
||||
openclaw mem0 stats
|
||||
# List all memories
|
||||
openclaw mem0 list
|
||||
openclaw mem0 list --user-id alice --top-k 20
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
@@ -134,7 +247,7 @@ openclaw mem0 stats
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| `mode` | `"platform"` \| `"open-source"` | `"platform"` | Which backend to use |
|
||||
| `userId` | `string` | `"default"` | Scope memories per user |
|
||||
| `userId` | `string` | OS username | Scope memories per user |
|
||||
| `autoRecall` | `boolean` | `true` | Inject memories before each turn |
|
||||
| `autoCapture` | `boolean` | `true` | Store facts after each turn |
|
||||
| `topK` | `number` | `5` | Max memories per recall |
|
||||
@@ -145,8 +258,6 @@ openclaw mem0 stats
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| `apiKey` | `string` | — | **Required.** Mem0 API key (supports `${MEM0_API_KEY}`) |
|
||||
| `orgId` | `string` | — | Organization ID |
|
||||
| `projectId` | `string` | — | Project ID |
|
||||
| `customInstructions` | `string` | *(built-in)* | Extraction rules — what to store, how to format |
|
||||
| `customCategories` | `object` | *(12 defaults)* | Category name → description map for tagging |
|
||||
|
||||
@@ -162,15 +273,77 @@ openclaw mem0 stats
|
||||
| `oss.llm.provider` | `string` | `"openai"` | LLM provider (`"openai"`, `"anthropic"`, `"ollama"`, etc.) |
|
||||
| `oss.llm.config` | `object` | — | Provider config: `apiKey`, `model`, `baseURL`, `temperature` |
|
||||
| `oss.historyDbPath` | `string` | — | SQLite path for memory edit history |
|
||||
| `oss.disableHistory` | `boolean` | `false` | Disable memory edit history tracking |
|
||||
|
||||
Everything inside `oss` is optional — defaults use OpenAI embeddings (`text-embedding-3-small`), in-memory vector store, and OpenAI LLM.
|
||||
|
||||
## Plugin Management
|
||||
|
||||
### Updating the Plugin
|
||||
|
||||
```bash
|
||||
openclaw plugins update @mem0/openclaw-mem0
|
||||
```
|
||||
|
||||
<Note>Use the npm package name (`@mem0/openclaw-mem0`) for plugin management commands, not the plugin ID (`openclaw-mem0`).</Note>
|
||||
|
||||
### Checking Plugin Status
|
||||
|
||||
```bash
|
||||
openclaw plugins list
|
||||
openclaw plugins inspect openclaw-mem0
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "plugins.allow excludes mem0" Error
|
||||
|
||||
If you see an error like:
|
||||
|
||||
```
|
||||
[openclaw] Failed to start CLI: Error: The `openclaw mem0` command is unavailable
|
||||
because `plugins.allow` excludes "mem0". Add "mem0" to `plugins.allow` if you want
|
||||
that bundled plugin CLI surface.
|
||||
```
|
||||
|
||||
Add `mem0` to your `plugins.allow` list in `openclaw.json`:
|
||||
|
||||
```json5
|
||||
{
|
||||
"plugins": {
|
||||
"allow": ["mem0"],
|
||||
"slots": {
|
||||
"memory": "openclaw-mem0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Plugin Not Activating
|
||||
|
||||
If the plugin installs but doesn't work:
|
||||
|
||||
1. Verify `plugins.slots.memory` is set to `"openclaw-mem0"` (not the npm package name)
|
||||
2. Check `openclaw plugins list --enabled` to confirm the plugin is loaded
|
||||
3. Run `openclaw mem0 status` to verify configuration
|
||||
|
||||
### Plugin Update Not Working
|
||||
|
||||
If `openclaw plugins update` fails:
|
||||
|
||||
1. Use the full npm package name: `openclaw plugins update @mem0/openclaw-mem0`
|
||||
2. If that fails, uninstall and reinstall:
|
||||
```bash
|
||||
openclaw plugins uninstall openclaw-mem0
|
||||
openclaw plugins install @mem0/openclaw-mem0
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **Zero Configuration** — Auto-recall and auto-capture work out of the box with no prompting required
|
||||
2. **Dual Memory Scopes** — Session-scoped short-term and user-scoped long-term memories
|
||||
3. **Flexible Backend** — Use Mem0 Cloud for managed service or self-host with open-source mode
|
||||
4. **Rich Tool Suite** — Five agent tools for explicit memory operations when needed
|
||||
4. **Rich Tool Suite** — Eight agent tools for explicit memory operations when needed
|
||||
|
||||
## Conclusion
|
||||
|
||||
|
||||
@@ -883,38 +883,160 @@ export function removeCodeBlocks(text: string): string {
|
||||
/**
|
||||
* Extracts a JSON object from text that may be wrapped in explanation text.
|
||||
*
|
||||
* Some LLMs (especially local models like Ollama/LM Studio) return JSON
|
||||
* wrapped in conversational text without code fences, e.g.:
|
||||
* Some LLMs (especially local models like Ollama/LM Studio, or OpenRouter)
|
||||
* return JSON wrapped in conversational text without code fences, e.g.:
|
||||
*
|
||||
* "Here are the facts I extracted:\n{\"facts\": [\"fact1\"]}\nI hope this helps!"
|
||||
*
|
||||
* This function first tries `removeCodeBlocks` for code-fence-wrapped JSON,
|
||||
* then falls back to locating the first `{` and last `}` to extract the
|
||||
* outermost JSON object.
|
||||
* This function:
|
||||
* 1. Strips known noise tokens from OpenRouter and other providers
|
||||
* 2. Removes code fences and <think> blocks
|
||||
* 3. Tries to find a valid JSON object by testing each `{` as a starting point
|
||||
* 4. Falls back to first/last brace matching if validation isn't possible
|
||||
*
|
||||
* @param text - The raw LLM response text
|
||||
* @returns The extracted JSON string, or the original text if no JSON object
|
||||
* boundaries are found
|
||||
*/
|
||||
export function extractJson(text: string): string {
|
||||
// Step 1: Strip code fences if present
|
||||
const cleaned = removeCodeBlocks(text);
|
||||
// Step 1: Strip known noise tokens from OpenRouter/local models
|
||||
let cleaned = text
|
||||
.replace(/<\|end_of_text\|>/g, "")
|
||||
.replace(/<\|eot_id\|>/g, "")
|
||||
.replace(/<\|im_end\|>/g, "")
|
||||
.replace(/<\|im_start\|>/g, "")
|
||||
.replace(/<\|endoftext\|>/g, "");
|
||||
|
||||
// Step 2: Strip code fences and <think> blocks
|
||||
cleaned = removeCodeBlocks(cleaned);
|
||||
const trimmed = cleaned.trim();
|
||||
|
||||
// Step 2: Try to locate a JSON object by first `{` and last `}` boundaries
|
||||
if (!trimmed) return "";
|
||||
|
||||
// Step 3: Try to find valid JSON object by testing each `{` as potential start
|
||||
// This handles cases like "Here's the {formatted} output: {...actual json...}"
|
||||
const braceIndices: number[] = [];
|
||||
for (let i = 0; i < trimmed.length; i++) {
|
||||
if (trimmed[i] === "{") braceIndices.push(i);
|
||||
}
|
||||
|
||||
for (const start of braceIndices) {
|
||||
// Find the matching closing brace by tracking depth
|
||||
let depth = 0;
|
||||
let inString = false;
|
||||
let escapeNext = false;
|
||||
|
||||
for (let i = start; i < trimmed.length; i++) {
|
||||
const char = trimmed[i];
|
||||
|
||||
if (escapeNext) {
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "\\") {
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"' && !escapeNext) {
|
||||
inString = !inString;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inString) continue;
|
||||
|
||||
if (char === "{") depth++;
|
||||
else if (char === "}") {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
const candidate = trimmed.substring(start, i + 1);
|
||||
try {
|
||||
JSON.parse(candidate);
|
||||
return candidate; // Valid JSON found
|
||||
} catch {
|
||||
// Not valid JSON, try next starting brace
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Fallback - try first/last brace (original behavior for edge cases)
|
||||
// Only use this if it produces valid JSON
|
||||
const firstBrace = trimmed.indexOf("{");
|
||||
const lastBrace = trimmed.lastIndexOf("}");
|
||||
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
||||
return trimmed.substring(firstBrace, lastBrace + 1);
|
||||
const candidate = trimmed.substring(firstBrace, lastBrace + 1);
|
||||
try {
|
||||
JSON.parse(candidate);
|
||||
return candidate;
|
||||
} catch {
|
||||
// Not valid JSON, continue to array extraction
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Try to locate a JSON array by first `[` and last `]` boundaries
|
||||
// Step 5: Try to locate a JSON array by testing each `[` as potential start
|
||||
const bracketIndices: number[] = [];
|
||||
for (let i = 0; i < trimmed.length; i++) {
|
||||
if (trimmed[i] === "[") bracketIndices.push(i);
|
||||
}
|
||||
|
||||
for (const start of bracketIndices) {
|
||||
let depth = 0;
|
||||
let inString = false;
|
||||
let escapeNext = false;
|
||||
|
||||
for (let i = start; i < trimmed.length; i++) {
|
||||
const char = trimmed[i];
|
||||
|
||||
if (escapeNext) {
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "\\") {
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"' && !escapeNext) {
|
||||
inString = !inString;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inString) continue;
|
||||
|
||||
if (char === "[") depth++;
|
||||
else if (char === "]") {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
const candidate = trimmed.substring(start, i + 1);
|
||||
try {
|
||||
JSON.parse(candidate);
|
||||
return candidate;
|
||||
} catch {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for arrays - validate before returning
|
||||
const firstBracket = trimmed.indexOf("[");
|
||||
const lastBracket = trimmed.lastIndexOf("]");
|
||||
if (firstBracket !== -1 && lastBracket > firstBracket) {
|
||||
return trimmed.substring(firstBracket, lastBracket + 1);
|
||||
const candidate = trimmed.substring(firstBracket, lastBracket + 1);
|
||||
try {
|
||||
JSON.parse(candidate);
|
||||
return candidate;
|
||||
} catch {
|
||||
// Not valid JSON
|
||||
}
|
||||
}
|
||||
|
||||
// No JSON boundaries found — return as-is and let the caller handle the error
|
||||
// No valid JSON found — return as-is and let the caller handle the error
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
@@ -148,4 +148,85 @@ That's all I found.`;
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({ facts: ["test"] });
|
||||
});
|
||||
|
||||
it("strips <|end_of_text|> tokens from OpenRouter responses", () => {
|
||||
const input = '{"facts": ["test"]}<|end_of_text|>';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({ facts: ["test"] });
|
||||
});
|
||||
|
||||
it("strips <|eot_id|> tokens from OpenRouter responses", () => {
|
||||
const input = '{"facts": ["hello"]}<|eot_id|>';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({ facts: ["hello"] });
|
||||
});
|
||||
|
||||
it("strips <|im_end|> tokens from ChatML responses", () => {
|
||||
const input = '{"memory": [{"text": "test"}]}<|im_end|>';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({ memory: [{ text: "test" }] });
|
||||
});
|
||||
|
||||
it("strips multiple noise tokens", () => {
|
||||
const input =
|
||||
'<|im_start|>assistant\n{"facts": ["data"]}<|im_end|><|end_of_text|>';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({ facts: ["data"] });
|
||||
});
|
||||
|
||||
// Issue #4737: Leading text with braces that aren't JSON
|
||||
it("handles leading text containing braces before actual JSON", () => {
|
||||
const input = 'Here\'s the {formatted} output: {"facts": ["real data"]}';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({ facts: ["real data"] });
|
||||
});
|
||||
|
||||
it("handles multiple fake braces in leading text", () => {
|
||||
const input =
|
||||
"I'll format this {nicely} with {proper} structure:\n" +
|
||||
'{"memory": [{"id": "1", "text": "actual memory"}]}';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({
|
||||
memory: [{ id: "1", text: "actual memory" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("handles incomplete JSON-like structures in leading text", () => {
|
||||
const input =
|
||||
"Based on {user preferences} I found:\n" +
|
||||
'{"facts": ["User likes TypeScript"]}';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({ facts: ["User likes TypeScript"] });
|
||||
});
|
||||
|
||||
it("validates JSON and skips malformed candidates", () => {
|
||||
const input = 'The result is {broken and {"facts": ["valid"]} is here';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({ facts: ["valid"] });
|
||||
});
|
||||
|
||||
it("handles deeply nested valid JSON after invalid starts", () => {
|
||||
const input =
|
||||
"Here {is some {context}} for you:\n" +
|
||||
'{"memory": [{"nested": {"deep": "value"}}]}';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({
|
||||
memory: [{ nested: { deep: "value" } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("handles JSON with escaped quotes correctly", () => {
|
||||
const input =
|
||||
'Output: {"facts": ["User said \\"hello\\"", "Has a \\"test\\" project"]}';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual({
|
||||
facts: ['User said "hello"', 'Has a "test" project'],
|
||||
});
|
||||
});
|
||||
|
||||
it("handles arrays with leading brace-like text", () => {
|
||||
const input = 'Here\'s {some context}. The array is: ["fact1", "fact2"]';
|
||||
const result = extractJson(input);
|
||||
expect(JSON.parse(result)).toEqual(["fact1", "fact2"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
*.db
|
||||
.claude/
|
||||
.claude-*/
|
||||
+97
-21
@@ -4,31 +4,98 @@ Long-term memory for [OpenClaw](https://github.com/openclaw/openclaw) agents, po
|
||||
|
||||
Your agent forgets everything between sessions. This plugin fixes that — it watches conversations, extracts what matters, and brings it back when relevant. Automatically.
|
||||
|
||||
## Quick Start
|
||||
## Requirements
|
||||
|
||||
Check your OpenClaw version:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @mem0/openclaw-mem0
|
||||
openclaw --version
|
||||
# OpenClaw 2026.4.15 (041266a)
|
||||
```
|
||||
|
||||
| OpenClaw Version | Plugin Support |
|
||||
|------------------|----------------|
|
||||
| `>= 2026.4.15` | Fully supported |
|
||||
|
||||
## Quick Start
|
||||
|
||||
The fastest way is to install directly from your OpenClaw chat — no CLI or config editing needed.
|
||||
|
||||
Copy and paste this into your OpenClaw chat (Telegram, WhatsApp, default chat, or any channel where your agent lives):
|
||||
|
||||
```
|
||||
Setup Mem0 from mem0.ai/claw-setup
|
||||
```
|
||||
|
||||
OpenClaw installs the plugin, prompts you for your email, and connects your Mem0 account with OTP verification. See [Chat Setup](#chat-setup-recommended) below for the full walkthrough.
|
||||
|
||||
If you prefer the OpenClaw CLI, or are setting up self-hosted / open-source mode, see [Manual Config](#manual-config) and [Open-Source (Self-hosted)](#open-source-self-hosted) below.
|
||||
|
||||
### Platform (Mem0 Cloud)
|
||||
|
||||
Get an API key from [app.mem0.ai](https://app.mem0.ai/dashboard/api-keys):
|
||||
There are two ways to set up `@mem0/openclaw-mem0` on the Mem0 platform:
|
||||
|
||||
```bash
|
||||
openclaw mem0 init --api-key <your-key> --user-id <your-user-id>
|
||||
```
|
||||
- **Chat setup (recommended)** — run the setup inside any OpenClaw chat. No config editing, no API key handling.
|
||||
- **Manual config** — edit `openclaw.json` directly.
|
||||
|
||||
Or configure manually in `openclaw.json`:
|
||||
#### Chat Setup (Recommended)
|
||||
|
||||
```json5
|
||||
"openclaw-mem0": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"apiKey": "${MEM0_API_KEY}",
|
||||
"userId": "alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
You no longer need manual config editing to get started. Everything happens inside the OpenClaw chat itself.
|
||||
|
||||
1. **Send the setup command to your OpenClaw agent.** Open any OpenClaw channel and paste:
|
||||
|
||||
```
|
||||
Setup Mem0 from mem0.ai/claw-setup
|
||||
```
|
||||
|
||||
OpenClaw responds with a Mem0 setup card and asks: *"What's your email address? I'll send you a verification code to connect your Mem0 account."*
|
||||
|
||||
2. **Enter your email.** Type your email address and send it. Mem0 replies: *"Check your email for a 6-digit code and paste it here."*
|
||||
|
||||
3. **Paste the OTP.** Copy the 6-digit code from your email inbox and paste it into the chat. You'll see: *"Connected to Mem0."*
|
||||
|
||||
That's it. No API key, no config file editing, no environment variables. The plugin is now active and auto-capture and auto-recall are running on every turn.
|
||||
|
||||
> The chat flow uses the same underlying config as manual setup — it writes `apiKey` and `userId` into `openclaw.json` for you. You can still open the file to inspect or override values afterward.
|
||||
|
||||
#### Manual Config
|
||||
|
||||
1. **Install the plugin via the OpenClaw CLI:**
|
||||
|
||||
```bash
|
||||
openclaw plugins install @mem0/openclaw-mem0
|
||||
```
|
||||
|
||||
2. **Get your API key** from [app.mem0.ai](https://app.mem0.ai/dashboard/api-keys).
|
||||
|
||||
3. **Select the plugin as your memory backend in `openclaw.json`.** Either initialize via the CLI:
|
||||
|
||||
```bash
|
||||
openclaw mem0 init --api-key <your-key> --user-id <your-user-id>
|
||||
```
|
||||
|
||||
Or add the full config to your `openclaw.json`:
|
||||
|
||||
```json5
|
||||
{
|
||||
"plugins": {
|
||||
"slots": {
|
||||
"memory": "openclaw-mem0"
|
||||
},
|
||||
"entries": {
|
||||
"openclaw-mem0": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"apiKey": "${MEM0_API_KEY}",
|
||||
"userId": "alice"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** OpenClaw memory plugins load through an exclusive slot, so install alone does not activate the plugin. You must set `plugins.slots.memory` as shown above.
|
||||
|
||||
### Open-Source (Self-hosted)
|
||||
|
||||
@@ -37,11 +104,20 @@ No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings and LLM. Ve
|
||||
Defaults: `text-embedding-3-small` for embeddings, `gpt-5.4` for fact extraction.
|
||||
|
||||
```json5
|
||||
"openclaw-mem0": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"mode": "open-source",
|
||||
"userId": "alice"
|
||||
{
|
||||
"plugins": {
|
||||
"slots": {
|
||||
"memory": "openclaw-mem0"
|
||||
},
|
||||
"entries": {
|
||||
"openclaw-mem0": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"mode": "open-source",
|
||||
"userId": "alice"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -10,9 +10,7 @@ export interface AddOptions {
|
||||
appId?: string;
|
||||
runId?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
immutable?: boolean;
|
||||
infer?: boolean;
|
||||
expires?: string;
|
||||
categories?: string[];
|
||||
}
|
||||
|
||||
@@ -24,7 +22,6 @@ export interface SearchOptions {
|
||||
topK?: number;
|
||||
threshold?: number;
|
||||
rerank?: boolean;
|
||||
keyword?: boolean;
|
||||
filters?: Record<string, unknown>;
|
||||
fields?: string[];
|
||||
}
|
||||
|
||||
@@ -107,9 +107,7 @@ export class PlatformBackend implements Backend {
|
||||
if (opts.appId) payload.app_id = opts.appId;
|
||||
if (opts.runId) payload.run_id = opts.runId;
|
||||
if (opts.metadata) payload.metadata = opts.metadata;
|
||||
if (opts.immutable) payload.immutable = true;
|
||||
if (opts.infer === false) payload.infer = false;
|
||||
if (opts.expires) payload.expiration_date = opts.expires;
|
||||
if (opts.categories) payload.categories = opts.categories;
|
||||
|
||||
return (await this._request("POST", "/v1/memories/", {
|
||||
@@ -168,7 +166,6 @@ export class PlatformBackend implements Backend {
|
||||
});
|
||||
if (apiFilters) payload.filters = apiFilters;
|
||||
if (opts.rerank) payload.rerank = true;
|
||||
if (opts.keyword) payload.keyword_search = true;
|
||||
if (opts.fields) payload.fields = opts.fields;
|
||||
|
||||
const result = (await this._request("POST", "/v2/memories/search/", {
|
||||
|
||||
+125
-22
@@ -19,6 +19,7 @@ export const OPENCLAW_CONFIG_FILE = join(OPENCLAW_CONFIG_DIR, "openclaw.json");
|
||||
export const DEFAULT_BASE_URL = "https://api.mem0.ai";
|
||||
|
||||
const PLUGIN_ID = "openclaw-mem0";
|
||||
const NPM_PACKAGE = "@mem0/openclaw-mem0";
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
@@ -41,16 +42,38 @@ export interface PluginAuthConfig {
|
||||
// OpenClaw config read/write
|
||||
// ============================================================================
|
||||
|
||||
/** Read the full ~/.openclaw/openclaw.json */
|
||||
/**
|
||||
* Read the full ~/.openclaw/openclaw.json.
|
||||
*
|
||||
* Returns {} only when the file doesn't exist (first-time setup).
|
||||
* Throws on parse errors to prevent writes from destroying existing config.
|
||||
*/
|
||||
function readFullConfig(): Record<string, unknown> {
|
||||
if (exists(OPENCLAW_CONFIG_FILE)) {
|
||||
try {
|
||||
return JSON.parse(readText(OPENCLAW_CONFIG_FILE));
|
||||
} catch {
|
||||
/* ignore parse errors */
|
||||
}
|
||||
if (!exists(OPENCLAW_CONFIG_FILE)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const text = readText(OPENCLAW_CONFIG_FILE);
|
||||
|
||||
// Handle empty or whitespace-only files as first-time setup
|
||||
if (!text.trim()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
throw new Error("Config is not a JSON object");
|
||||
}
|
||||
return parsed;
|
||||
} catch (err) {
|
||||
// Fail closed: throw so writes don't proceed with empty config
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(
|
||||
`[openclaw-mem0] Failed to parse ${OPENCLAW_CONFIG_FILE}: ${msg}\n` +
|
||||
`Fix the JSON syntax error manually before running config commands.`,
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/** Write the full ~/.openclaw/openclaw.json (preserves all non-plugin config) */
|
||||
@@ -87,15 +110,7 @@ export function readPluginAuth(): PluginAuthConfig {
|
||||
export function writePluginAuth(auth: PluginAuthConfig): void {
|
||||
const full = readFullConfig() as any;
|
||||
|
||||
// Ensure nested structure exists
|
||||
if (!full.plugins) full.plugins = {};
|
||||
if (!full.plugins.entries) full.plugins.entries = {};
|
||||
if (!full.plugins.entries[PLUGIN_ID]) {
|
||||
full.plugins.entries[PLUGIN_ID] = { enabled: true, config: {} };
|
||||
}
|
||||
if (!full.plugins.entries[PLUGIN_ID].config) {
|
||||
full.plugins.entries[PLUGIN_ID].config = {};
|
||||
}
|
||||
ensurePluginStructure(full);
|
||||
|
||||
const cfg = full.plugins.entries[PLUGIN_ID].config;
|
||||
|
||||
@@ -107,12 +122,81 @@ export function writePluginAuth(auth: PluginAuthConfig): void {
|
||||
writeFullConfig(full);
|
||||
}
|
||||
|
||||
export function writePluginConfigField(
|
||||
path: string[],
|
||||
value: unknown,
|
||||
): void {
|
||||
const full = readFullConfig() as any;
|
||||
/**
|
||||
* Ensure the plugin has a valid install record and is in plugins.allow.
|
||||
*
|
||||
* OpenClaw's `plugins update` command requires a `plugins.installs.<id>`
|
||||
* record with `source: "npm"` and `spec` to know how to update. Without
|
||||
* this, `openclaw plugins update` prints "No install record" and skips.
|
||||
*
|
||||
* Similarly, if `plugins.allow` exists as an array, the plugin ID must
|
||||
* be in it or OpenClaw treats the plugin as untrusted.
|
||||
*
|
||||
* This is safe to call multiple times — it only writes missing fields.
|
||||
*/
|
||||
export function ensureInstallRecord(): void {
|
||||
try {
|
||||
const full = readFullConfig() as any;
|
||||
|
||||
const entry = full?.plugins?.entries?.[PLUGIN_ID];
|
||||
const record = full?.plugins?.installs?.[PLUGIN_ID];
|
||||
const allow = full?.plugins?.allow;
|
||||
if (
|
||||
entry?.enabled === true &&
|
||||
record?.source &&
|
||||
record?.spec &&
|
||||
Array.isArray(allow) &&
|
||||
allow.includes(PLUGIN_ID)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensurePluginStructure(full);
|
||||
|
||||
let changed = false;
|
||||
|
||||
// Ensure install record exists for `openclaw plugins update` support
|
||||
if (!full.plugins.installs) full.plugins.installs = {};
|
||||
if (!full.plugins.installs[PLUGIN_ID]) {
|
||||
full.plugins.installs[PLUGIN_ID] = {
|
||||
source: "npm",
|
||||
spec: `${NPM_PACKAGE}@latest`,
|
||||
resolvedName: NPM_PACKAGE,
|
||||
installedAt: new Date().toISOString(),
|
||||
};
|
||||
changed = true;
|
||||
} else {
|
||||
const record = full.plugins.installs[PLUGIN_ID];
|
||||
if (!record.source) {
|
||||
record.source = "npm";
|
||||
changed = true;
|
||||
}
|
||||
if (!record.spec) {
|
||||
record.spec = `${NPM_PACKAGE}@latest`;
|
||||
changed = true;
|
||||
}
|
||||
if (!record.resolvedName) {
|
||||
record.resolvedName = NPM_PACKAGE;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(full.plugins.allow)) {
|
||||
full.plugins.allow = [PLUGIN_ID];
|
||||
changed = true;
|
||||
} else if (!full.plugins.allow.includes(PLUGIN_ID)) {
|
||||
full.plugins.allow.push(PLUGIN_ID);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) writeFullConfig(full);
|
||||
} catch {
|
||||
// Best-effort — don't break plugin loading if config is unreadable
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensure the nested plugin entry structure exists in the config object. */
|
||||
function ensurePluginStructure(full: any): void {
|
||||
if (!full.plugins) full.plugins = {};
|
||||
if (!full.plugins.entries) full.plugins.entries = {};
|
||||
if (!full.plugins.entries[PLUGIN_ID]) {
|
||||
@@ -121,6 +205,15 @@ export function writePluginConfigField(
|
||||
if (!full.plugins.entries[PLUGIN_ID].config) {
|
||||
full.plugins.entries[PLUGIN_ID].config = {};
|
||||
}
|
||||
}
|
||||
|
||||
export function writePluginConfigField(
|
||||
path: string[],
|
||||
value: unknown,
|
||||
): void {
|
||||
const full = readFullConfig() as any;
|
||||
|
||||
ensurePluginStructure(full);
|
||||
|
||||
let target = full.plugins.entries[PLUGIN_ID].config;
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
@@ -139,3 +232,13 @@ export function getBaseUrl(): string {
|
||||
const auth = readPluginAuth();
|
||||
return auth.baseUrl || DEFAULT_BASE_URL;
|
||||
}
|
||||
|
||||
/** Remove anonymousTelemetryId from config (after PostHog aliasing) */
|
||||
export function clearAnonymousTelemetryId(): void {
|
||||
const full = readFullConfig() as any;
|
||||
const cfg = full?.plugins?.entries?.[PLUGIN_ID]?.config;
|
||||
if (cfg && "anonymousTelemetryId" in cfg) {
|
||||
delete cfg.anonymousTelemetryId;
|
||||
writeFullConfig(full);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -233,20 +233,19 @@ export const mem0ConfigSchema = {
|
||||
})(),
|
||||
autoCapture: cfg.autoCapture !== false,
|
||||
autoRecall: cfg.autoRecall !== false,
|
||||
// v3.0.0: customPrompt renamed to customInstructions (backwards-compat: accept either)
|
||||
customInstructions:
|
||||
typeof cfg.customInstructions === "string"
|
||||
? cfg.customInstructions
|
||||
: DEFAULT_CUSTOM_INSTRUCTIONS,
|
||||
: typeof cfg.customPrompt === "string"
|
||||
? cfg.customPrompt
|
||||
: DEFAULT_CUSTOM_INSTRUCTIONS,
|
||||
customCategories:
|
||||
cfg.customCategories &&
|
||||
typeof cfg.customCategories === "object" &&
|
||||
!Array.isArray(cfg.customCategories)
|
||||
? (cfg.customCategories as Record<string, string>)
|
||||
: DEFAULT_CUSTOM_CATEGORIES,
|
||||
customPrompt:
|
||||
typeof cfg.customPrompt === "string"
|
||||
? cfg.customPrompt
|
||||
: DEFAULT_CUSTOM_INSTRUCTIONS,
|
||||
searchThreshold:
|
||||
typeof cfg.searchThreshold === "number" ? cfg.searchThreshold : 0.5,
|
||||
topK: typeof cfg.topK === "number" ? cfg.topK : 5,
|
||||
|
||||
+75
-2
@@ -14,9 +14,41 @@ const NOISE_MESSAGE_PATTERNS: RegExp[] = [
|
||||
/^(HEARTBEAT_OK|NO_REPLY)$/i,
|
||||
/^Current time:.*\d{4}/,
|
||||
/^Pre-compaction memory flush/i,
|
||||
/^(ok|yes|no|sir|sure|thanks|done|good|nice|cool|got it|it's on|continue)$/i,
|
||||
/^(ok|yes|no|sir|sure|thanks|done|good|nice|cool|got it|it's on|continue|alright|okay|yep|nope|uh-huh|mm-hmm|hmm)$/i,
|
||||
/^System: \[.*\] (Slack message edited|Gateway restart|Exec (failed|completed))/,
|
||||
/^System: \[.*\] ⚠️ Post-Compaction Audit:/,
|
||||
// JSON-only messages (tool results, metadata)
|
||||
/^[\s]*\{[\s\S]*\}[\s]*$/,
|
||||
/^[\s]*\[[\s\S]*\][\s]*$/,
|
||||
// Empty or whitespace-only after trimming
|
||||
/^[\s\n\r]*$/,
|
||||
// Technical noise patterns
|
||||
/^(Error|Warning|Info|Debug):/i,
|
||||
/^(Loading|Loaded|Fetching|Fetched|Processing|Processed)\b/i,
|
||||
/^\[[\d:T\-\.Z]+\]/, // Timestamps like [2024-01-01T12:00:00.000Z]
|
||||
/^(SUCCESS|FAILURE|PENDING|COMPLETED|FAILED)$/i,
|
||||
// Tool/function call noise
|
||||
/^(Calling|Called|Invoking|Invoked|Executing|Executed)\s+(function|tool|method)/i,
|
||||
/^Tool (call|result|output):/i,
|
||||
// Single emoji or very short messages
|
||||
/^[\p{Emoji}\s]{1,5}$/u,
|
||||
];
|
||||
|
||||
/** Patterns for session-specific technical content that should not be stored as memories. */
|
||||
const SESSION_SPECIFIC_PATTERNS: RegExp[] = [
|
||||
// Tool availability discussions
|
||||
/tools?\s+(are|is)\s+(not\s+)?(exposed|available|accessible)/i,
|
||||
/plugin\s+(does not|doesn't)\s+expose/i,
|
||||
/I\s+(do not|don't)\s+(currently\s+)?see\s+.*tools?\s+exposed/i,
|
||||
/memory_(search|get|add|update|delete|list)\s+(tool|is|are)/i,
|
||||
// Session-specific capability statements
|
||||
/in\s+this\s+session/i,
|
||||
/my\s+(live\s+)?callable\s+tool\s+registry/i,
|
||||
/tools?\s+I\s+(have|currently have)\s+access\s+to/i,
|
||||
// Plugin/capability status statements
|
||||
/openclaw-mem0\s+plugin/i,
|
||||
/memory\s+wiki.*capability/i,
|
||||
/workspace\s+memory\s+files/i,
|
||||
];
|
||||
|
||||
/** Content fragments that should be stripped from otherwise-valid messages. */
|
||||
@@ -50,6 +82,31 @@ const NOISE_CONTENT_PATTERNS: Array<{ pattern: RegExp; replacement: string }> =
|
||||
/Replied message \(untrusted, for context\):\s*```json[\s\S]*?```/g,
|
||||
replacement: "",
|
||||
},
|
||||
// Strip embedded JSON blocks that might contain metadata
|
||||
{
|
||||
pattern: /```json\s*\{[\s\S]*?\}\s*```/g,
|
||||
replacement: "",
|
||||
},
|
||||
// Strip code blocks that are just tool outputs
|
||||
{
|
||||
pattern: /```(?:text|output|result|log)\s*[\s\S]*?```/gi,
|
||||
replacement: "",
|
||||
},
|
||||
// Strip inline tool call IDs
|
||||
{
|
||||
pattern: /\[tool_call_id:[^\]]+\]/g,
|
||||
replacement: "",
|
||||
},
|
||||
// Strip memory IDs from responses
|
||||
{
|
||||
pattern: /\(id:\s*[a-f0-9-]+\)/gi,
|
||||
replacement: "",
|
||||
},
|
||||
// Strip session/run IDs
|
||||
{
|
||||
pattern: /(?:session|run|agent)[_-]?(?:id|key)?:\s*[a-zA-Z0-9_:-]+/gi,
|
||||
replacement: "",
|
||||
},
|
||||
];
|
||||
|
||||
const MAX_MESSAGE_LENGTH = 2000;
|
||||
@@ -81,6 +138,20 @@ export function isNoiseMessage(content: string): boolean {
|
||||
return NOISE_MESSAGE_PATTERNS.some((p) => p.test(trimmed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a message contains session-specific technical content
|
||||
* that should not be stored (tool availability discussions, plugin
|
||||
* capability statements, etc.). These are facts about the current session
|
||||
* that have no value in future sessions.
|
||||
*/
|
||||
export function isSessionSpecificContent(content: string): boolean {
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed) return false;
|
||||
// Check if multiple session-specific patterns match (more confident filtering)
|
||||
const matches = SESSION_SPECIFIC_PATTERNS.filter((p) => p.test(trimmed));
|
||||
return matches.length >= 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an assistant message is a generic acknowledgment with no
|
||||
* extractable facts (e.g. "I see you've shared an update. How can I help?").
|
||||
@@ -120,7 +191,7 @@ function truncateMessage(content: string): string {
|
||||
|
||||
/**
|
||||
* Full pre-extraction pipeline: drop noise messages, strip noise fragments,
|
||||
* and truncate remaining messages to a reasonable length.
|
||||
* filter session-specific content, and truncate remaining messages.
|
||||
*/
|
||||
export function filterMessagesForExtraction(
|
||||
messages: Array<{ role: string; content: string }>,
|
||||
@@ -131,6 +202,8 @@ export function filterMessagesForExtraction(
|
||||
// Drop generic assistant acknowledgments that contain no facts
|
||||
if (msg.role === "assistant" && isGenericAssistantMessage(msg.content))
|
||||
continue;
|
||||
// Drop session-specific technical content (tool availability, plugin capabilities)
|
||||
if (isSessionSpecificContent(msg.content)) continue;
|
||||
const cleaned = stripNoiseFromContent(msg.content);
|
||||
if (!cleaned) continue;
|
||||
filtered.push({ role: msg.role, content: truncateMessage(cleaned) });
|
||||
|
||||
+27
-9
@@ -28,6 +28,7 @@ import type {
|
||||
import { createProvider, providerToBackend } from "./providers.ts";
|
||||
import { mem0ConfigSchema } from "./config.ts";
|
||||
import type { FileConfig } from "./config.ts";
|
||||
import { createPublicArtifactsProvider } from "./public-artifacts.ts";
|
||||
import { filterMessagesForExtraction } from "./filtering.ts";
|
||||
import {
|
||||
effectiveUserId,
|
||||
@@ -53,13 +54,14 @@ import {
|
||||
import { PlatformBackend } from "./backend/platform.ts";
|
||||
import type { Backend } from "./backend/base.ts";
|
||||
import { registerCliCommands } from "./cli/commands.ts";
|
||||
import { readPluginAuth } from "./cli/config-file.ts";
|
||||
import { readPluginAuth, ensureInstallRecord } from "./cli/config-file.ts";
|
||||
import { registerAllTools } from "./tools/index.ts";
|
||||
import type { ToolDeps } from "./tools/index.ts";
|
||||
import { captureEvent } from "./telemetry.ts";
|
||||
import { bootstrapTelemetryFlag } from "./fs-safe.ts";
|
||||
|
||||
bootstrapTelemetryFlag();
|
||||
ensureInstallRecord();
|
||||
|
||||
// ============================================================================
|
||||
// Re-exports (for tests and external consumers)
|
||||
@@ -76,6 +78,7 @@ export {
|
||||
export {
|
||||
isNoiseMessage,
|
||||
isGenericAssistantMessage,
|
||||
isSessionSpecificContent,
|
||||
stripNoiseFromContent,
|
||||
filterMessagesForExtraction,
|
||||
} from "./filtering.ts";
|
||||
@@ -191,24 +194,43 @@ const memoryPlugin = definePluginEntry({
|
||||
`openclaw-mem0: registered (mode: ${cfg.mode}, user: ${cfg.userId}, autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture}, skills: ${skillsActive})`,
|
||||
);
|
||||
|
||||
// ========================================================================
|
||||
// Public Artifacts (for memory-wiki bridge mode)
|
||||
// ========================================================================
|
||||
if (typeof api.registerMemoryCapability === "function") {
|
||||
api.registerMemoryCapability({
|
||||
publicArtifacts: createPublicArtifactsProvider({
|
||||
provider,
|
||||
cfg,
|
||||
get stateDir() {
|
||||
return pluginStateDir;
|
||||
},
|
||||
effectiveUserId: _effectiveUserId,
|
||||
}),
|
||||
});
|
||||
api.logger.debug("openclaw-mem0: publicArtifacts capability registered");
|
||||
}
|
||||
|
||||
// Helper: build add options
|
||||
function buildAddOptions(
|
||||
userIdOverride?: string,
|
||||
runId?: string,
|
||||
sessionKey?: string,
|
||||
): AddOptions {
|
||||
// v3.0.0: removed output_format, customPrompt renamed to customInstructions
|
||||
const opts: AddOptions = {
|
||||
user_id: userIdOverride || _effectiveUserId(sessionKey),
|
||||
source: "OPENCLAW",
|
||||
};
|
||||
if (runId) opts.run_id = runId;
|
||||
if (cfg.mode === "platform") {
|
||||
opts.output_format = "v1.1";
|
||||
}
|
||||
// Pass customInstructions and customCategories to control what Mem0 extracts
|
||||
if (cfg.customInstructions) opts.custom_instructions = cfg.customInstructions;
|
||||
if (cfg.customCategories) opts.custom_categories = cfg.customCategories;
|
||||
return opts;
|
||||
}
|
||||
|
||||
// Helper: build search options (skills config overrides legacy defaults)
|
||||
// v3.0.0: removed keyword_search, reranking, filter_memories, limit
|
||||
function buildSearchOptions(
|
||||
userIdOverride?: string,
|
||||
limit?: number,
|
||||
@@ -219,13 +241,9 @@ const memoryPlugin = definePluginEntry({
|
||||
const opts: SearchOptions = {
|
||||
user_id: userIdOverride || _effectiveUserId(sessionKey),
|
||||
top_k: limit ?? cfg.topK,
|
||||
limit: limit ?? cfg.topK,
|
||||
threshold: recallCfg?.threshold ?? cfg.searchThreshold,
|
||||
keyword_search: recallCfg?.keywordSearch !== false,
|
||||
reranking: recallCfg?.rerank !== false,
|
||||
source: "OPENCLAW",
|
||||
};
|
||||
if (recallCfg?.filterMemories) opts.filter_memories = true;
|
||||
if (runId) opts.run_id = runId;
|
||||
return opts;
|
||||
}
|
||||
@@ -950,7 +968,7 @@ function registerHooks(
|
||||
content: `Current date: ${timestamp}. The user is identified as "${cfg.userId}". Extract durable facts from this conversation. Include this date when storing time-sensitive information.`,
|
||||
});
|
||||
|
||||
const addOpts = buildAddOptions(undefined, sessionId, sessionId);
|
||||
const addOpts = buildAddOptions(undefined, undefined, sessionId);
|
||||
const captureStart = Date.now();
|
||||
provider
|
||||
.add(formattedMessages, addOpts)
|
||||
|
||||
Vendored
+26
@@ -1,4 +1,29 @@
|
||||
declare module "openclaw/plugin-sdk" {
|
||||
export interface MemoryArtifact {
|
||||
id: string;
|
||||
type: "memory" | "dream" | "digest" | "entity";
|
||||
title: string;
|
||||
content: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface PublicArtifactsProvider {
|
||||
listArtifacts(options?: {
|
||||
userId?: string;
|
||||
types?: string[];
|
||||
limit?: number;
|
||||
}): Promise<MemoryArtifact[]>;
|
||||
}
|
||||
|
||||
export interface MemoryCapabilityConfig {
|
||||
promptBuilder?: (ctx: any) => Promise<string | null>;
|
||||
flushPlanResolver?: (ctx: any) => Promise<any>;
|
||||
runtime?: Record<string, unknown>;
|
||||
publicArtifacts?: PublicArtifactsProvider;
|
||||
}
|
||||
|
||||
export interface OpenClawPluginApi {
|
||||
pluginConfig: Record<string, unknown>;
|
||||
logger: {
|
||||
@@ -32,6 +57,7 @@ declare module "openclaw/plugin-sdk" {
|
||||
start: (...args: any[]) => void;
|
||||
stop: () => void;
|
||||
}): void;
|
||||
registerMemoryCapability?(config: MemoryCapabilityConfig): void;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
{
|
||||
"id": "openclaw-mem0",
|
||||
"name": "Memory (Mem0)",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source. Injects recalled memories into agent context (auto-recall) and extracts facts after each turn (auto-capture). Both configurable via autoRecall/autoCapture settings.",
|
||||
"version": "1.0.5",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source. PLATFORM MODE: Sends conversation data to mem0.ai cloud (requires MEM0_API_KEY). OPEN-SOURCE MODE: Stores vectors locally (~/.mem0/history.db) but uses external APIs for embeddings/LLM (default: OpenAI, requires OPENAI_API_KEY). Auto-recall injects memories before agent turns; auto-capture extracts facts after turns. Both configurable via autoRecall/autoCapture settings. Config stored in ~/.openclaw/openclaw.json.",
|
||||
"version": "1.0.7",
|
||||
"kind": "memory",
|
||||
"skills": ["skills"],
|
||||
"commandAliases": [
|
||||
{
|
||||
"name": "mem0",
|
||||
"cliCommand": "mem0",
|
||||
"description": "Mem0 memory plugin commands"
|
||||
}
|
||||
],
|
||||
"contracts": {
|
||||
"tools": [
|
||||
"memory_search", "memory_add", "memory_get", "memory_list",
|
||||
@@ -13,7 +20,7 @@
|
||||
},
|
||||
"providerAuthEnvVars": {
|
||||
"mem0": ["MEM0_API_KEY"],
|
||||
"openclaw-mem0-oss": ["OPENAI_API_KEY"]
|
||||
"openclaw-mem0-oss": ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "AZURE_OPENAI_API_KEY", "COHERE_API_KEY"]
|
||||
},
|
||||
"providerAuthChoices": [
|
||||
{
|
||||
@@ -176,6 +183,9 @@
|
||||
},
|
||||
"historyDbPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableHistory": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/openclaw-mem0",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"type": "module",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
|
||||
"license": "Apache-2.0",
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "0.34.47",
|
||||
"mem0ai": "2.4.5"
|
||||
"mem0ai": "3.0.1"
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
@@ -48,6 +48,9 @@
|
||||
"build": {
|
||||
"openclawVersion": "2026.4.1",
|
||||
"pluginSdkVersion": "2026.4.1"
|
||||
},
|
||||
"install": {
|
||||
"npmSpec": "@mem0/openclaw-mem0"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Generated
+338
-118
@@ -12,8 +12,8 @@ importers:
|
||||
specifier: 0.34.47
|
||||
version: 0.34.47
|
||||
mem0ai:
|
||||
specifier: 2.4.5
|
||||
version: 2.4.5(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(groq-sdk@0.3.0)(neo4j-driver@5.28.3)(ollama@0.5.18)(pg@8.11.3)(redis@4.7.1)(ws@8.19.0)
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(compromise@14.15.0)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.20.0)(redis@5.12.1)(ws@8.19.0)
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.15.0
|
||||
@@ -340,6 +340,9 @@ packages:
|
||||
'@mistralai/mistralai@1.15.1':
|
||||
resolution: {integrity: sha512-fb995eiz3r0KsBGtRjFV+/iLbX+UpfalxpF+YitT3R6ukrPD4PN+FGwwmYcRFhNAzVzDUtTVxQYnjQWEnwV5nw==}
|
||||
|
||||
'@mongodb-js/saslprep@1.4.8':
|
||||
resolution: {integrity: sha512-kpjr2jy2w71w0oqAMI8oibBmiF9lXxWkEQs5gMkW4hVE48bsqINGLxnCSYW62ck/NHXJQpQEfA9WlJ1sY0eqBg==}
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.1':
|
||||
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
|
||||
|
||||
@@ -394,34 +397,41 @@ packages:
|
||||
resolution: {integrity: sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==}
|
||||
engines: {node: '>=18.0.0', pnpm: '>=8'}
|
||||
|
||||
'@redis/bloom@1.2.0':
|
||||
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
|
||||
'@redis/bloom@5.12.1':
|
||||
resolution: {integrity: sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA==}
|
||||
engines: {node: '>= 18.19.0'}
|
||||
peerDependencies:
|
||||
'@redis/client': ^1.0.0
|
||||
'@redis/client': ^5.12.1
|
||||
|
||||
'@redis/client@1.6.1':
|
||||
resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@redis/graph@1.1.1':
|
||||
resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==}
|
||||
'@redis/client@5.12.1':
|
||||
resolution: {integrity: sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==}
|
||||
engines: {node: '>= 18.19.0'}
|
||||
peerDependencies:
|
||||
'@redis/client': ^1.0.0
|
||||
'@node-rs/xxhash': ^1.1.0
|
||||
'@opentelemetry/api': '>=1 <2'
|
||||
peerDependenciesMeta:
|
||||
'@node-rs/xxhash':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
|
||||
'@redis/json@1.0.7':
|
||||
resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==}
|
||||
'@redis/json@5.12.1':
|
||||
resolution: {integrity: sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg==}
|
||||
engines: {node: '>= 18.19.0'}
|
||||
peerDependencies:
|
||||
'@redis/client': ^1.0.0
|
||||
'@redis/client': ^5.12.1
|
||||
|
||||
'@redis/search@1.2.0':
|
||||
resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==}
|
||||
'@redis/search@5.12.1':
|
||||
resolution: {integrity: sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w==}
|
||||
engines: {node: '>= 18.19.0'}
|
||||
peerDependencies:
|
||||
'@redis/client': ^1.0.0
|
||||
'@redis/client': ^5.12.1
|
||||
|
||||
'@redis/time-series@1.1.0':
|
||||
resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==}
|
||||
'@redis/time-series@5.12.1':
|
||||
resolution: {integrity: sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ==}
|
||||
engines: {node: '>= 18.19.0'}
|
||||
peerDependencies:
|
||||
'@redis/client': ^1.0.0
|
||||
'@redis/client': ^5.12.1
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-rc.9':
|
||||
resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==}
|
||||
@@ -724,6 +734,12 @@ packages:
|
||||
'@types/uuid@10.0.0':
|
||||
resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
|
||||
|
||||
'@types/webidl-conversions@7.0.3':
|
||||
resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==}
|
||||
|
||||
'@types/whatwg-url@13.0.0':
|
||||
resolution: {integrity: sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||
|
||||
@@ -784,6 +800,12 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
afinn-165-financialmarketnews@3.0.0:
|
||||
resolution: {integrity: sha512-0g9A1S3ZomFIGDTzZ0t6xmv4AuokBvBmpes8htiyHpH7N4xDmvSQL6UxL/Zcs2ypRb3VwgCscaD8Q3zEawKYhw==}
|
||||
|
||||
afinn-165@2.0.2:
|
||||
resolution: {integrity: sha512-mJ/RLUfpXfQA6bzugv+bBsc/QYkVrKaLYeS8fWBpKbTCsonv4iuV9ET0fgReEunm9vKLkaNgnekuSNlTC3WQ1Q==}
|
||||
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -815,6 +837,10 @@ packages:
|
||||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
apparatus@0.0.10:
|
||||
resolution: {integrity: sha512-KLy/ugo33KZA7nugtQ7O0E1c8kQ52N3IvD/XgIh4w/Nr28ypfkwDfA67F1ev4N1m5D+BOk1+b2dEJDfpj/VvZg==}
|
||||
engines: {node: '>=0.2.6'}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -857,19 +883,16 @@ packages:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
bson@7.2.0:
|
||||
resolution: {integrity: sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
|
||||
buffer-writer@2.0.0:
|
||||
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||
|
||||
bundle-name@4.1.0:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -936,6 +959,10 @@ packages:
|
||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
compromise@14.15.0:
|
||||
resolution: {integrity: sha512-YEMv5JGWyqRJw5hdZqDVQF3MMlHA6TRiXreR8IYffk6xB7GA5p/8DeDzvg0Jy2tHNGpD+qJGl0+oJwA+5R/sVA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
||||
@@ -1008,6 +1035,10 @@ packages:
|
||||
digest-fetch@1.3.0:
|
||||
resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==}
|
||||
|
||||
dotenv@17.4.2:
|
||||
resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1018,6 +1049,10 @@ packages:
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
|
||||
efrt@2.7.0:
|
||||
resolution: {integrity: sha512-/RInbCy1d4P6Zdfa+TMVsf/ufZVotat5hCw3QXmWtjU+3pFEOvOQ7ibo3aIxyCJw2leIeAMjmPj+1SLJiCpdrQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
@@ -1154,10 +1189,6 @@ packages:
|
||||
resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
generic-pool@3.9.0:
|
||||
resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1189,6 +1220,10 @@ packages:
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
grad-school@0.0.5:
|
||||
resolution: {integrity: sha512-rXunEHF9M9EkMydTBux7+IryYXEZinRk6g8OBOGDBzo/qWJjhTxy86i5q7lQYpCLHN8Sqv1XX3OIOc7ka2gtvQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
groq-sdk@0.3.0:
|
||||
resolution: {integrity: sha512-Cdgjh4YoSBE2X4S9sxPGXaAy1dlN4bRtAaDZ3cnq+XsxhhN9WSBeHF64l7LWwuD5ntmw7YC5Vf4Ff1oHCg1LOg==}
|
||||
|
||||
@@ -1324,6 +1359,10 @@ packages:
|
||||
jws@4.0.1:
|
||||
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
|
||||
|
||||
kareem@3.2.0:
|
||||
resolution: {integrity: sha512-VS8MWZz/cT+SqBCpVfNN4zoVz5VskR3N4+sTmUXme55e9avQHntpwpNq0yjnosISXqwJ3AQVjlbI4Dyzv//JtA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
langsmith@0.3.87:
|
||||
resolution: {integrity: sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q==}
|
||||
peerDependencies:
|
||||
@@ -1466,8 +1505,8 @@ packages:
|
||||
md5@2.3.0:
|
||||
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
|
||||
|
||||
mem0ai@2.4.5:
|
||||
resolution: {integrity: sha512-0XMRe5/KKZXkSJDb2YDqgWdIfJpL2cn8Lelx9pvDPJOvBfXx8uHjtSc80jyGyb9C/1wW2uPcaE+WS+Rs/2phCg==}
|
||||
mem0ai@3.0.1:
|
||||
resolution: {integrity: sha512-6phM544/3NRcCg7n5DBNRc9uUEMqGTe7sksULM2KM/FTihH27yTl5nPms8cnNpSKM5/ZKJ9jnkpzwGlvYYbtBQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@anthropic-ai/sdk': ^0.40.1
|
||||
@@ -1483,12 +1522,20 @@ packages:
|
||||
'@types/pg': 8.11.0
|
||||
better-sqlite3: ^12.6.2
|
||||
cloudflare: ^4.2.0
|
||||
compromise: ^14.0.0
|
||||
groq-sdk: 0.3.0
|
||||
neo4j-driver: ^5.28.1
|
||||
natural: ^8.0.1
|
||||
ollama: ^0.5.14
|
||||
pg: 8.11.3
|
||||
redis: ^4.6.13
|
||||
|
||||
memjs@1.3.2:
|
||||
resolution: {integrity: sha512-qUEg2g8vxPe+zPn09KidjIStHPtoBO8Cttm8bgJFWWabbsjQ9Av9Ky+6UcvKx6ue0LLb/LEhtcyQpRyKfzeXcg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
memory-pager@1.5.0:
|
||||
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
|
||||
|
||||
micromatch@4.0.8:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
@@ -1522,6 +1569,49 @@ packages:
|
||||
mlly@1.8.1:
|
||||
resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==}
|
||||
|
||||
mongodb-connection-string-url@7.0.1:
|
||||
resolution: {integrity: sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
mongodb@7.1.1:
|
||||
resolution: {integrity: sha512-067DXiMjcpYQl6bGjWQoTUEE9UoRViTtKFcoqX7z08I+iDZv/emH1g8XEFiO3qiDfXAheT5ozl1VffDTKhIW/w==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
peerDependencies:
|
||||
'@aws-sdk/credential-providers': ^3.806.0
|
||||
'@mongodb-js/zstd': ^7.0.0
|
||||
gcp-metadata: ^7.0.1
|
||||
kerberos: ^7.0.0
|
||||
mongodb-client-encryption: '>=7.0.0 <7.1.0'
|
||||
snappy: ^7.3.2
|
||||
socks: ^2.8.6
|
||||
peerDependenciesMeta:
|
||||
'@aws-sdk/credential-providers':
|
||||
optional: true
|
||||
'@mongodb-js/zstd':
|
||||
optional: true
|
||||
gcp-metadata:
|
||||
optional: true
|
||||
kerberos:
|
||||
optional: true
|
||||
mongodb-client-encryption:
|
||||
optional: true
|
||||
snappy:
|
||||
optional: true
|
||||
socks:
|
||||
optional: true
|
||||
|
||||
mongoose@9.4.1:
|
||||
resolution: {integrity: sha512-4rFBWa+/wdBQSfvnOPJBpiSG6UCEbhSQh865dEdaH9Y8WfHBUC+I2XT28dp0IBIGrEwmh+gzrgZgea5PbmrHWA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
mpath@0.9.0:
|
||||
resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
mquery@6.0.0:
|
||||
resolution: {integrity: sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -1540,14 +1630,9 @@ packages:
|
||||
napi-build-utils@2.0.0:
|
||||
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
|
||||
|
||||
neo4j-driver-bolt-connection@5.28.3:
|
||||
resolution: {integrity: sha512-wqHBYcU0FVRDmdsoZ+Fk0S/InYmu9/4BT6fPYh45Jimg/J7vQBUcdkiHGU7nop7HRb1ZgJmL305mJb6g5Bv35Q==}
|
||||
|
||||
neo4j-driver-core@5.28.3:
|
||||
resolution: {integrity: sha512-Jk+hAmjFmO5YzVH/U7FyKXigot9zmIfLz6SZQy0xfr4zfTE/S8fOYFOGqKQTHBE86HHOWH2RbTslbxIb+XtU2g==}
|
||||
|
||||
neo4j-driver@5.28.3:
|
||||
resolution: {integrity: sha512-k7c0wEh3HoONv1v5AyLp9/BDAbYHJhz2TZvzWstSEU3g3suQcXmKEaYBfrK2UMzxcy3bCT0DrnfRbzsOW5G/Ag==}
|
||||
natural@8.1.1:
|
||||
resolution: {integrity: sha512-Ucb+lsUcGxUqu3rn8cwHjT6gJQosO63nIX/aBQXB3+IDkNbFV7PuviysO+Rzz3aKn7PZhPj3bNF4PS9gDVjYCQ==}
|
||||
engines: {node: '>=0.4.10'}
|
||||
|
||||
node-abi@3.88.0:
|
||||
resolution: {integrity: sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==}
|
||||
@@ -1622,9 +1707,6 @@ packages:
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
|
||||
packet-reader@1.0.0:
|
||||
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1666,9 +1748,9 @@ packages:
|
||||
resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
pg@8.11.3:
|
||||
resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
pg@8.20.0:
|
||||
resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==}
|
||||
engines: {node: '>= 16.0.0'}
|
||||
peerDependencies:
|
||||
pg-native: '>=3.0.1'
|
||||
peerDependenciesMeta:
|
||||
@@ -1773,6 +1855,10 @@ packages:
|
||||
pump@3.0.4:
|
||||
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
rc@1.2.8:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
@@ -1788,8 +1874,9 @@ packages:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
redis@4.7.1:
|
||||
resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==}
|
||||
redis@5.12.1:
|
||||
resolution: {integrity: sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==}
|
||||
engines: {node: '>= 18.19.0'}
|
||||
|
||||
resolve-from@5.0.0:
|
||||
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
|
||||
@@ -1817,12 +1904,13 @@ packages:
|
||||
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
rxjs@7.8.2:
|
||||
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
safe-stable-stringify@2.5.0:
|
||||
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
semver@7.7.4:
|
||||
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1836,6 +1924,9 @@ packages:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
sift@17.1.3:
|
||||
resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==}
|
||||
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
@@ -1864,6 +1955,9 @@ packages:
|
||||
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
sparse-bitfield@3.0.3:
|
||||
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
|
||||
|
||||
split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
@@ -1878,6 +1972,10 @@ packages:
|
||||
std-env@4.0.0:
|
||||
resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
|
||||
|
||||
stopwords-iso@1.1.0:
|
||||
resolution: {integrity: sha512-I6GPS/E0zyieHehMRPQcqkiBMJKGgLta+1hREixhoLPqEA0AlVFiC43dl8uPpmkkeRdDMzYRWFWk5/l9x7nmNg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1906,10 +2004,17 @@ packages:
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
hasBin: true
|
||||
|
||||
suffix-thumb@5.0.2:
|
||||
resolution: {integrity: sha512-I5PWXAFKx3FYnI9a+dQMWNqTxoRt6vdBdb0O+BJ1sxXCWtSoQCusc13E58f+9p4MYx/qCnEMkD5jac6K2j3dgA==}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
sylvester@0.0.21:
|
||||
resolution: {integrity: sha512-yUT0ukFkFEt4nb+NY+n2ag51aS/u9UHXoZw+A4jgD77/jzZsBoSDHuqysrVCBC4CYR4TYvUJq54ONpXgDBH8tA==}
|
||||
engines: {node: '>=0.2.6'}
|
||||
|
||||
tar-fs@2.1.4:
|
||||
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
|
||||
|
||||
@@ -1949,6 +2054,10 @@ packages:
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
tr46@5.1.1:
|
||||
resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
@@ -1989,6 +2098,9 @@ packages:
|
||||
ufo@1.6.3:
|
||||
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
|
||||
|
||||
underscore@1.13.8:
|
||||
resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==}
|
||||
|
||||
undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
@@ -2006,6 +2118,10 @@ packages:
|
||||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
|
||||
hasBin: true
|
||||
|
||||
uuid@13.0.0:
|
||||
resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
|
||||
hasBin: true
|
||||
|
||||
uuid@8.3.2:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
hasBin: true
|
||||
@@ -2103,9 +2219,17 @@ packages:
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
webidl-conversions@7.0.0:
|
||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
whatwg-fetch@3.6.20:
|
||||
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
|
||||
|
||||
whatwg-url@14.2.0:
|
||||
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
@@ -2119,6 +2243,10 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
wordnet-db@3.1.14:
|
||||
resolution: {integrity: sha512-zVyFsvE+mq9MCmwXUWHIcpfbrHHClZWZiVOzKSxNJruIcFn2RbY55zkhiAMMxM8zCVSmtNiViq8FsAZSFpMYag==}
|
||||
engines: {node: '>=0.6.0'}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2150,9 +2278,6 @@ packages:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
||||
yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
|
||||
zod-to-json-schema@3.25.1:
|
||||
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
|
||||
peerDependencies:
|
||||
@@ -2487,6 +2612,10 @@ snapshots:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@mongodb-js/saslprep@1.4.8':
|
||||
dependencies:
|
||||
sparse-bitfield: 3.0.3
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.1':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.9.0
|
||||
@@ -2533,31 +2662,25 @@ snapshots:
|
||||
|
||||
'@qdrant/openapi-typescript-fetch@1.2.6': {}
|
||||
|
||||
'@redis/bloom@1.2.0(@redis/client@1.6.1)':
|
||||
'@redis/bloom@5.12.1(@redis/client@5.12.1)':
|
||||
dependencies:
|
||||
'@redis/client': 1.6.1
|
||||
'@redis/client': 5.12.1
|
||||
|
||||
'@redis/client@1.6.1':
|
||||
'@redis/client@5.12.1':
|
||||
dependencies:
|
||||
cluster-key-slot: 1.1.2
|
||||
generic-pool: 3.9.0
|
||||
yallist: 4.0.0
|
||||
|
||||
'@redis/graph@1.1.1(@redis/client@1.6.1)':
|
||||
'@redis/json@5.12.1(@redis/client@5.12.1)':
|
||||
dependencies:
|
||||
'@redis/client': 1.6.1
|
||||
'@redis/client': 5.12.1
|
||||
|
||||
'@redis/json@1.0.7(@redis/client@1.6.1)':
|
||||
'@redis/search@5.12.1(@redis/client@5.12.1)':
|
||||
dependencies:
|
||||
'@redis/client': 1.6.1
|
||||
'@redis/client': 5.12.1
|
||||
|
||||
'@redis/search@1.2.0(@redis/client@1.6.1)':
|
||||
'@redis/time-series@5.12.1(@redis/client@5.12.1)':
|
||||
dependencies:
|
||||
'@redis/client': 1.6.1
|
||||
|
||||
'@redis/time-series@1.1.0(@redis/client@1.6.1)':
|
||||
dependencies:
|
||||
'@redis/client': 1.6.1
|
||||
'@redis/client': 5.12.1
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-rc.9':
|
||||
optional: true
|
||||
@@ -2785,6 +2908,12 @@ snapshots:
|
||||
|
||||
'@types/uuid@10.0.0': {}
|
||||
|
||||
'@types/webidl-conversions@7.0.3': {}
|
||||
|
||||
'@types/whatwg-url@13.0.0':
|
||||
dependencies:
|
||||
'@types/webidl-conversions': 7.0.3
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 22.19.15
|
||||
@@ -2864,6 +2993,10 @@ snapshots:
|
||||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
afinn-165-financialmarketnews@3.0.0: {}
|
||||
|
||||
afinn-165@2.0.2: {}
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
agentkeepalive@4.6.0:
|
||||
@@ -2884,6 +3017,10 @@ snapshots:
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
apparatus@0.0.10:
|
||||
dependencies:
|
||||
sylvester: 0.0.21
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
ast-v8-to-istanbul@1.0.0:
|
||||
@@ -2933,20 +3070,15 @@ snapshots:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
|
||||
bson@7.2.0: {}
|
||||
|
||||
buffer-equal-constant-time@1.0.1: {}
|
||||
|
||||
buffer-writer@2.0.0: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
buffer@6.0.3:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
bundle-name@4.1.0:
|
||||
dependencies:
|
||||
run-applescript: 7.1.0
|
||||
@@ -3008,6 +3140,12 @@ snapshots:
|
||||
|
||||
commander@4.1.1: {}
|
||||
|
||||
compromise@14.15.0:
|
||||
dependencies:
|
||||
efrt: 2.7.0
|
||||
grad-school: 0.0.5
|
||||
suffix-thumb: 5.0.2
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
consola@3.4.2: {}
|
||||
@@ -3060,6 +3198,8 @@ snapshots:
|
||||
base-64: 0.1.0
|
||||
md5: 2.3.0
|
||||
|
||||
dotenv@17.4.2: {}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
@@ -3072,6 +3212,8 @@ snapshots:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
efrt@2.7.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
@@ -3223,8 +3365,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
generic-pool@3.9.0: {}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
@@ -3271,6 +3411,8 @@ snapshots:
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
grad-school@0.0.5: {}
|
||||
|
||||
groq-sdk@0.3.0:
|
||||
dependencies:
|
||||
'@types/node': 18.19.130
|
||||
@@ -3437,6 +3579,8 @@ snapshots:
|
||||
jwa: 2.0.1
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
kareem@3.2.0: {}
|
||||
|
||||
langsmith@0.3.87(openai@4.104.0(ws@8.19.0)(zod@3.25.76)):
|
||||
dependencies:
|
||||
'@types/uuid': 10.0.0
|
||||
@@ -3543,7 +3687,7 @@ snapshots:
|
||||
crypt: 0.0.2
|
||||
is-buffer: 1.1.6
|
||||
|
||||
mem0ai@2.4.5(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(groq-sdk@0.3.0)(neo4j-driver@5.28.3)(ollama@0.5.18)(pg@8.11.3)(redis@4.7.1)(ws@8.19.0):
|
||||
mem0ai@3.0.1(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(compromise@14.15.0)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.20.0)(redis@5.12.1)(ws@8.19.0):
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk': 0.40.1
|
||||
'@azure/identity': 4.13.0
|
||||
@@ -3559,12 +3703,13 @@ snapshots:
|
||||
axios: 1.13.6
|
||||
better-sqlite3: 12.8.0
|
||||
cloudflare: 4.5.0
|
||||
compromise: 14.15.0
|
||||
groq-sdk: 0.3.0
|
||||
neo4j-driver: 5.28.3
|
||||
natural: 8.1.1
|
||||
ollama: 0.5.18
|
||||
openai: 4.104.0(ws@8.19.0)(zod@3.25.76)
|
||||
pg: 8.11.3
|
||||
redis: 4.7.1
|
||||
pg: 8.20.0
|
||||
redis: 5.12.1
|
||||
uuid: 9.0.1
|
||||
zod: 3.25.76
|
||||
transitivePeerDependencies:
|
||||
@@ -3572,6 +3717,10 @@ snapshots:
|
||||
- encoding
|
||||
- ws
|
||||
|
||||
memjs@1.3.2: {}
|
||||
|
||||
memory-pager@1.5.0: {}
|
||||
|
||||
micromatch@4.0.8:
|
||||
dependencies:
|
||||
braces: 3.0.3
|
||||
@@ -3602,6 +3751,38 @@ snapshots:
|
||||
pkg-types: 1.3.1
|
||||
ufo: 1.6.3
|
||||
|
||||
mongodb-connection-string-url@7.0.1:
|
||||
dependencies:
|
||||
'@types/whatwg-url': 13.0.0
|
||||
whatwg-url: 14.2.0
|
||||
|
||||
mongodb@7.1.1:
|
||||
dependencies:
|
||||
'@mongodb-js/saslprep': 1.4.8
|
||||
bson: 7.2.0
|
||||
mongodb-connection-string-url: 7.0.1
|
||||
|
||||
mongoose@9.4.1:
|
||||
dependencies:
|
||||
kareem: 3.2.0
|
||||
mongodb: 7.1.1
|
||||
mpath: 0.9.0
|
||||
mquery: 6.0.0
|
||||
ms: 2.1.3
|
||||
sift: 17.1.3
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-providers'
|
||||
- '@mongodb-js/zstd'
|
||||
- gcp-metadata
|
||||
- kerberos
|
||||
- mongodb-client-encryption
|
||||
- snappy
|
||||
- socks
|
||||
|
||||
mpath@0.9.0: {}
|
||||
|
||||
mquery@6.0.0: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
mustache@4.2.0: {}
|
||||
@@ -3616,19 +3797,33 @@ snapshots:
|
||||
|
||||
napi-build-utils@2.0.0: {}
|
||||
|
||||
neo4j-driver-bolt-connection@5.28.3:
|
||||
natural@8.1.1:
|
||||
dependencies:
|
||||
buffer: 6.0.3
|
||||
neo4j-driver-core: 5.28.3
|
||||
string_decoder: 1.3.0
|
||||
|
||||
neo4j-driver-core@5.28.3: {}
|
||||
|
||||
neo4j-driver@5.28.3:
|
||||
dependencies:
|
||||
neo4j-driver-bolt-connection: 5.28.3
|
||||
neo4j-driver-core: 5.28.3
|
||||
rxjs: 7.8.2
|
||||
afinn-165: 2.0.2
|
||||
afinn-165-financialmarketnews: 3.0.0
|
||||
apparatus: 0.0.10
|
||||
dotenv: 17.4.2
|
||||
memjs: 1.3.2
|
||||
mongoose: 9.4.1
|
||||
pg: 8.20.0
|
||||
redis: 5.12.1
|
||||
safe-stable-stringify: 2.5.0
|
||||
stopwords-iso: 1.1.0
|
||||
sylvester: 0.0.21
|
||||
underscore: 1.13.8
|
||||
uuid: 13.0.0
|
||||
wordnet-db: 3.1.14
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-providers'
|
||||
- '@mongodb-js/zstd'
|
||||
- '@node-rs/xxhash'
|
||||
- '@opentelemetry/api'
|
||||
- gcp-metadata
|
||||
- kerberos
|
||||
- mongodb-client-encryption
|
||||
- pg-native
|
||||
- snappy
|
||||
- socks
|
||||
|
||||
node-abi@3.88.0:
|
||||
dependencies:
|
||||
@@ -3700,8 +3895,6 @@ snapshots:
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
packet-reader@1.0.0: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
@@ -3720,9 +3913,9 @@ snapshots:
|
||||
|
||||
pg-numeric@1.0.2: {}
|
||||
|
||||
pg-pool@3.13.0(pg@8.11.3):
|
||||
pg-pool@3.13.0(pg@8.20.0):
|
||||
dependencies:
|
||||
pg: 8.11.3
|
||||
pg: 8.20.0
|
||||
|
||||
pg-protocol@1.13.0: {}
|
||||
|
||||
@@ -3744,12 +3937,10 @@ snapshots:
|
||||
postgres-interval: 3.0.0
|
||||
postgres-range: 1.1.4
|
||||
|
||||
pg@8.11.3:
|
||||
pg@8.20.0:
|
||||
dependencies:
|
||||
buffer-writer: 2.0.0
|
||||
packet-reader: 1.0.0
|
||||
pg-connection-string: 2.12.0
|
||||
pg-pool: 3.13.0(pg@8.11.3)
|
||||
pg-pool: 3.13.0(pg@8.20.0)
|
||||
pg-protocol: 1.13.0
|
||||
pg-types: 2.2.0
|
||||
pgpass: 1.0.5
|
||||
@@ -3851,6 +4042,8 @@ snapshots:
|
||||
end-of-stream: 1.4.5
|
||||
once: 1.4.0
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
rc@1.2.8:
|
||||
dependencies:
|
||||
deep-extend: 0.6.0
|
||||
@@ -3868,14 +4061,16 @@ snapshots:
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
redis@4.7.1:
|
||||
redis@5.12.1:
|
||||
dependencies:
|
||||
'@redis/bloom': 1.2.0(@redis/client@1.6.1)
|
||||
'@redis/client': 1.6.1
|
||||
'@redis/graph': 1.1.1(@redis/client@1.6.1)
|
||||
'@redis/json': 1.0.7(@redis/client@1.6.1)
|
||||
'@redis/search': 1.2.0(@redis/client@1.6.1)
|
||||
'@redis/time-series': 1.1.0(@redis/client@1.6.1)
|
||||
'@redis/bloom': 5.12.1(@redis/client@5.12.1)
|
||||
'@redis/client': 5.12.1
|
||||
'@redis/json': 5.12.1(@redis/client@5.12.1)
|
||||
'@redis/search': 5.12.1(@redis/client@5.12.1)
|
||||
'@redis/time-series': 5.12.1(@redis/client@5.12.1)
|
||||
transitivePeerDependencies:
|
||||
- '@node-rs/xxhash'
|
||||
- '@opentelemetry/api'
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
|
||||
@@ -3939,12 +4134,10 @@ snapshots:
|
||||
|
||||
run-applescript@7.1.0: {}
|
||||
|
||||
rxjs@7.8.2:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-stable-stringify@2.5.0: {}
|
||||
|
||||
semver@7.7.4: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
@@ -3953,6 +4146,8 @@ snapshots:
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
sift@17.1.3: {}
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
@@ -3973,6 +4168,10 @@ snapshots:
|
||||
|
||||
source-map@0.7.6: {}
|
||||
|
||||
sparse-bitfield@3.0.3:
|
||||
dependencies:
|
||||
memory-pager: 1.5.0
|
||||
|
||||
split2@4.2.0: {}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
@@ -3983,6 +4182,8 @@ snapshots:
|
||||
|
||||
std-env@4.0.0: {}
|
||||
|
||||
stopwords-iso@1.1.0: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
@@ -4019,10 +4220,14 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
ts-interface-checker: 0.1.13
|
||||
|
||||
suffix-thumb@5.0.2: {}
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
sylvester@0.0.21: {}
|
||||
|
||||
tar-fs@2.1.4:
|
||||
dependencies:
|
||||
chownr: 1.1.4
|
||||
@@ -4065,6 +4270,10 @@ snapshots:
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
tr46@5.1.1:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
@@ -4107,6 +4316,8 @@ snapshots:
|
||||
|
||||
ufo@1.6.3: {}
|
||||
|
||||
underscore@1.13.8: {}
|
||||
|
||||
undici-types@5.26.5: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
@@ -4119,6 +4330,8 @@ snapshots:
|
||||
|
||||
uuid@10.0.0: {}
|
||||
|
||||
uuid@13.0.0: {}
|
||||
|
||||
uuid@8.3.2: {}
|
||||
|
||||
uuid@9.0.1: {}
|
||||
@@ -4169,8 +4382,15 @@ snapshots:
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
webidl-conversions@7.0.0: {}
|
||||
|
||||
whatwg-fetch@3.6.20: {}
|
||||
|
||||
whatwg-url@14.2.0:
|
||||
dependencies:
|
||||
tr46: 5.1.1
|
||||
webidl-conversions: 7.0.0
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
@@ -4185,6 +4405,8 @@ snapshots:
|
||||
siginfo: 2.0.0
|
||||
stackback: 0.0.2
|
||||
|
||||
wordnet-db@3.1.14: {}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
@@ -4207,8 +4429,6 @@ snapshots:
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
yallist@4.0.0: {}
|
||||
|
||||
zod-to-json-schema@3.25.1(zod@3.25.76):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
+61
-55
@@ -111,21 +111,19 @@ class PlatformProvider implements Mem0Provider {
|
||||
options: AddOptions,
|
||||
): Promise<AddResult> {
|
||||
await this.ensureClient();
|
||||
const opts: Record<string, unknown> = { user_id: options.user_id };
|
||||
if (options.run_id) opts.run_id = options.run_id;
|
||||
// v3.0.0: SDK uses camelCase (userId, runId, etc.) - it converts to snake_case internally
|
||||
const opts: Record<string, unknown> = { userId: options.user_id };
|
||||
if (options.run_id) opts.runId = options.run_id;
|
||||
if (options.custom_instructions)
|
||||
opts.custom_instructions = options.custom_instructions;
|
||||
opts.customInstructions = options.custom_instructions;
|
||||
if (options.custom_categories)
|
||||
opts.custom_categories = options.custom_categories;
|
||||
if (options.output_format) opts.output_format = options.output_format;
|
||||
opts.customCategories = options.custom_categories;
|
||||
if (options.source) opts.source = options.source;
|
||||
// Agentic harness: direct storage bypass
|
||||
if (options.infer !== undefined) opts.infer = options.infer;
|
||||
if (options.deduced_memories)
|
||||
opts.deduced_memories = options.deduced_memories;
|
||||
opts.deducedMemories = options.deduced_memories;
|
||||
if (options.metadata) opts.metadata = options.metadata;
|
||||
if (options.expiration_date) opts.expiration_date = options.expiration_date;
|
||||
if (options.immutable) opts.immutable = options.immutable;
|
||||
|
||||
const result = await this.client.add(messages, opts);
|
||||
return normalizeAddResult(result);
|
||||
@@ -133,19 +131,15 @@ class PlatformProvider implements Mem0Provider {
|
||||
|
||||
async search(query: string, options: SearchOptions): Promise<MemoryItem[]> {
|
||||
await this.ensureClient();
|
||||
const opts: Record<string, unknown> = {
|
||||
api_version: "v2",
|
||||
user_id: options.user_id,
|
||||
};
|
||||
if (options.run_id) opts.run_id = options.run_id;
|
||||
if (options.top_k != null) opts.top_k = options.top_k;
|
||||
// v3.0.0: SDK uses camelCase options, userId must be in filters
|
||||
const opts: Record<string, unknown> = {};
|
||||
if (options.top_k != null) opts.topK = options.top_k;
|
||||
if (options.threshold != null) opts.threshold = options.threshold;
|
||||
if (options.keyword_search != null)
|
||||
opts.keyword_search = options.keyword_search;
|
||||
if (options.reranking != null) opts.rerank = options.reranking;
|
||||
if (options.filter_memories != null)
|
||||
opts.filter_memories = options.filter_memories;
|
||||
if (options.categories != null) opts.categories = options.categories;
|
||||
|
||||
// Build filters with user_id/run_id inside (v3.0.0 requirement)
|
||||
// Filters use snake_case as they're passed directly to the API
|
||||
// Note: source is NOT a valid filter field - only used when adding
|
||||
const baseFilters: Record<string, unknown> = { user_id: options.user_id };
|
||||
if (options.run_id) baseFilters.run_id = options.run_id;
|
||||
|
||||
@@ -167,16 +161,14 @@ class PlatformProvider implements Mem0Provider {
|
||||
|
||||
async getAll(options: ListOptions): Promise<MemoryItem[]> {
|
||||
await this.ensureClient();
|
||||
const opts: Record<string, unknown> = {
|
||||
api_version: "v2",
|
||||
user_id: options.user_id,
|
||||
filters: { user_id: options.user_id },
|
||||
};
|
||||
if (options.run_id) {
|
||||
opts.run_id = options.run_id;
|
||||
(opts.filters as Record<string, unknown>).run_id = options.run_id;
|
||||
}
|
||||
if (options.page_size != null) opts.page_size = options.page_size;
|
||||
// v3.0.0: SDK uses camelCase options, userId must be in filters
|
||||
// Filters use snake_case as they're passed directly to the API
|
||||
// Note: source is NOT a valid filter field - only used when adding
|
||||
const filters: Record<string, unknown> = { user_id: options.user_id };
|
||||
if (options.run_id) filters.run_id = options.run_id;
|
||||
|
||||
const opts: Record<string, unknown> = { filters };
|
||||
if (options.page_size != null) opts.pageSize = options.page_size;
|
||||
|
||||
const results = await this.client.getAll(opts);
|
||||
if (Array.isArray(results)) return results.map(normalizeMemoryItem);
|
||||
@@ -198,7 +190,8 @@ class PlatformProvider implements Mem0Provider {
|
||||
|
||||
async deleteAll(userId: string): Promise<void> {
|
||||
await this.ensureClient();
|
||||
await this.client.deleteAll({ user_id: userId });
|
||||
// v3.0.0: SDK uses camelCase
|
||||
await this.client.deleteAll({ userId });
|
||||
}
|
||||
|
||||
async history(memoryId: string): Promise<
|
||||
@@ -226,7 +219,7 @@ class OSSProvider implements Mem0Provider {
|
||||
|
||||
constructor(
|
||||
private readonly ossConfig?: Mem0Config["oss"],
|
||||
private readonly customPrompt?: string,
|
||||
private readonly customInstructions?: string,
|
||||
private readonly resolvePath?: (p: string) => string,
|
||||
) {}
|
||||
|
||||
@@ -241,7 +234,8 @@ class OSSProvider implements Mem0Provider {
|
||||
}
|
||||
|
||||
private _buildConfig(disableHistory = false): Record<string, unknown> {
|
||||
const config: Record<string, unknown> = { version: "v1.1" };
|
||||
// v3.0.0: removed version field
|
||||
const config: Record<string, unknown> = {};
|
||||
|
||||
const defaultEmbedder = {
|
||||
provider: "openai",
|
||||
@@ -299,7 +293,8 @@ class OSSProvider implements Mem0Provider {
|
||||
config.disableHistory = true;
|
||||
}
|
||||
|
||||
if (this.customPrompt) config.customPrompt = this.customPrompt;
|
||||
// v3.0.0: customPrompt renamed to customInstructions
|
||||
if (this.customInstructions) config.customInstructions = this.customInstructions;
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -347,7 +342,8 @@ class OSSProvider implements Mem0Provider {
|
||||
}
|
||||
}
|
||||
|
||||
await mem.getAll({ userId: "__mem0_warmup__" });
|
||||
// v3.0.0: entity IDs must be in filters, not top-level
|
||||
await mem.getAll({ filters: { user_id: "__mem0_warmup__" } });
|
||||
|
||||
this.memory = mem;
|
||||
}
|
||||
@@ -364,9 +360,7 @@ class OSSProvider implements Mem0Provider {
|
||||
// Agentic harness: direct storage bypass
|
||||
if (options.infer !== undefined) addOpts.infer = options.infer;
|
||||
if (options.metadata) addOpts.metadata = options.metadata;
|
||||
if (options.expiration_date)
|
||||
addOpts.expirationDate = options.expiration_date;
|
||||
if (options.immutable) addOpts.immutable = options.immutable;
|
||||
// v3.0.0: removed expiration_date, immutable
|
||||
|
||||
// OSS SDK doesn't support deduced_memories — when infer=false, it stores
|
||||
// raw message content directly. Rewrite messages to contain the facts so
|
||||
@@ -385,17 +379,24 @@ class OSSProvider implements Mem0Provider {
|
||||
|
||||
async search(query: string, options: SearchOptions): Promise<MemoryItem[]> {
|
||||
await this.ensureMemory();
|
||||
// OSS SDK uses camelCase: userId/runId, not user_id/run_id
|
||||
const opts: Record<string, unknown> = { userId: options.user_id };
|
||||
if (options.run_id) opts.runId = options.run_id;
|
||||
if (options.limit != null) opts.limit = options.limit;
|
||||
else if (options.top_k != null) opts.limit = options.top_k;
|
||||
if (options.keyword_search != null)
|
||||
opts.keyword_search = options.keyword_search;
|
||||
if (options.reranking != null) opts.reranking = options.reranking;
|
||||
if (options.source) opts.source = options.source;
|
||||
// v3.0.0: entity IDs must be in filters, not top-level; limit renamed to topK
|
||||
const opts: Record<string, unknown> = {};
|
||||
if (options.top_k != null) opts.topK = options.top_k;
|
||||
if (options.threshold != null) opts.threshold = options.threshold;
|
||||
|
||||
// Build filters with user_id/run_id inside (v3.0.0 requirement)
|
||||
// Filters use snake_case as they're passed directly to the vector store
|
||||
// Note: source is NOT a valid filter field - only used when adding
|
||||
const baseFilters: Record<string, unknown> = { user_id: options.user_id };
|
||||
if (options.run_id) baseFilters.run_id = options.run_id;
|
||||
|
||||
// Merge with any additional user-provided filters
|
||||
if (options.filters) {
|
||||
opts.filters = { AND: [baseFilters, options.filters] };
|
||||
} else {
|
||||
opts.filters = baseFilters;
|
||||
}
|
||||
|
||||
const results = await this.memory.search(query, opts);
|
||||
const normalized = normalizeSearchResults(results);
|
||||
|
||||
@@ -417,10 +418,16 @@ class OSSProvider implements Mem0Provider {
|
||||
|
||||
async getAll(options: ListOptions): Promise<MemoryItem[]> {
|
||||
await this.ensureMemory();
|
||||
// OSS SDK uses camelCase: userId/runId, not user_id/run_id
|
||||
const getAllOpts: Record<string, unknown> = { userId: options.user_id };
|
||||
if (options.run_id) getAllOpts.runId = options.run_id;
|
||||
if (options.source) getAllOpts.source = options.source;
|
||||
// v3.0.0: entity IDs must be in filters, not top-level
|
||||
// Filters use snake_case as they're passed directly to the vector store
|
||||
// Note: source is NOT a valid filter field - only used when adding
|
||||
const filters: Record<string, unknown> = { user_id: options.user_id };
|
||||
if (options.run_id) filters.run_id = options.run_id;
|
||||
|
||||
// OSS SDK uses topK for limiting results (not pageSize like Platform)
|
||||
const getAllOpts: Record<string, unknown> = { filters };
|
||||
if (options.page_size != null) getAllOpts.topK = options.page_size;
|
||||
|
||||
const results = await this.memory.getAll(getAllOpts);
|
||||
if (Array.isArray(results)) return results.map(normalizeMemoryItem);
|
||||
if (results?.results && Array.isArray(results.results))
|
||||
@@ -476,7 +483,8 @@ export function createProvider(
|
||||
api: OpenClawPluginApi,
|
||||
): Mem0Provider {
|
||||
if (cfg.mode === "open-source") {
|
||||
return new OSSProvider(cfg.oss, cfg.customPrompt, (p) =>
|
||||
// v3.0.0: use customInstructions (was customPrompt)
|
||||
return new OSSProvider(cfg.oss, cfg.customInstructions, (p) =>
|
||||
api.resolvePath(p),
|
||||
);
|
||||
}
|
||||
@@ -502,6 +510,7 @@ export function providerToBackend(
|
||||
return {
|
||||
async add(content, messages, opts = {}) {
|
||||
const msgs = messages ?? (content ? [{ role: "user", content }] : []);
|
||||
// v3.0.0: removed immutable, expiration_date
|
||||
const result = await provider.add(
|
||||
msgs as Array<{ role: string; content: string }>,
|
||||
{
|
||||
@@ -509,21 +518,18 @@ export function providerToBackend(
|
||||
source: "OPENCLAW",
|
||||
...(opts.runId && { run_id: opts.runId }),
|
||||
...(opts.metadata && { metadata: opts.metadata }),
|
||||
...(opts.immutable && { immutable: true }),
|
||||
...(opts.infer === false && { infer: false }),
|
||||
...(opts.expires && { expiration_date: opts.expires }),
|
||||
},
|
||||
);
|
||||
return result as unknown as Record<string, unknown>;
|
||||
},
|
||||
|
||||
async search(query, opts = {}) {
|
||||
// v3.0.0: removed keyword_search, reranking
|
||||
const results = await provider.search(query, {
|
||||
user_id: opts.userId ?? userId,
|
||||
top_k: opts.topK,
|
||||
threshold: opts.threshold,
|
||||
keyword_search: opts.keyword,
|
||||
reranking: opts.rerank,
|
||||
filters: opts.filters,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Public Artifacts Provider for OpenClaw memory-wiki bridge mode.
|
||||
*
|
||||
* Exposes Mem0 memories and dream state as artifacts that can be
|
||||
* consumed by other plugins (e.g., memory-wiki in bridge mode).
|
||||
*/
|
||||
|
||||
import type { Mem0Provider, MemoryItem, Mem0Config } from "./types.ts";
|
||||
import type { MemoryArtifact } from "openclaw/plugin-sdk";
|
||||
import { getDreamState } from "./dream-gate.ts";
|
||||
|
||||
export interface PublicArtifactsContext {
|
||||
provider: Mem0Provider;
|
||||
cfg: Mem0Config;
|
||||
stateDir?: string;
|
||||
effectiveUserId: (sessionKey?: string) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a publicArtifacts provider that exposes Mem0 data to other plugins.
|
||||
*/
|
||||
export function createPublicArtifactsProvider(ctx: PublicArtifactsContext) {
|
||||
return {
|
||||
async listArtifacts(options?: {
|
||||
userId?: string;
|
||||
types?: string[];
|
||||
limit?: number;
|
||||
}): Promise<MemoryArtifact[]> {
|
||||
const artifacts: MemoryArtifact[] = [];
|
||||
const userId = options?.userId ?? ctx.effectiveUserId();
|
||||
const types = options?.types ?? ["memory", "dream", "entity"];
|
||||
const limit = options?.limit ?? 100;
|
||||
|
||||
try {
|
||||
// Memory artifacts
|
||||
if (types.includes("memory")) {
|
||||
const memories = await ctx.provider.getAll({
|
||||
user_id: userId,
|
||||
page_size: limit,
|
||||
});
|
||||
|
||||
for (const mem of memories) {
|
||||
artifacts.push(memoryToArtifact(mem));
|
||||
}
|
||||
}
|
||||
|
||||
// Dream state artifact (if dream enabled and stateDir available)
|
||||
if (types.includes("dream") && ctx.stateDir && ctx.cfg.skills?.dream?.enabled) {
|
||||
const dreamArtifact = getDreamArtifact(ctx.stateDir, userId);
|
||||
if (dreamArtifact) {
|
||||
artifacts.push(dreamArtifact);
|
||||
}
|
||||
}
|
||||
|
||||
// Entity artifacts (grouped memories by category)
|
||||
if (types.includes("entity")) {
|
||||
const entityArtifacts = extractEntityArtifacts(artifacts.filter(a => a.type === "memory"));
|
||||
artifacts.push(...entityArtifacts);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
"[mem0] publicArtifacts.listArtifacts failed:",
|
||||
err instanceof Error ? err.message : err,
|
||||
);
|
||||
}
|
||||
|
||||
return artifacts.slice(0, limit);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a MemoryItem to a MemoryArtifact.
|
||||
*/
|
||||
function memoryToArtifact(mem: MemoryItem): MemoryArtifact {
|
||||
return {
|
||||
id: `mem0:memory:${mem.id}`,
|
||||
type: "memory",
|
||||
title: mem.memory.slice(0, 80) + (mem.memory.length > 80 ? "..." : ""),
|
||||
content: mem.memory,
|
||||
metadata: {
|
||||
score: mem.score,
|
||||
categories: mem.categories,
|
||||
user_id: mem.user_id,
|
||||
...mem.metadata,
|
||||
},
|
||||
createdAt: mem.created_at,
|
||||
updatedAt: mem.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dream consolidation state as an artifact.
|
||||
*/
|
||||
function getDreamArtifact(stateDir: string, userId: string): MemoryArtifact | null {
|
||||
try {
|
||||
const state = getDreamState(stateDir);
|
||||
if (state.lastConsolidatedAt === 0) {
|
||||
return null; // No consolidation has occurred yet
|
||||
}
|
||||
|
||||
const lastDate = new Date(state.lastConsolidatedAt).toISOString();
|
||||
return {
|
||||
id: `mem0:dream:${userId}:state`,
|
||||
type: "dream",
|
||||
title: `Dream State (last: ${lastDate.split("T")[0]})`,
|
||||
content: [
|
||||
`Last consolidation: ${lastDate}`,
|
||||
`Sessions since: ${state.sessionsSince}`,
|
||||
`Last session: ${state.lastSessionId ?? "none"}`,
|
||||
].join("\n"),
|
||||
metadata: {
|
||||
lastConsolidatedAt: state.lastConsolidatedAt,
|
||||
sessionsSince: state.sessionsSince,
|
||||
lastSessionId: state.lastSessionId,
|
||||
user_id: userId,
|
||||
},
|
||||
updatedAt: lastDate,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract entity artifacts from memories (grouped by category).
|
||||
*/
|
||||
function extractEntityArtifacts(memoryArtifacts: MemoryArtifact[]): MemoryArtifact[] {
|
||||
const byCategory = new Map<string, MemoryArtifact[]>();
|
||||
|
||||
for (const artifact of memoryArtifacts) {
|
||||
const categories = (artifact.metadata?.categories as string[]) ?? ["uncategorized"];
|
||||
for (const cat of categories) {
|
||||
const existing = byCategory.get(cat) ?? [];
|
||||
existing.push(artifact);
|
||||
byCategory.set(cat, existing);
|
||||
}
|
||||
}
|
||||
|
||||
const entities: MemoryArtifact[] = [];
|
||||
for (const [category, mems] of byCategory) {
|
||||
if (mems.length >= 2) {
|
||||
entities.push({
|
||||
id: `mem0:entity:${category}`,
|
||||
type: "entity",
|
||||
title: `${category.charAt(0).toUpperCase() + category.slice(1)} (${mems.length} memories)`,
|
||||
content: mems.map(m => `- ${m.content}`).join("\n"),
|
||||
metadata: {
|
||||
category,
|
||||
memoryCount: mems.length,
|
||||
memoryIds: mems.map(m => m.id),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
+2
-7
@@ -2,7 +2,7 @@
|
||||
* Token-budgeted, category-ranked recall engine.
|
||||
*
|
||||
* Replaces naive "dump all search results" with:
|
||||
* 1. Search with rerank + keyword_search
|
||||
* 1. Search memories with threshold filtering
|
||||
* 2. Rank by category priority (identity first)
|
||||
* 3. Token-budget the results
|
||||
* 4. Format by category with importance scores
|
||||
@@ -253,18 +253,13 @@ export async function recall(
|
||||
const categoryOrder = recallConfig.categoryOrder ?? DEFAULT_CATEGORY_ORDER;
|
||||
const identityAlwaysInclude = recallConfig.identityAlwaysInclude !== false;
|
||||
|
||||
// Build search options with enhanced features
|
||||
// Build search options (v3.0.0: keyword_search, reranking, filter_memories removed)
|
||||
const searchOpts: SearchOptions = {
|
||||
user_id: userId,
|
||||
top_k: maxMemories * 2, // Over-fetch for ranking
|
||||
threshold,
|
||||
keyword_search: recallConfig.keywordSearch !== false, // Default on
|
||||
reranking: recallConfig.rerank !== false, // Default on
|
||||
source: "OPENCLAW",
|
||||
};
|
||||
if (recallConfig.filterMemories) {
|
||||
searchOpts.filter_memories = true;
|
||||
}
|
||||
|
||||
// Sanitize query: strip OpenClaw metadata prefix before searching
|
||||
const cleanQuery = sanitizeQuery(query);
|
||||
|
||||
@@ -654,9 +654,9 @@ describe("OSSProvider — history error handling", () => {
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 9. OSSProvider: customPrompt passthrough
|
||||
// 9. OSSProvider: customInstructions passthrough (v3.0.0: renamed from customPrompt)
|
||||
// ---------------------------------------------------------------------------
|
||||
describe("OSSProvider — customPrompt passthrough", () => {
|
||||
describe("OSSProvider — customInstructions passthrough", () => {
|
||||
let capturedConfig: Record<string, unknown> | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -682,12 +682,13 @@ describe("OSSProvider — customPrompt passthrough", () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("passes customPrompt to Memory config when provided", async () => {
|
||||
// v3.0.0: customPrompt renamed to customInstructions
|
||||
it("passes customInstructions to Memory config when provided", async () => {
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: { disableHistory: true },
|
||||
customPrompt: "Extract only user preferences.",
|
||||
customInstructions: "Extract only user preferences.",
|
||||
});
|
||||
const api = { resolvePath: (p: string) => p } as any;
|
||||
const provider = createProvider(cfg, api);
|
||||
@@ -695,6 +696,6 @@ describe("OSSProvider — customPrompt passthrough", () => {
|
||||
await provider.search("test", { user_id: "u1" });
|
||||
|
||||
expect(capturedConfig).toBeDefined();
|
||||
expect(capturedConfig!.customPrompt).toBe("Extract only user preferences.");
|
||||
expect(capturedConfig!.customInstructions).toBe("Extract only user preferences.");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
*/
|
||||
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { readPluginAuth, writePluginAuth, getBaseUrl } from "./cli/config-file.ts";
|
||||
import { readPluginAuth, writePluginAuth, getBaseUrl, clearAnonymousTelemetryId } from "./cli/config-file.ts";
|
||||
|
||||
export const PLUGIN_VERSION = "1.0.6";
|
||||
export const PLUGIN_VERSION = "1.0.7";
|
||||
|
||||
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
|
||||
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
|
||||
@@ -84,8 +84,9 @@ function maybeBuildIdentifyEvent(
|
||||
$lib: "posthog-node",
|
||||
},
|
||||
};
|
||||
// Clear the anonymous ID from config after aliasing (don't write empty string)
|
||||
try {
|
||||
writePluginAuth({ anonymousTelemetryId: "" });
|
||||
clearAnonymousTelemetryId();
|
||||
} catch {
|
||||
/* ignore — alias may double-fire next session, harmless */
|
||||
}
|
||||
|
||||
@@ -113,9 +113,15 @@ describe("readPluginAuth", () => {
|
||||
expect(auth.userId).toBe("user-snake");
|
||||
});
|
||||
|
||||
it("returns empty object when JSON is invalid", () => {
|
||||
it("throws error when JSON is invalid (prevents config destruction)", () => {
|
||||
mockExists.mockReturnValue(true);
|
||||
mockReadText.mockReturnValue("not valid json {{{");
|
||||
expect(() => readPluginAuth()).toThrow(/Failed to parse[\s\S]*Fix the JSON syntax error/);
|
||||
});
|
||||
|
||||
it("returns empty object when config file is empty", () => {
|
||||
mockExists.mockReturnValue(true);
|
||||
mockReadText.mockReturnValue(" ");
|
||||
expect(readPluginAuth()).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,9 +74,10 @@ describe("mem0ConfigSchema.parse() — defaults", () => {
|
||||
expect(cfg.customCategories).toBe(DEFAULT_CUSTOM_CATEGORIES);
|
||||
});
|
||||
|
||||
it("customPrompt defaults to DEFAULT_CUSTOM_INSTRUCTIONS", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" });
|
||||
expect(cfg.customPrompt).toBe(DEFAULT_CUSTOM_INSTRUCTIONS);
|
||||
// v3.0.0: customPrompt removed, use customInstructions instead
|
||||
it("customPrompt input falls back to customInstructions", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key", customPrompt: "My prompt" });
|
||||
expect(cfg.customInstructions).toBe("My prompt");
|
||||
});
|
||||
|
||||
it("oss defaults to undefined", () => {
|
||||
@@ -296,13 +297,14 @@ describe("mem0ConfigSchema.parse() — explicit overrides", () => {
|
||||
expect(cfg.customInstructions).toBe(custom);
|
||||
});
|
||||
|
||||
it("custom customPrompt overrides defaults", () => {
|
||||
// v3.0.0: customPrompt renamed to customInstructions (backwards compat: customPrompt maps to customInstructions)
|
||||
it("customPrompt input maps to customInstructions output", () => {
|
||||
const custom = "My custom prompt";
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
apiKey: "k",
|
||||
customPrompt: custom,
|
||||
});
|
||||
expect(cfg.customPrompt).toBe(custom);
|
||||
expect(cfg.customInstructions).toBe(custom);
|
||||
});
|
||||
|
||||
it("custom customCategories override defaults", () => {
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
isNoiseMessage,
|
||||
isGenericAssistantMessage,
|
||||
isSessionSpecificContent,
|
||||
stripNoiseFromContent,
|
||||
filterMessagesForExtraction,
|
||||
} from "../filtering.ts";
|
||||
|
||||
describe("isNoiseMessage", () => {
|
||||
it("returns true for heartbeat messages", () => {
|
||||
expect(isNoiseMessage("HEARTBEAT_OK")).toBe(true);
|
||||
expect(isNoiseMessage("NO_REPLY")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for single-word acknowledgments", () => {
|
||||
expect(isNoiseMessage("ok")).toBe(true);
|
||||
expect(isNoiseMessage("done")).toBe(true);
|
||||
expect(isNoiseMessage("thanks")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for substantive content", () => {
|
||||
expect(isNoiseMessage("My name is John and I live in Tokyo")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isGenericAssistantMessage", () => {
|
||||
it("returns true for generic acknowledgments", () => {
|
||||
expect(isGenericAssistantMessage("I see you've shared this. How can I help?")).toBe(true);
|
||||
expect(isGenericAssistantMessage("Got it! How can I assist you?")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for substantive responses", () => {
|
||||
expect(isGenericAssistantMessage("Based on the code, the bug is in line 42 where the null check is missing.")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isSessionSpecificContent", () => {
|
||||
it("returns true for tool availability discussions", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"I do not currently see Mem0 write/update/delete tools exposed in this session."
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for plugin capability statements", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"As of 2026-04-18, the openclaw-mem0 plugin does not expose a memory wiki capability."
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for session-specific tool lists", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"The tools I have access to in this session are memory_search and memory_get."
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for user preferences", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"My name is Kartik and I like to watch Pokemon."
|
||||
)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for technical facts that span sessions", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"The project uses TypeScript 5.0 and Node.js 20."
|
||||
)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("filterMessagesForExtraction", () => {
|
||||
it("filters out session-specific tool discussions", () => {
|
||||
const messages = [
|
||||
{ role: "user", content: "What tools do you have access to?" },
|
||||
{ role: "assistant", content: "I have memory_search and memory_get tools. The other tools are not exposed in this session." },
|
||||
{ role: "user", content: "My name is Kartik" },
|
||||
{ role: "assistant", content: "Got it, I'll remember that!" },
|
||||
];
|
||||
|
||||
const filtered = filterMessagesForExtraction(messages);
|
||||
|
||||
// Should keep the name statement, filter the tool discussion
|
||||
expect(filtered.some(m => m.content.includes("Kartik"))).toBe(true);
|
||||
expect(filtered.some(m => m.content.includes("not exposed in this session"))).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps valuable user facts", () => {
|
||||
const messages = [
|
||||
{ role: "user", content: "I prefer dark mode and use VS Code for development." },
|
||||
{ role: "assistant", content: "Noted! Dark mode in VS Code is great for reducing eye strain." },
|
||||
];
|
||||
|
||||
const filtered = filterMessagesForExtraction(messages);
|
||||
expect(filtered.length).toBe(2);
|
||||
expect(filtered[0].content).toContain("dark mode");
|
||||
});
|
||||
});
|
||||
@@ -42,6 +42,7 @@ beforeEach(() => {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("providerToBackend — search", () => {
|
||||
// v3.0.0: keyword_search, reranking removed from SDK
|
||||
it("delegates to provider.search with correct options", async () => {
|
||||
const provider = createMockProvider();
|
||||
const backend = providerToBackend(provider as any, DEFAULT_USER);
|
||||
@@ -49,8 +50,6 @@ describe("providerToBackend — search", () => {
|
||||
const results = await backend.search("hello world", {
|
||||
topK: 10,
|
||||
threshold: 0.5,
|
||||
keyword: true,
|
||||
rerank: true,
|
||||
filters: { category: "preference" },
|
||||
});
|
||||
|
||||
@@ -58,8 +57,6 @@ describe("providerToBackend — search", () => {
|
||||
user_id: DEFAULT_USER,
|
||||
top_k: 10,
|
||||
threshold: 0.5,
|
||||
keyword_search: true,
|
||||
reranking: true,
|
||||
filters: { category: "preference" },
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
@@ -73,12 +70,11 @@ describe("providerToBackend — search", () => {
|
||||
|
||||
await backend.search("query");
|
||||
|
||||
// v3.0.0: keyword_search, reranking removed
|
||||
expect(provider.search).toHaveBeenCalledWith("query", {
|
||||
user_id: DEFAULT_USER,
|
||||
top_k: undefined,
|
||||
threshold: undefined,
|
||||
keyword_search: undefined,
|
||||
reranking: undefined,
|
||||
filters: undefined,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
@@ -119,6 +115,7 @@ describe("providerToBackend — add", () => {
|
||||
);
|
||||
});
|
||||
|
||||
// v3.0.0: immutable, expiration_date removed from SDK
|
||||
it("forwards optional add options", async () => {
|
||||
const provider = createMockProvider();
|
||||
const backend = providerToBackend(provider as any, DEFAULT_USER);
|
||||
@@ -126,9 +123,7 @@ describe("providerToBackend — add", () => {
|
||||
await backend.add("fact", undefined, {
|
||||
runId: "run-1",
|
||||
metadata: { source: "test" },
|
||||
immutable: true,
|
||||
infer: false,
|
||||
expires: "2027-01-01",
|
||||
});
|
||||
|
||||
expect(provider.add).toHaveBeenCalledWith(
|
||||
@@ -137,9 +132,7 @@ describe("providerToBackend — add", () => {
|
||||
user_id: DEFAULT_USER,
|
||||
run_id: "run-1",
|
||||
metadata: { source: "test" },
|
||||
immutable: true,
|
||||
infer: false,
|
||||
expiration_date: "2027-01-01",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("telemetry", () => {
|
||||
});
|
||||
|
||||
it("exports PLUGIN_VERSION", () => {
|
||||
expect(PLUGIN_VERSION).toBe("1.0.6");
|
||||
expect(PLUGIN_VERSION).toBe("1.0.7");
|
||||
});
|
||||
|
||||
it("captureEvent does not throw", () => {
|
||||
|
||||
@@ -37,7 +37,6 @@ function createMockToolDeps(overrides = {}): ToolDeps {
|
||||
searchThreshold: 0.5,
|
||||
customInstructions: "test",
|
||||
customCategories: {},
|
||||
customPrompt: "test",
|
||||
} as any,
|
||||
provider: {
|
||||
search: vi
|
||||
@@ -108,13 +107,14 @@ describe("registerAllTools", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("registers tools without a second argument (required, not optional)", () => {
|
||||
it("registers tools with optional: false metadata", () => {
|
||||
const ctx = createMockToolDeps();
|
||||
registerAllTools(ctx);
|
||||
|
||||
const calls = (ctx.api.registerTool as ReturnType<typeof vi.fn>).mock.calls;
|
||||
for (const call of calls) {
|
||||
expect(call).toHaveLength(1);
|
||||
expect(call).toHaveLength(2);
|
||||
expect(call[1]).toEqual({ optional: false });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,13 +28,14 @@ export interface ToolDeps {
|
||||
|
||||
export function registerAllTools(deps: ToolDeps): void {
|
||||
const { api } = deps;
|
||||
const nonOptional = { optional: false };
|
||||
|
||||
api.registerTool(createMemorySearchTool(deps));
|
||||
api.registerTool(createMemoryAddTool(deps));
|
||||
api.registerTool(createMemoryGetTool(deps));
|
||||
api.registerTool(createMemoryListTool(deps));
|
||||
api.registerTool(createMemoryUpdateTool(deps));
|
||||
api.registerTool(createMemoryDeleteTool(deps));
|
||||
api.registerTool(createMemoryEventListTool(deps));
|
||||
api.registerTool(createMemoryEventStatusTool(deps));
|
||||
api.registerTool(createMemorySearchTool(deps), nonOptional);
|
||||
api.registerTool(createMemoryAddTool(deps), nonOptional);
|
||||
api.registerTool(createMemoryGetTool(deps), nonOptional);
|
||||
api.registerTool(createMemoryListTool(deps), nonOptional);
|
||||
api.registerTool(createMemoryUpdateTool(deps), nonOptional);
|
||||
api.registerTool(createMemoryDeleteTool(deps), nonOptional);
|
||||
api.registerTool(createMemoryEventListTool(deps), nonOptional);
|
||||
api.registerTool(createMemoryEventStatusTool(deps), nonOptional);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type { AddOptions } from "../types.ts";
|
||||
import { isSubagentSession } from "../isolation.ts";
|
||||
import { resolveCategories, ttlToExpirationDate } from "../skill-loader.ts";
|
||||
import { isNoiseMessage, stripNoiseFromContent } from "../filtering.ts";
|
||||
// v3.0.0: resolveCategories/ttlToExpirationDate removed - expiration_date/immutable no longer supported
|
||||
import type { ToolDeps } from "./index.ts";
|
||||
|
||||
export function createMemoryAddTool(deps: ToolDeps) {
|
||||
const { api, cfg, provider, resolveUserId, getCurrentSessionId, buildAddOptions, buildSearchOptions, skillsActive } = deps;
|
||||
const { api, provider, resolveUserId, getCurrentSessionId, buildAddOptions, buildSearchOptions, skillsActive } = deps;
|
||||
|
||||
return {
|
||||
name: "memory_add",
|
||||
@@ -28,11 +29,20 @@ export function createMemoryAddTool(deps: ToolDeps) {
|
||||
userId?: string; agentId?: string; metadata?: Record<string, unknown>; longTerm?: boolean;
|
||||
};
|
||||
|
||||
const allFacts: string[] = p.facts?.length ? p.facts : (p.text ? [p.text] : []);
|
||||
if (allFacts.length === 0) {
|
||||
const rawFacts: string[] = p.facts?.length ? p.facts : (p.text ? [p.text] : []);
|
||||
if (rawFacts.length === 0) {
|
||||
return { content: [{ type: "text", text: "No facts provided. Pass 'text' or 'facts' array." }], details: { error: "missing_facts" } };
|
||||
}
|
||||
|
||||
// Filter out noise and clean the facts before storing
|
||||
const allFacts = rawFacts
|
||||
.map((f) => stripNoiseFromContent(f))
|
||||
.filter((f) => f.length > 0 && !isNoiseMessage(f));
|
||||
|
||||
if (allFacts.length === 0) {
|
||||
return { content: [{ type: "text", text: "All provided facts were filtered as noise. Nothing stored." }], details: { error: "all_noise" } };
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
try {
|
||||
const currentSessionId = getCurrentSessionId();
|
||||
@@ -53,21 +63,12 @@ export function createMemoryAddTool(deps: ToolDeps) {
|
||||
...(category && { category }),
|
||||
...(importance !== undefined && { importance }),
|
||||
};
|
||||
const categories = resolveCategories(cfg.skills);
|
||||
const catConfig = category ? categories[category] : undefined;
|
||||
const expirationDate = catConfig ? ttlToExpirationDate(catConfig.ttl) : undefined;
|
||||
const isImmutable = catConfig?.immutable ?? false;
|
||||
|
||||
const addOpts: AddOptions = {
|
||||
user_id: uid, source: "OPENCLAW", infer: false,
|
||||
deduced_memories: allFacts, metadata: parsedMetadata ?? {},
|
||||
...(expirationDate && { expiration_date: expirationDate }),
|
||||
...(isImmutable && { immutable: true }),
|
||||
};
|
||||
if (runId) addOpts.run_id = runId;
|
||||
if (cfg.mode === "platform") {
|
||||
addOpts.output_format = "v1.1";
|
||||
}
|
||||
|
||||
const result = await provider.add([{ role: "user", content: allFacts.join("\n") }], addOpts);
|
||||
const count = result.results?.length ?? 0;
|
||||
|
||||
@@ -16,7 +16,7 @@ export function createMemorySearchTool(deps: ToolDeps) {
|
||||
agentId: Type.Optional(Type.String({ description: "Agent ID to search a specific agent's memories" })),
|
||||
scope: Type.Optional(
|
||||
Type.Union([Type.Literal("session"), Type.Literal("long-term"), Type.Literal("all")], {
|
||||
description: 'Scope: "long-term" (default), "session", or "all"',
|
||||
description: 'Scope: "all" (default), "session", or "long-term"',
|
||||
}),
|
||||
),
|
||||
categories: Type.Optional(Type.Array(Type.String(), { description: "Filter by category" })),
|
||||
@@ -25,7 +25,7 @@ export function createMemorySearchTool(deps: ToolDeps) {
|
||||
|
||||
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
||||
const {
|
||||
query, limit, userId, agentId, scope = "long-term",
|
||||
query, limit, userId, agentId, scope = "all",
|
||||
categories: filterCategories, filters: agentFilters,
|
||||
} = params as {
|
||||
query: string; limit?: number; userId?: string; agentId?: string;
|
||||
|
||||
+2
-10
@@ -12,8 +12,7 @@ export type Mem0Config = {
|
||||
baseUrl?: string;
|
||||
customInstructions: string;
|
||||
customCategories: Record<string, string>;
|
||||
// OSS-specific
|
||||
customPrompt?: string;
|
||||
// OSS-specific (customPrompt renamed to customInstructions in v3.0.0)
|
||||
oss?: {
|
||||
embedder?: { provider: string; config: Record<string, unknown> };
|
||||
vectorStore?: { provider: string; config: Record<string, unknown> };
|
||||
@@ -37,15 +36,12 @@ export interface AddOptions {
|
||||
user_id: string;
|
||||
run_id?: string;
|
||||
custom_instructions?: string;
|
||||
custom_categories?: Array<Record<string, string>>;
|
||||
output_format?: string;
|
||||
custom_categories?: Record<string, string>;
|
||||
source?: string;
|
||||
// Agentic harness additions
|
||||
infer?: boolean;
|
||||
deduced_memories?: string[];
|
||||
metadata?: Record<string, unknown>;
|
||||
expiration_date?: string;
|
||||
immutable?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
@@ -53,10 +49,6 @@ export interface SearchOptions {
|
||||
run_id?: string;
|
||||
top_k?: number;
|
||||
threshold?: number;
|
||||
limit?: number;
|
||||
keyword_search?: boolean;
|
||||
reranking?: boolean;
|
||||
filter_memories?: boolean;
|
||||
categories?: string[];
|
||||
filters?: Record<string, unknown>;
|
||||
source?: string;
|
||||
|
||||
Reference in New Issue
Block a user