@memtensor/memos-local-openclaw-plugin 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -5
- package/README.md +180 -68
- package/dist/capture/index.d.ts +5 -7
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +72 -43
- package/dist/capture/index.js.map +1 -1
- package/dist/ingest/providers/anthropic.d.ts +2 -0
- package/dist/ingest/providers/anthropic.d.ts.map +1 -1
- package/dist/ingest/providers/anthropic.js +110 -1
- package/dist/ingest/providers/anthropic.js.map +1 -1
- package/dist/ingest/providers/bedrock.d.ts +2 -5
- package/dist/ingest/providers/bedrock.d.ts.map +1 -1
- package/dist/ingest/providers/bedrock.js +110 -6
- package/dist/ingest/providers/bedrock.js.map +1 -1
- package/dist/ingest/providers/gemini.d.ts +2 -0
- package/dist/ingest/providers/gemini.d.ts.map +1 -1
- package/dist/ingest/providers/gemini.js +106 -1
- package/dist/ingest/providers/gemini.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +9 -0
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +66 -4
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +2 -0
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +112 -1
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/task-processor.d.ts +63 -0
- package/dist/ingest/task-processor.d.ts.map +1 -0
- package/dist/ingest/task-processor.js +339 -0
- package/dist/ingest/task-processor.js.map +1 -0
- package/dist/ingest/worker.d.ts +1 -1
- package/dist/ingest/worker.d.ts.map +1 -1
- package/dist/ingest/worker.js +18 -13
- package/dist/ingest/worker.js.map +1 -1
- package/dist/recall/engine.d.ts +1 -0
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +21 -11
- package/dist/recall/engine.js.map +1 -1
- package/dist/recall/mmr.d.ts.map +1 -1
- package/dist/recall/mmr.js +3 -1
- package/dist/recall/mmr.js.map +1 -1
- package/dist/storage/sqlite.d.ts +67 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +251 -5
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/viewer/html.d.ts +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +955 -115
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +3 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +59 -1
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +221 -45
- package/openclaw.plugin.json +20 -45
- package/package.json +3 -4
- package/skill/SKILL.md +59 -0
- package/src/capture/index.ts +85 -45
- package/src/ingest/providers/anthropic.ts +128 -1
- package/src/ingest/providers/bedrock.ts +130 -6
- package/src/ingest/providers/gemini.ts +128 -1
- package/src/ingest/providers/index.ts +74 -8
- package/src/ingest/providers/openai.ts +130 -1
- package/src/ingest/task-processor.ts +380 -0
- package/src/ingest/worker.ts +21 -15
- package/src/recall/engine.ts +22 -12
- package/src/recall/mmr.ts +3 -1
- package/src/storage/sqlite.ts +298 -5
- package/src/types.ts +19 -0
- package/src/viewer/html.ts +955 -115
- package/src/viewer/server.ts +63 -1
- package/SKILL.md +0 -43
- package/www/index.html +0 -606
package/.env.example
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
# Embedding API
|
|
2
|
-
# Use any OpenAI-compatible embedding service, or leave blank for local offline model
|
|
1
|
+
# ─── Embedding API ───
|
|
2
|
+
# Use any OpenAI-compatible embedding service, or leave blank for local offline model (Xenova/all-MiniLM-L6-v2)
|
|
3
|
+
EMBEDDING_PROVIDER=openai_compatible
|
|
3
4
|
EMBEDDING_API_KEY=your-embedding-api-key
|
|
4
|
-
EMBEDDING_ENDPOINT=https://api.
|
|
5
|
-
EMBEDDING_MODEL=
|
|
5
|
+
EMBEDDING_ENDPOINT=https://your-embedding-api.com/v1
|
|
6
|
+
EMBEDDING_MODEL=bge-m3
|
|
6
7
|
|
|
7
|
-
# Summarizer API
|
|
8
|
+
# ─── Summarizer API ───
|
|
9
|
+
# OpenAI-compatible LLM for one-sentence chunk summaries
|
|
8
10
|
# Leave blank to use rule-based fallback (no LLM needed)
|
|
11
|
+
SUMMARIZER_PROVIDER=openai_compatible
|
|
9
12
|
SUMMARIZER_API_KEY=your-summarizer-api-key
|
|
10
13
|
SUMMARIZER_ENDPOINT=https://api.openai.com/v1
|
|
11
14
|
SUMMARIZER_MODEL=gpt-4o-mini
|
|
15
|
+
SUMMARIZER_TEMPERATURE=0
|
|
16
|
+
|
|
17
|
+
# ─── Memory Viewer ───
|
|
18
|
+
# Port for the web-based Memory Viewer (default: 18799)
|
|
19
|
+
# VIEWER_PORT=18799
|
package/README.md
CHANGED
|
@@ -6,12 +6,17 @@ Persistent local conversation memory for [OpenClaw](https://github.com/nicepkg/o
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Auto-capture** — Stores user, assistant, and tool messages after each agent turn
|
|
10
|
-
- **
|
|
9
|
+
- **Auto-capture** — Stores user, assistant, and tool messages after each agent turn via `agent_end` event
|
|
10
|
+
- **Content deduplication** — SHA-256 content hashing prevents duplicate chunks across repeated ingestion
|
|
11
|
+
- **Task-level organization** — Conversations auto-grouped into tasks by topic shift (LLM-judged) or 2-hour idle timeout; each completed task gets a detailed LLM summary
|
|
12
|
+
- **Task status** — `active` (in progress), `completed` (with LLM summary), `skipped` (too brief, excluded from search)
|
|
11
13
|
- **Hybrid retrieval** — FTS5 keyword + vector semantic dual-channel search with RRF fusion
|
|
12
|
-
- **LLM summarization** —
|
|
13
|
-
- **Multi-provider** — OpenAI,
|
|
14
|
-
- **
|
|
14
|
+
- **LLM summarization** — Detailed task summaries preserving code, commands, URLs, file paths; chunk-level short summaries for search indexing
|
|
15
|
+
- **Multi-provider embedding** — OpenAI-compatible, Gemini, Cohere, Voyage, Mistral, or local offline (Xenova/all-MiniLM-L6-v2)
|
|
16
|
+
- **Multi-provider summarizer** — OpenAI-compatible, Anthropic, Gemini, AWS Bedrock
|
|
17
|
+
- **Web viewer** — Built-in dashboard at `http://127.0.0.1:18799` with CRUD, search, analytics, task browser, chat-bubble chunk view
|
|
18
|
+
- **Skill-based retrieval** — Agent learns when and how to use memory tools via a bundled Skill (no system prompt injection)
|
|
19
|
+
- **i18n** — Memory Viewer supports Chinese / English with one-click toggle
|
|
15
20
|
- **Privacy first** — All data in local SQLite, no cloud uploads, password-protected viewer
|
|
16
21
|
|
|
17
22
|
## Quick Start
|
|
@@ -24,9 +29,9 @@ Persistent local conversation memory for [OpenClaw](https://github.com/nicepkg/o
|
|
|
24
29
|
openclaw plugins install @memtensor/memos-local-openclaw-plugin
|
|
25
30
|
```
|
|
26
31
|
|
|
27
|
-
The plugin is installed under `~/.openclaw/extensions
|
|
32
|
+
The plugin is installed under `~/.openclaw/extensions/memos-local` and registered as `memos-local`.
|
|
28
33
|
|
|
29
|
-
> **Important:**
|
|
34
|
+
> **Important:** The Memory Viewer starts only when the **OpenClaw gateway** is running. After install, **configure** `openclaw.json` (step 2) and **start the gateway** (step 3); the viewer will then be available at `http://127.0.0.1:18799`.
|
|
30
35
|
|
|
31
36
|
**From source (development):**
|
|
32
37
|
|
|
@@ -43,6 +48,14 @@ Add the plugin config to `~/.openclaw/openclaw.json`:
|
|
|
43
48
|
|
|
44
49
|
```jsonc
|
|
45
50
|
{
|
|
51
|
+
"agents": {
|
|
52
|
+
"defaults": {
|
|
53
|
+
// IMPORTANT: Disable OpenClaw's built-in memory to avoid conflicts
|
|
54
|
+
"memorySearch": {
|
|
55
|
+
"enabled": false
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
46
59
|
"plugins": {
|
|
47
60
|
"slots": {
|
|
48
61
|
"memory": "memos-local"
|
|
@@ -71,6 +84,8 @@ Add the plugin config to `~/.openclaw/openclaw.json`:
|
|
|
71
84
|
}
|
|
72
85
|
```
|
|
73
86
|
|
|
87
|
+
> **Critical:** You must set `agents.defaults.memorySearch.enabled` to `false`. Otherwise OpenClaw's built-in memory search runs alongside this plugin, causing duplicate retrieval and wasted tokens.
|
|
88
|
+
|
|
74
89
|
#### Embedding Provider Options
|
|
75
90
|
|
|
76
91
|
| Provider | `provider` value | Example `model` | Notes |
|
|
@@ -107,8 +122,6 @@ Use `${ENV_VAR}` placeholders in config to avoid hardcoding keys:
|
|
|
107
122
|
|
|
108
123
|
### 3. Start or Restart the Gateway
|
|
109
124
|
|
|
110
|
-
The Memory Viewer and all plugin features only run when the OpenClaw gateway is running. After installing and configuring the plugin, start (or restart) the gateway:
|
|
111
|
-
|
|
112
125
|
```bash
|
|
113
126
|
openclaw gateway stop # if already running
|
|
114
127
|
openclaw gateway install # ensure LaunchAgent is installed (macOS)
|
|
@@ -120,7 +133,6 @@ Once the gateway is up, the plugin loads and starts the Memory Viewer at `http:/
|
|
|
120
133
|
### 4. Verify Installation
|
|
121
134
|
|
|
122
135
|
```bash
|
|
123
|
-
# Check the gateway log
|
|
124
136
|
tail -20 ~/.openclaw/logs/gateway.log
|
|
125
137
|
```
|
|
126
138
|
|
|
@@ -145,73 +157,113 @@ memos-local: started (embedding: openai_compatible)
|
|
|
145
157
|
**Step C** — In a new conversation, ask the agent to recall what you discussed:
|
|
146
158
|
|
|
147
159
|
```
|
|
148
|
-
You:
|
|
149
|
-
Agent: (calls memory_search)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Step D** — Check the gateway log for ingest activity:
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
grep -E "Stored chunk|Chunked turn|Dedup" ~/.openclaw/logs/gateway.log | tail -10
|
|
160
|
+
You: 你还记得我之前让你帮我处理过什么事情吗?
|
|
161
|
+
Agent: (calls memory_search) 是的,我们之前讨论过...
|
|
156
162
|
```
|
|
157
163
|
|
|
158
|
-
|
|
164
|
+
## Retrieval Strategy
|
|
159
165
|
|
|
160
|
-
|
|
161
|
-
Chunked turn=1772459198930-839rr3 into 3 chunks
|
|
162
|
-
Stored chunk=667f289e kind=paragraph len=392 hasVec=true
|
|
163
|
-
Stored chunk=107d7f32 kind=tool_result role=tool len=210 hasVec=true
|
|
164
|
-
```
|
|
166
|
+
The plugin uses a **Skill-based** approach to teach the agent when and how to use memory tools. A `SKILL.md` file is automatically installed into the workspace skills directory (`~/.openclaw/workspace/skills/memos-local/`).
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
The Skill instructs the agent to follow a **layered retrieval strategy**:
|
|
167
169
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
npx tsx scripts/smoke-test.ts
|
|
175
|
-
```
|
|
170
|
+
1. **`memory_search`** — Lightweight search returning hit summaries + IDs. Always the first step.
|
|
171
|
+
2. **Sufficiency check** — If summaries are enough, answer directly without drilling down.
|
|
172
|
+
3. **`memory_get`** — Read the full original text of a specific hit when the summary is truncated or insufficient.
|
|
173
|
+
4. **`task_summary`** — Get the complete task-level summary when the answer may be spread across multiple turns of the same task.
|
|
174
|
+
5. **`memory_timeline`** — Expand surrounding conversation context when cause-and-effect or chronological details are needed.
|
|
175
|
+
6. **Ask user** — Only after exhausting search budget, ask the user a minimal follow-up question.
|
|
176
176
|
|
|
177
|
-
The
|
|
177
|
+
The Skill triggers when:
|
|
178
|
+
- The current context is insufficient to answer definitively
|
|
179
|
+
- The agent is about to ask the user for information (search memory first)
|
|
180
|
+
- The user references past conversations
|
|
181
|
+
- User-specific information is needed (identity, preferences, project config, etc.)
|
|
178
182
|
|
|
179
183
|
## Agent Tools
|
|
180
184
|
|
|
181
|
-
The plugin registers
|
|
185
|
+
The plugin registers 5 tools:
|
|
182
186
|
|
|
183
|
-
| Tool | Purpose |
|
|
184
|
-
|
|
185
|
-
| `memory_search` | Search memories by natural language query |
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
187
|
+
| Tool | Purpose | When to Use |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| `memory_search` | Search memories by natural language query | When current context lacks needed facts; before asking the user for information |
|
|
190
|
+
| `memory_get` | Get full original text of a memory chunk | When exact details (commands, code, errors) are needed from a specific hit |
|
|
191
|
+
| `task_summary` | Get detailed summary of a completed task | When the answer may be spread across multiple turns of the same task |
|
|
192
|
+
| `memory_timeline` | Get surrounding context for a search hit | When a search hit needs cause-and-effect or chronological context |
|
|
193
|
+
| `memory_viewer` | Get the URL of the web dashboard | When user asks where to view/manage memories |
|
|
189
194
|
|
|
190
|
-
|
|
195
|
+
### Search Parameters
|
|
196
|
+
|
|
197
|
+
| Parameter | Default | Range | Description |
|
|
198
|
+
|---|---|---|---|
|
|
199
|
+
| `query` | — | — | Natural language search query |
|
|
200
|
+
| `maxResults` | 6 | 1–20 | Maximum number of results |
|
|
201
|
+
| `minScore` | 0.45 | 0.35–1.0 | Minimum relevance score |
|
|
202
|
+
| `role` | — | `user` / `assistant` / `tool` | Filter by message role |
|
|
191
203
|
|
|
192
204
|
## Memory Viewer
|
|
193
205
|
|
|
194
|
-
Open `http://127.0.0.1:18799` in your browser
|
|
206
|
+
Open `http://127.0.0.1:18799` in your browser after starting the gateway.
|
|
207
|
+
|
|
208
|
+
**Features:**
|
|
209
|
+
- Browse all stored memories with timeline view
|
|
210
|
+
- Semantic search powered by your embedding model
|
|
211
|
+
- Create, edit, and delete memories (CRUD)
|
|
212
|
+
- Task browser with status filters (active / completed / skipped)
|
|
213
|
+
- Chat-bubble style chunk view in task details
|
|
214
|
+
- Filter by session, role (user/assistant/tool), time range
|
|
215
|
+
- Analytics dashboard — daily write/read activity charts, memory breakdown
|
|
216
|
+
- Light / Dark theme toggle
|
|
217
|
+
- Chinese / English language toggle
|
|
218
|
+
- Pagination (30 per page)
|
|
195
219
|
|
|
196
|
-
**Viewer won't open
|
|
220
|
+
**Viewer won't open?**
|
|
197
221
|
|
|
198
222
|
- The viewer is started by the plugin when the **gateway** starts. It does **not** run at install time.
|
|
199
|
-
- Ensure the gateway is running: `openclaw gateway start`
|
|
200
|
-
- Ensure the plugin is enabled in `~/.openclaw/openclaw.json
|
|
201
|
-
- Check the
|
|
202
|
-
|
|
203
|
-
- First visit: set a password (min 4 chars)
|
|
204
|
-
- Browse, search, create, edit, delete memories
|
|
205
|
-
- Filter by role (user/assistant/tool), type, time range (down to seconds)
|
|
206
|
-
- Pagination (30 per page)
|
|
223
|
+
- Ensure the gateway is running: `openclaw gateway start`
|
|
224
|
+
- Ensure the plugin is enabled in `~/.openclaw/openclaw.json`
|
|
225
|
+
- Check the log: `tail -30 ~/.openclaw/logs/gateway.log` — look for `MemOS Memory Viewer`
|
|
207
226
|
|
|
208
|
-
**Forgot password?** Click "Forgot password?" on the login page and use the reset token
|
|
227
|
+
**Forgot password?** Click "Forgot password?" on the login page and use the reset token:
|
|
209
228
|
|
|
210
229
|
```bash
|
|
211
|
-
|
|
212
|
-
|
|
230
|
+
grep "password reset token:" ~/.openclaw/logs/gateway.log 2>/dev/null | tail -1
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Copy the 32-character hex string after `password reset token:`.
|
|
234
|
+
|
|
235
|
+
## How It Works
|
|
236
|
+
|
|
237
|
+
**Write path** (automatic on every agent turn via `agent_end` event):
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
Conversation → Capture (filter roles, strip system prompts)
|
|
241
|
+
→ Content hash dedup check → Store chunk (SQLite + FTS5 + Embed)
|
|
242
|
+
→ Associate to Task → Task boundary detected?
|
|
243
|
+
→ Yes: Finalize previous task
|
|
244
|
+
→ Chunks ≥ 4 & turns ≥ 2: LLM summarize → status = "completed"
|
|
245
|
+
→ Otherwise: status = "skipped" (excluded from search)
|
|
246
|
+
→ No: Continue accumulating in current task
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Key details:
|
|
250
|
+
- System messages are skipped; tool results from the plugin's own tools are not re-stored
|
|
251
|
+
- Evidence wrapper blocks (`[STORED_MEMORY]...[/STORED_MEMORY]`) are stripped to prevent feedback loops
|
|
252
|
+
- Content hash (SHA-256, first 16 hex chars) prevents duplicate chunk ingestion within the same session+role
|
|
253
|
+
- Task boundaries are detected by LLM topic judgment or 2-hour idle timeout
|
|
254
|
+
|
|
255
|
+
**Read path** (when agent calls `memory_search`):
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
Query → FTS5 + Vector dual recall → RRF Fusion → MMR Rerank
|
|
259
|
+
→ Recency Decay → Score Filter → Top-K Results
|
|
213
260
|
```
|
|
214
|
-
|
|
261
|
+
|
|
262
|
+
Key details:
|
|
263
|
+
- **RRF (Reciprocal Rank Fusion):** Merges FTS5 and vector search rankings into a unified score
|
|
264
|
+
- **MMR (Maximal Marginal Relevance):** Re-ranks to balance relevance with diversity, preventing redundant results
|
|
265
|
+
- **Recency Decay:** Recent memories get a boost (half-life: 14 days by default)
|
|
266
|
+
- **Score Filter:** Relative threshold based on the top hit's score, preventing over-filtering with RRF's compressed score range
|
|
215
267
|
|
|
216
268
|
## Advanced Configuration
|
|
217
269
|
|
|
@@ -237,34 +289,94 @@ All optional — shown with defaults:
|
|
|
237
289
|
}
|
|
238
290
|
```
|
|
239
291
|
|
|
240
|
-
##
|
|
292
|
+
## Reinstall / Upgrade
|
|
241
293
|
|
|
242
|
-
**
|
|
294
|
+
If you see **"plugin already exists"** or **"plugin not found"**:
|
|
243
295
|
|
|
296
|
+
**Option A — Clean reinstall via OpenClaw CLI:**
|
|
297
|
+
```bash
|
|
298
|
+
rm -rf ~/.openclaw/extensions/memos-local
|
|
299
|
+
openclaw plugins install @memtensor/memos-local-openclaw-plugin
|
|
300
|
+
cd ~/.openclaw/extensions/memos-local && npm install --omit=dev
|
|
301
|
+
openclaw gateway stop && openclaw gateway start
|
|
244
302
|
```
|
|
245
|
-
|
|
246
|
-
|
|
303
|
+
|
|
304
|
+
**Option B — Manual install (when config already references memos-local):**
|
|
305
|
+
```bash
|
|
306
|
+
rm -rf ~/.openclaw/extensions/memos-local
|
|
307
|
+
cd /tmp
|
|
308
|
+
npm pack @memtensor/memos-local-openclaw-plugin
|
|
309
|
+
tar -xzf memtensor-memos-local-openclaw-plugin-*.tgz
|
|
310
|
+
mv package ~/.openclaw/extensions/memos-local
|
|
311
|
+
cd ~/.openclaw/extensions/memos-local && npm install --omit=dev
|
|
312
|
+
openclaw gateway stop && openclaw gateway start
|
|
247
313
|
```
|
|
248
314
|
|
|
249
|
-
**
|
|
315
|
+
**Plugin shows as "error" in `openclaw plugins list`?** (e.g. `Cannot find module '@sinclair/typebox'`)
|
|
250
316
|
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
→ Recency Decay → Score Filter → Top-K Results
|
|
317
|
+
```bash
|
|
318
|
+
cd ~/.openclaw/extensions/memos-local && npm install --omit=dev
|
|
254
319
|
```
|
|
255
320
|
|
|
256
|
-
|
|
321
|
+
Then restart the gateway.
|
|
257
322
|
|
|
258
|
-
##
|
|
323
|
+
## Troubleshooting
|
|
259
324
|
|
|
260
|
-
|
|
325
|
+
### 常见问题排查
|
|
326
|
+
|
|
327
|
+
1. **确认报错内容** — 记下完整报错,例如:`plugin not found`、`Cannot find module 'xxx'`、`Invalid config` 等。
|
|
328
|
+
|
|
329
|
+
2. **看插件状态**
|
|
330
|
+
```bash
|
|
331
|
+
openclaw plugins list
|
|
332
|
+
```
|
|
333
|
+
- Status 为 **error** → 记下错误信息
|
|
334
|
+
- 列表中没有 MemOS Local → 未安装或未放到 `~/.openclaw/extensions/memos-local`
|
|
335
|
+
|
|
336
|
+
3. **看网关日志**
|
|
337
|
+
```bash
|
|
338
|
+
tail -50 ~/.openclaw/logs/gateway.log
|
|
339
|
+
```
|
|
340
|
+
搜索 `memos-local`、`failed to load`、`Error`、`Cannot find module`。
|
|
341
|
+
|
|
342
|
+
4. **检查环境**
|
|
343
|
+
- Node 版本:`node -v`(需要 **>= 18**)
|
|
344
|
+
- 插件目录存在:`ls ~/.openclaw/extensions/memos-local/package.json`
|
|
345
|
+
- 依赖已安装:`ls ~/.openclaw/extensions/memos-local/node_modules/@sinclair/typebox`
|
|
346
|
+
若不存在:`cd ~/.openclaw/extensions/memos-local && npm install --omit=dev`
|
|
347
|
+
|
|
348
|
+
5. **检查配置** — 打开 `~/.openclaw/openclaw.json`,确认:
|
|
349
|
+
- `agents.defaults.memorySearch.enabled` = `false`(关闭内置记忆)
|
|
350
|
+
- `plugins.slots.memory` = `"memos-local"`
|
|
351
|
+
- `plugins.entries.memos-local.enabled` = `true`
|
|
352
|
+
|
|
353
|
+
6. **记忆和 OpenClaw 内置记忆冲突** — 如果看到 agent 同时调用了内置的 memory 搜索和插件的 `memory_search`,说明 `agents.defaults.memorySearch.enabled` 没有设为 `false`。
|
|
354
|
+
|
|
355
|
+
## Data Location
|
|
261
356
|
|
|
262
357
|
| File | Path |
|
|
263
358
|
|---|---|
|
|
264
359
|
| Database | `~/.openclaw/memos-local/memos.db` |
|
|
265
360
|
| Viewer auth | `~/.openclaw/memos-local/viewer-auth.json` |
|
|
266
361
|
| Gateway log | `~/.openclaw/logs/gateway.log` |
|
|
267
|
-
| Plugin code
|
|
362
|
+
| Plugin code | `~/.openclaw/extensions/memos-local/` |
|
|
363
|
+
| Skill | `~/.openclaw/workspace/skills/memos-local/SKILL.md` |
|
|
364
|
+
|
|
365
|
+
## Testing
|
|
366
|
+
|
|
367
|
+
Run the test suite:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
cd MemOS/apps/memos-local-openclaw
|
|
371
|
+
npm test
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Test coverage includes:
|
|
375
|
+
- **Policy tests** — Verifies retrieval strategy, search filtering, evidence extraction, instruction stripping
|
|
376
|
+
- **Recall tests** — RRF fusion, recency decay correctness
|
|
377
|
+
- **Capture tests** — Message filtering, evidence block stripping, self-tool exclusion
|
|
378
|
+
- **Storage tests** — SQLite CRUD, FTS5, vector storage, content hash dedup
|
|
379
|
+
- **Task processor tests** — Task boundary detection, skip logic, summary generation
|
|
268
380
|
|
|
269
381
|
## License
|
|
270
382
|
|
package/dist/capture/index.d.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import type { ConversationMessage, Logger } from "../types";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Extract writable messages from a conversation turn.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - Truncate long tool results to avoid storage bloat
|
|
9
|
-
* - Strip injected evidence blocks wrapped in [STORED_MEMORY]...[/STORED_MEMORY]
|
|
5
|
+
* Stores the user's actual text — strips only OpenClaw's injected metadata
|
|
6
|
+
* prefixes (Sender info, conversation context, etc.) which are not user content.
|
|
7
|
+
* Only skips: system prompts and our own memory tool results (prevents loop).
|
|
10
8
|
*/
|
|
11
9
|
export declare function captureMessages(messages: Array<{
|
|
12
10
|
role: string;
|
|
13
11
|
content: string;
|
|
14
12
|
toolName?: string;
|
|
15
|
-
}>, sessionKey: string, turnId: string,
|
|
13
|
+
}>, sessionKey: string, turnId: string, _evidenceTag: string, log: Logger): ConversationMessage[];
|
|
16
14
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/capture/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAQ,MAAM,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/capture/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAQ,MAAM,EAAE,MAAM,UAAU,CAAC;AA0BlE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACrE,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,GACV,mBAAmB,EAAE,CAgCvB"}
|
package/dist/capture/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.captureMessages = captureMessages;
|
|
4
|
-
const types_1 = require("../types");
|
|
5
4
|
const SKIP_ROLES = new Set(["system"]);
|
|
6
5
|
const SELF_TOOLS = new Set([
|
|
7
6
|
"memory_search",
|
|
@@ -9,16 +8,25 @@ const SELF_TOOLS = new Set([
|
|
|
9
8
|
"memory_get",
|
|
10
9
|
"memory_viewer",
|
|
11
10
|
]);
|
|
11
|
+
// OpenClaw inbound metadata sentinels — these are AI-facing prefixes,
|
|
12
|
+
// not user content. Must be stripped before storing as memory.
|
|
13
|
+
const INBOUND_META_SENTINELS = [
|
|
14
|
+
"Conversation info (untrusted metadata):",
|
|
15
|
+
"Sender (untrusted metadata):",
|
|
16
|
+
"Thread starter (untrusted, for context):",
|
|
17
|
+
"Replied message (untrusted, for context):",
|
|
18
|
+
"Forwarded message context (untrusted metadata):",
|
|
19
|
+
"Chat history since last reply (untrusted, for context):",
|
|
20
|
+
];
|
|
21
|
+
const SENTINEL_FAST_RE = new RegExp(INBOUND_META_SENTINELS.map(s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"));
|
|
12
22
|
/**
|
|
13
|
-
*
|
|
23
|
+
* Extract writable messages from a conversation turn.
|
|
14
24
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - Truncate long tool results to avoid storage bloat
|
|
19
|
-
* - Strip injected evidence blocks wrapped in [STORED_MEMORY]...[/STORED_MEMORY]
|
|
25
|
+
* Stores the user's actual text — strips only OpenClaw's injected metadata
|
|
26
|
+
* prefixes (Sender info, conversation context, etc.) which are not user content.
|
|
27
|
+
* Only skips: system prompts and our own memory tool results (prevents loop).
|
|
20
28
|
*/
|
|
21
|
-
function captureMessages(messages, sessionKey, turnId,
|
|
29
|
+
function captureMessages(messages, sessionKey, turnId, _evidenceTag, log) {
|
|
22
30
|
const now = Date.now();
|
|
23
31
|
const result = [];
|
|
24
32
|
for (const msg of messages) {
|
|
@@ -27,54 +35,75 @@ function captureMessages(messages, sessionKey, turnId, evidenceTag, log) {
|
|
|
27
35
|
continue;
|
|
28
36
|
if (!msg.content || msg.content.trim().length === 0)
|
|
29
37
|
continue;
|
|
30
|
-
if (role === "tool") {
|
|
31
|
-
|
|
32
|
-
log.debug(`Skipping self-tool result: ${msg.toolName}`);
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
let content = msg.content.trim();
|
|
36
|
-
const maxChars = types_1.DEFAULTS.toolResultMaxChars;
|
|
37
|
-
if (content.length > maxChars) {
|
|
38
|
-
content = content.slice(0, maxChars) + `\n\n[truncated — original ${content.length} chars]`;
|
|
39
|
-
}
|
|
40
|
-
const toolLabel = msg.toolName ? `[tool:${msg.toolName}] ` : "[tool] ";
|
|
41
|
-
result.push({
|
|
42
|
-
role: "tool",
|
|
43
|
-
content: toolLabel + content,
|
|
44
|
-
timestamp: now,
|
|
45
|
-
turnId,
|
|
46
|
-
sessionKey,
|
|
47
|
-
toolName: msg.toolName,
|
|
48
|
-
});
|
|
38
|
+
if (role === "tool" && msg.toolName && SELF_TOOLS.has(msg.toolName)) {
|
|
39
|
+
log.debug(`Skipping self-tool result: ${msg.toolName}`);
|
|
49
40
|
continue;
|
|
50
41
|
}
|
|
51
|
-
|
|
52
|
-
if (
|
|
42
|
+
let content = msg.content;
|
|
43
|
+
if (role === "user") {
|
|
44
|
+
content = stripInboundMetadata(content);
|
|
45
|
+
}
|
|
46
|
+
if (!content.trim())
|
|
53
47
|
continue;
|
|
54
48
|
result.push({
|
|
55
49
|
role,
|
|
56
|
-
content
|
|
50
|
+
content,
|
|
57
51
|
timestamp: now,
|
|
58
52
|
turnId,
|
|
59
53
|
sessionKey,
|
|
54
|
+
toolName: role === "tool" ? msg.toolName : undefined,
|
|
60
55
|
});
|
|
61
56
|
}
|
|
62
57
|
log.debug(`Captured ${result.length}/${messages.length} messages for session=${sessionKey} turn=${turnId}`);
|
|
63
58
|
return result;
|
|
64
59
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Strip OpenClaw-injected inbound metadata blocks from user messages.
|
|
62
|
+
*
|
|
63
|
+
* These blocks have the shape:
|
|
64
|
+
* Sender (untrusted metadata):
|
|
65
|
+
* ```json
|
|
66
|
+
* { "label": "...", "id": "..." }
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* Also strips the envelope timestamp prefix like "[Tue 2026-03-03 21:58 GMT+8] "
|
|
70
|
+
*/
|
|
71
|
+
function stripInboundMetadata(text) {
|
|
72
|
+
if (!SENTINEL_FAST_RE.test(text))
|
|
73
|
+
return text;
|
|
74
|
+
const lines = text.split("\n");
|
|
75
|
+
const result = [];
|
|
76
|
+
let inMetaBlock = false;
|
|
77
|
+
let inFencedJson = false;
|
|
78
|
+
for (let i = 0; i < lines.length; i++) {
|
|
79
|
+
const line = lines[i];
|
|
80
|
+
const trimmed = line.trim();
|
|
81
|
+
if (!inMetaBlock && INBOUND_META_SENTINELS.some(s => s === trimmed)) {
|
|
82
|
+
if (lines[i + 1]?.trim() === "```json") {
|
|
83
|
+
inMetaBlock = true;
|
|
84
|
+
inFencedJson = false;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// Sentinel without fenced JSON — skip this line only
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (inMetaBlock) {
|
|
91
|
+
if (!inFencedJson && trimmed === "```json") {
|
|
92
|
+
inFencedJson = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (inFencedJson && trimmed === "```") {
|
|
96
|
+
inMetaBlock = false;
|
|
97
|
+
inFencedJson = false;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
result.push(line);
|
|
77
103
|
}
|
|
78
|
-
|
|
104
|
+
let cleaned = result.join("\n").trim();
|
|
105
|
+
// Strip envelope timestamp prefix: "[Tue 2026-03-03 21:58 GMT+8] actual message"
|
|
106
|
+
cleaned = cleaned.replace(/^\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?::\d{2})?\s+[A-Z]{3}[+-]\d{1,2}\]\s*/, "");
|
|
107
|
+
return cleaned;
|
|
79
108
|
}
|
|
80
109
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/capture/index.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/capture/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { SummarizerConfig, Logger } from "../../types";
|
|
2
|
+
export declare function summarizeTaskAnthropic(text: string, cfg: SummarizerConfig, log: Logger): Promise<string>;
|
|
3
|
+
export declare function judgeNewTopicAnthropic(currentContext: string, newMessage: string, cfg: SummarizerConfig, log: Logger): Promise<boolean>;
|
|
2
4
|
export declare function summarizeAnthropic(text: string, cfg: SummarizerConfig, log: Logger): Promise<string>;
|
|
3
5
|
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/ingest/providers/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/ingest/providers/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAyC5D,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CA8BjB;AAeD,wBAAsB,sBAAsB,CAC1C,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAkClB;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
|