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:
Kartik
2026-04-20 20:09:45 +05:30
committed by GitHub
parent 5520226b5b
commit 4e611e8dba
31 changed files with 1531 additions and 365 deletions
+13
View File
@@ -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
View File
@@ -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
+134 -12
View File
@@ -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"]);
});
});
+2
View File
@@ -1,3 +1,5 @@
node_modules/
package-lock.json
*.db
.claude/
.claude-*/
+97 -21
View File
@@ -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"
}
}
}
}
}
```
-3
View File
@@ -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[];
}
-3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+26
View File
@@ -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;
}
}
+13 -3
View File
@@ -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"
}
}
},
+5 -2
View File
@@ -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": {
+338 -118
View File
@@ -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
View File
@@ -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",
});
+159
View File
@@ -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
View File
@@ -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);
+6 -5
View File
@@ -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.");
});
});
+4 -3
View File
@@ -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 */
}
+7 -1
View File
@@ -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({});
});
});
+7 -5
View File
@@ -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", () => {
+96
View File
@@ -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");
});
});
+3 -10
View File
@@ -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",
}),
);
});
+1 -1
View File
@@ -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", () => {
+3 -3
View File
@@ -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 });
}
});
});
+9 -8
View File
@@ -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);
}
+14 -13
View File
@@ -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;
+2 -2
View File
@@ -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
View File
@@ -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;