openclaw-amem 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -15
- package/dist/index.js +72 -28
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
**Memory for [OpenClaw](https://github.com/openclaw/openclaw) agents that catches its own contradictions.**
|
|
12
12
|
|
|
13
|
-
A nightly pass re-reads what changed and flags memories that no longer agree with each other
|
|
13
|
+
A nightly pass re-reads what changed and flags memories that no longer agree with each other. This is the failure mode every long-lived memory store eventually has. The plugin also extracts facts instead of storing transcripts. It rewrites notes as new ones arrive, links them into a Zettelkasten-style graph, and retrieves them with hybrid BM25 + dense search over 2-hop expansion. The plugin scopes memories per agent (`owner`/`readers`/`writers`) and per subject, and enforces both inside the Qdrant query rather than filtering after it.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
The plugin runs on a small model — `gpt-4o-mini`, `haiku`, a local Ollama. It does not require Python.
|
|
16
16
|
|
|
17
17
|
中文走 [jieba](https://github.com/messense/node-jieba) 分词,提示词有完整中文版(`AMEM_PROMPT_LOCALE=zh`),embedding 模型本身多语言。
|
|
18
18
|
|
|
@@ -26,9 +26,9 @@ Requires a local [Qdrant](https://qdrant.tech). Implements [A-MEM](https://arxiv
|
|
|
26
26
|
|
|
27
27
|
- 🔄 **Memories evolve** — new facts update/link related memories (EVOLVE / CONFLICT / EXPAND / NEW), not silent overwrite.
|
|
28
28
|
- 🔍 **Hybrid retrieval** — BM25 (Jieba for Chinese) + dense vectors (RRF) + 2-hop graph expansion with a relevance gate.
|
|
29
|
-
- 🧠 **Knowledge vs episodic** — durable knowledge notes skip consolidation & time-decay
|
|
29
|
+
- 🧠 **Knowledge vs episodic** — durable knowledge notes skip consolidation & time-decay. Topic tags for precise recall.
|
|
30
30
|
- 🧹 **Self-consolidating** — daily 02:30 in-process merge of semantic duplicates with link cascading.
|
|
31
|
-
- 🔐 **Per-agent isolation** — private by default
|
|
31
|
+
- 🔐 **Per-agent isolation** — private by default. Explicit `owner`/`readers`/`writers`. Mode A (shared collection) or Mode B (dedicated collection).
|
|
32
32
|
- 🀄 **Chinese-optimized** & local embeddings (Transformers.js) — no Python, no external embedding API.
|
|
33
33
|
|
|
34
34
|
→ Full feature list & internals: **[@amemhq/core README](../amem-core)** · **[docs](https://amem.owo.lc)**.
|
|
@@ -36,7 +36,7 @@ Requires a local [Qdrant](https://qdrant.tech). Implements [A-MEM](https://arxiv
|
|
|
36
36
|
## Requirements
|
|
37
37
|
|
|
38
38
|
- OpenClaw v2026.4+
|
|
39
|
-
- Node.js 24 (18+ works
|
|
39
|
+
- Node.js 24 (18+ works, 24/26 supported)
|
|
40
40
|
- Qdrant running on `:6333`
|
|
41
41
|
- An LLM: `ANTHROPIC_API_KEY` by default, or any OpenAI-compatible provider — see [LLM provider](#llm-provider)
|
|
42
42
|
- ~1.1 GB of disk for the embedding model, downloaded once on first run
|
|
@@ -61,12 +61,11 @@ Updating:
|
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
63
|
openclaw plugins update openclaw-amem
|
|
64
|
-
openclaw gateway restart
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
plugin directory. Set that once and updates cost nothing.
|
|
66
|
+
The gateway does not load the new build until it restarts. Run `openclaw gateway restart` after the update.
|
|
67
|
+
|
|
68
|
+
An update rebuilds `node_modules`. The model cache lives there by default, so the plugin re-downloads 2.27 GB unless `AMEM_MODEL_CACHE` points somewhere outside the plugin directory. Set that once and updates cost nothing.
|
|
70
69
|
|
|
71
70
|
### 2. Configure `~/.openclaw/openclaw.json`
|
|
72
71
|
|
|
@@ -92,7 +91,7 @@ Add `openclaw-amem` to your allowed plugins and hook it into the `memory` slot:
|
|
|
92
91
|
}
|
|
93
92
|
```
|
|
94
93
|
|
|
95
|
-
> **⚠️ Memory slot conflict:** If your `openclaw.json` already assigns the `memory` slot to another plugin (
|
|
94
|
+
> **⚠️ Memory slot conflict:** If your `openclaw.json` already assigns the `memory` slot to another plugin (for example, `memory-core`), you **must** change it to `openclaw-amem`. On OpenClaw 2026.8.1 the slot alone is not enough. The other plugin still loads and takes the `memory_search` tool name, because the gateway loads built-in plugins before installed ones. Memory keeps working through the slot, but the tool an agent calls is then the other plugin's. Set `"memory-core": { "enabled": false }` in `entries` as well. Note that `memory-core` also writes its dream diary, and disabling it stops that too.
|
|
96
95
|
|
|
97
96
|
### 3. Restart OpenClaw
|
|
98
97
|
|
|
@@ -103,11 +102,11 @@ openclaw gateway restart
|
|
|
103
102
|
First run downloads the embedding model (`bge-m3`, 2.27 GB) and caches it. Later
|
|
104
103
|
restarts are instant. If that is too much, the only smaller model worth setting is
|
|
105
104
|
`AMEM_EMBED_MODEL=Xenova/bge-small-zh-v1.5` — 25 MB, Chinese only, and it caps at
|
|
106
|
-
512 tokens. Set it **before** you have memories,
|
|
105
|
+
512 tokens. Set it **before** you have memories, because changing it afterwards means
|
|
107
106
|
a [migration](https://amem.owo.lc/reference/embedding-models#changing-the-model-on-a-store-you-already-have).
|
|
108
107
|
|
|
109
108
|
Upgrading from 1.x downloads nothing. Your existing memories keep the model that
|
|
110
|
-
built them, and the plugin
|
|
109
|
+
built them, and the plugin shows this at startup along with the one command that
|
|
111
110
|
moves them.
|
|
112
111
|
|
|
113
112
|
## LLM provider
|
|
@@ -115,7 +114,7 @@ moves them.
|
|
|
115
114
|
The plugin calls an LLM for note construction, linking, and evolution. Pick the backend with `AMEM_LLM_PROVIDER`:
|
|
116
115
|
|
|
117
116
|
- **`anthropic`** (default) — the Anthropic Messages API. Set `ANTHROPIC_API_KEY`.
|
|
118
|
-
- **`openai`** — the OpenAI Chat Completions API, which every OpenAI-compatible endpoint speaks. Set `AMEM_LLM_PROVIDER=openai`, point `AMEM_LLM_BASE_URL` at the endpoint, and set `AMEM_LLM_API_KEY` (or the standard `OPENAI_API_KEY`). Covers **OpenAI, DeepSeek, OpenRouter, Groq, Together**, and local servers (**Ollama, vLLM, LM Studio** — no key needed).
|
|
117
|
+
- **`openai`** — the OpenAI Chat Completions API, which every OpenAI-compatible endpoint speaks. Set `AMEM_LLM_PROVIDER=openai`, point `AMEM_LLM_BASE_URL` at the endpoint, and set `AMEM_LLM_API_KEY` (or the standard `OPENAI_API_KEY`). Covers **OpenAI, DeepSeek, OpenRouter, Groq, Together**, and local servers (**Ollama, vLLM, LM Studio** — no key needed). The plugin handles reasoning models (`o1`, `o3`, `gpt-5`) automatically.
|
|
119
118
|
|
|
120
119
|
Choose the model with `AMEM_LLM_MODEL`. Full env-var reference and examples: **[amem.owo.lc/reference/configuration](https://amem.owo.lc/reference/configuration)**.
|
|
121
120
|
|
|
@@ -142,13 +141,13 @@ Once installed, the plugin exposes five tools to the agent:
|
|
|
142
141
|
|
|
143
142
|
## Security & data flow
|
|
144
143
|
|
|
145
|
-
A memory plugin's job is to read configuration from the environment and send memory data to backends **you** control, so registry static scanners flag its `env` + `network` pattern (
|
|
144
|
+
A memory plugin's job is to read configuration from the environment and send memory data to backends **you** control, so registry static scanners flag its `env` + `network` pattern (for example, ClawHub's `suspicious.env_credential_access`). This is structurally expected for any configurable memory/LLM plugin. The audit outcome is **advisory (`Review`), not `Malicious`**, and VirusTotal reports the bundle clean.
|
|
146
145
|
|
|
147
146
|
What it actually does — all of it declared in [`openclaw.plugin.json`](openclaw.plugin.json):
|
|
148
147
|
|
|
149
148
|
- **Environment variables it reads** (its configuration surface, supplied by you): `AMEM_LLM_PROVIDER`, `AMEM_LLM_API_KEY`, `OPENAI_API_KEY`, `AMEM_LLM_BASE_URL`, `AMEM_LLM_MODEL`, `AMEM_COLLECTION`, `AMEM_DATA_DIR`, `AMEM_EVO_COUNTER_PATH`, `AMEM_REVIEW_DIR`, `AMEM_PROMPT_LOCALE`. No credential is bundled, hardcoded, or logged.
|
|
150
149
|
- **Network destinations**: only your **local Qdrant** (`http://localhost:6333`) and your configured **LLM endpoint** (Anthropic by default, or any OpenAI-compatible endpoint via `AMEM_LLM_PROVIDER=openai` + `AMEM_LLM_BASE_URL`). It sends memory text/embeddings there to store and evolve notes — its stated purpose. It does not phone home.
|
|
151
|
-
- **
|
|
150
|
+
- The plugin processes **conversation content** for memory only when you set `hooks.allowConversationAccess: true`. Keep Qdrant and review-output paths scoped to locations you control.
|
|
152
151
|
|
|
153
152
|
## Development
|
|
154
153
|
|
package/dist/index.js
CHANGED
|
@@ -38,17 +38,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
38
38
|
// ../amem-core/src/config.ts
|
|
39
39
|
function configure(opts) {
|
|
40
40
|
if (opts.dataDir) _dataDir = opts.dataDir;
|
|
41
|
+
if (opts.warn) _warn = opts.warn;
|
|
41
42
|
}
|
|
42
43
|
function getDataDir() {
|
|
43
44
|
return _dataDir;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
+
function warn(msg) {
|
|
47
|
+
_warn(msg);
|
|
48
|
+
}
|
|
49
|
+
var os, path, _dataDir, _warn;
|
|
46
50
|
var init_config = __esm({
|
|
47
51
|
"../amem-core/src/config.ts"() {
|
|
48
52
|
"use strict";
|
|
49
53
|
os = __toESM(require("os"), 1);
|
|
50
54
|
path = __toESM(require("path"), 1);
|
|
51
55
|
_dataDir = process.env.AMEM_DATA_DIR || path.join(os.homedir(), ".amem");
|
|
56
|
+
_warn = (msg) => console.warn(msg);
|
|
52
57
|
}
|
|
53
58
|
});
|
|
54
59
|
|
|
@@ -87,6 +92,12 @@ function applyModelPaths(env) {
|
|
|
87
92
|
env.allowLocalModels = true;
|
|
88
93
|
}
|
|
89
94
|
}
|
|
95
|
+
function humanBytes(n) {
|
|
96
|
+
if (n >= 1e9) return `${(n / 1e9).toFixed(2)} GB`;
|
|
97
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)} MB`;
|
|
98
|
+
if (n >= 1e3) return `${Math.round(n / 1e3)} kB`;
|
|
99
|
+
return `${n} B`;
|
|
100
|
+
}
|
|
90
101
|
function makeProgressReporter() {
|
|
91
102
|
const lastPct = /* @__PURE__ */ new Map();
|
|
92
103
|
return (e) => {
|
|
@@ -95,7 +106,7 @@ function makeProgressReporter() {
|
|
|
95
106
|
const pct = Math.floor(e.progress / 10) * 10;
|
|
96
107
|
if (lastPct.get(e.file) === pct) return;
|
|
97
108
|
lastPct.set(e.file, pct);
|
|
98
|
-
const size = e.total ? ` of ${(e.total
|
|
109
|
+
const size = e.total ? ` of ${humanBytes(e.total)}` : "";
|
|
99
110
|
console.log(`[amem] downloading ${e.file}: ${pct}%${size}`);
|
|
100
111
|
};
|
|
101
112
|
}
|
|
@@ -294,7 +305,7 @@ async function ensureCollection(collectionName) {
|
|
|
294
305
|
if (inUse !== null && inUse !== wanted) throw new MixedEmbeddingModelsError(col, wanted, inUse);
|
|
295
306
|
pinEmbeddingModel(wanted);
|
|
296
307
|
if (wanted === LEGACY_DEFAULT_EMBEDDING_MODEL) {
|
|
297
|
-
|
|
308
|
+
warn(
|
|
298
309
|
`[amem] "${col}" is on ${LEGACY_DEFAULT_EMBEDDING_MODEL} (${LEGACY_DEFAULT_DIM}-dim).
|
|
299
310
|
[amem] ${DEFAULT_EMBEDDING_MODEL} reads 8192 tokens where that one stops at 128, so anything longer is being truncated before it reaches the vector.
|
|
300
311
|
[amem] To move: npx --package=@amemhq/core amem-migrate --from-collection ${col}`
|
|
@@ -698,7 +709,7 @@ function makeCrud(collectionName, modeBIsolated = false) {
|
|
|
698
709
|
})
|
|
699
710
|
)
|
|
700
711
|
]).catch((err) => {
|
|
701
|
-
|
|
712
|
+
warn(`[amem] retrieval tracking patch failed: ${err.message}`);
|
|
702
713
|
});
|
|
703
714
|
for (const r of queryResults) {
|
|
704
715
|
r.note.retrieval_count = (r.note.retrieval_count || 0) + 1;
|
|
@@ -818,6 +829,7 @@ var init_storage = __esm({
|
|
|
818
829
|
"../amem-core/src/storage.ts"() {
|
|
819
830
|
"use strict";
|
|
820
831
|
init_auth();
|
|
832
|
+
init_config();
|
|
821
833
|
init_embedding();
|
|
822
834
|
QDRANT_URL = "http://localhost:6333";
|
|
823
835
|
getCollection = () => process.env.AMEM_COLLECTION || "amem_notes";
|
|
@@ -1128,7 +1140,7 @@ function configureLlm(cfg) {
|
|
|
1128
1140
|
function warnOnce(key, message) {
|
|
1129
1141
|
if (_warned.has(key)) return;
|
|
1130
1142
|
_warned.add(key);
|
|
1131
|
-
|
|
1143
|
+
warn(message);
|
|
1132
1144
|
}
|
|
1133
1145
|
function resolveProvider(role = "fast") {
|
|
1134
1146
|
const raw = role === "strong" ? process.env.AMEM_LLM_STRONG_PROVIDER || _override.strong?.provider || void 0 : void 0;
|
|
@@ -1197,9 +1209,11 @@ async function llmCall(prompt, maxTokens = 500, role = "fast") {
|
|
|
1197
1209
|
const isThinking = model.includes("gemini") || model.includes("pro-agent");
|
|
1198
1210
|
const effectiveMaxTokens = isThinking ? Math.max(maxTokens * 8, 4e3) : maxTokens;
|
|
1199
1211
|
try {
|
|
1200
|
-
|
|
1212
|
+
const text = provider === "openai" ? await openaiCall(prompt, model, effectiveMaxTokens, baseURL) : await anthropicCall(prompt, model, effectiveMaxTokens, baseURL);
|
|
1213
|
+
if (text === null) warn(`[amem] ${provider} answered with no text (model ${model})`);
|
|
1214
|
+
return text;
|
|
1201
1215
|
} catch (e) {
|
|
1202
|
-
|
|
1216
|
+
warn(`[amem] LLM call failed: ${e.message}`);
|
|
1203
1217
|
return null;
|
|
1204
1218
|
}
|
|
1205
1219
|
}
|
|
@@ -1288,7 +1302,8 @@ confidence guide (Story 27):
|
|
|
1288
1302
|
|
|
1289
1303
|
Text: ${content}`;
|
|
1290
1304
|
const raw = await llmCall(prompt, 400);
|
|
1291
|
-
if (!raw)
|
|
1305
|
+
if (!raw) {
|
|
1306
|
+
warn("[amem] note construction got nothing back; storing with no keywords, tags or context");
|
|
1292
1307
|
return {
|
|
1293
1308
|
keywords: [],
|
|
1294
1309
|
tags: [],
|
|
@@ -1298,6 +1313,7 @@ Text: ${content}`;
|
|
|
1298
1313
|
topics: [],
|
|
1299
1314
|
confidence: "medium"
|
|
1300
1315
|
};
|
|
1316
|
+
}
|
|
1301
1317
|
try {
|
|
1302
1318
|
const data = parseJsonLoose(raw);
|
|
1303
1319
|
const rawCategory = typeof data.category === "string" ? data.category : "General";
|
|
@@ -1316,7 +1332,7 @@ Text: ${content}`;
|
|
|
1316
1332
|
confidence
|
|
1317
1333
|
};
|
|
1318
1334
|
} catch (e) {
|
|
1319
|
-
|
|
1335
|
+
warn(`[amem] Note construction parse failed: ${e.message}`);
|
|
1320
1336
|
return {
|
|
1321
1337
|
keywords: [],
|
|
1322
1338
|
tags: [],
|
|
@@ -1345,9 +1361,15 @@ async function llmCrudDecision(userText, assistantText, existingMemories) {
|
|
|
1345
1361
|
const raw = await llmCall(prompt, 400, resolveCrudRole());
|
|
1346
1362
|
if (!raw) return [];
|
|
1347
1363
|
const match = stripReasoning(raw).match(/\[.*\]/s);
|
|
1348
|
-
if (!match)
|
|
1364
|
+
if (!match) {
|
|
1365
|
+
warn("[amem] llmCrudDecision found no array in the response; nothing from this turn is stored");
|
|
1366
|
+
return [];
|
|
1367
|
+
}
|
|
1349
1368
|
const parsed = JSON.parse(match[0]);
|
|
1350
|
-
if (!Array.isArray(parsed))
|
|
1369
|
+
if (!Array.isArray(parsed)) {
|
|
1370
|
+
warn("[amem] llmCrudDecision parsed a non-array; nothing from this turn is stored");
|
|
1371
|
+
return [];
|
|
1372
|
+
}
|
|
1351
1373
|
const ops = [];
|
|
1352
1374
|
for (const item of parsed) {
|
|
1353
1375
|
if (!item || typeof item !== "object") continue;
|
|
@@ -1366,7 +1388,7 @@ async function llmCrudDecision(userText, assistantText, existingMemories) {
|
|
|
1366
1388
|
}
|
|
1367
1389
|
return ops.slice(0, 3);
|
|
1368
1390
|
} catch (e) {
|
|
1369
|
-
|
|
1391
|
+
warn(`[amem] llmCrudDecision failed: ${e.message}`);
|
|
1370
1392
|
return [];
|
|
1371
1393
|
}
|
|
1372
1394
|
}
|
|
@@ -1376,20 +1398,26 @@ async function llmShouldMerge(contentA, contentB) {
|
|
|
1376
1398
|
if (!raw) return { shouldMerge: false };
|
|
1377
1399
|
try {
|
|
1378
1400
|
const data = parseJsonLoose(raw);
|
|
1379
|
-
if (typeof data.shouldMerge !== "boolean")
|
|
1401
|
+
if (typeof data.shouldMerge !== "boolean") {
|
|
1402
|
+
warn("[amem] llmShouldMerge got no boolean verdict; treating the pair as distinct");
|
|
1403
|
+
return { shouldMerge: false };
|
|
1404
|
+
}
|
|
1380
1405
|
if (data.shouldMerge && typeof data.merged === "string") {
|
|
1381
1406
|
return { shouldMerge: true, merged: data.merged };
|
|
1382
1407
|
}
|
|
1383
1408
|
return { shouldMerge: false };
|
|
1384
1409
|
} catch (e) {
|
|
1385
|
-
|
|
1410
|
+
warn(`[amem] llmShouldMerge parse failed: ${e.message}`);
|
|
1386
1411
|
return { shouldMerge: false };
|
|
1387
1412
|
}
|
|
1388
1413
|
}
|
|
1389
1414
|
async function llmEvolutionJudge(oldContent, newContent) {
|
|
1390
1415
|
const prompt = t.evolutionJudge(oldContent, newContent);
|
|
1391
1416
|
const raw = await llmCall(prompt, 300, "strong");
|
|
1392
|
-
if (!raw)
|
|
1417
|
+
if (!raw) {
|
|
1418
|
+
warn("[amem] evolution judge got nothing back; defaulting the pair to NEW");
|
|
1419
|
+
return { type: "NEW" };
|
|
1420
|
+
}
|
|
1393
1421
|
try {
|
|
1394
1422
|
const data = parseJsonLoose(raw);
|
|
1395
1423
|
const type = VALID_EVOLUTION_TYPES.has(data.type) ? data.type : "NEW";
|
|
@@ -1398,7 +1426,7 @@ async function llmEvolutionJudge(oldContent, newContent) {
|
|
|
1398
1426
|
mergedContent: typeof data.mergedContent === "string" ? data.mergedContent : void 0
|
|
1399
1427
|
};
|
|
1400
1428
|
} catch (e) {
|
|
1401
|
-
|
|
1429
|
+
warn(`[amem] llmEvolutionJudge parse failed: ${e.message}`);
|
|
1402
1430
|
return { type: "NEW" };
|
|
1403
1431
|
}
|
|
1404
1432
|
}
|
|
@@ -1437,7 +1465,7 @@ ${linkedStr}`;
|
|
|
1437
1465
|
tagsToUpdate: Array.isArray(data.tags_to_update) ? data.tags_to_update.map(String) : []
|
|
1438
1466
|
};
|
|
1439
1467
|
} catch (e) {
|
|
1440
|
-
|
|
1468
|
+
warn(`[amem] Evolution parse failed: ${e.message}`);
|
|
1441
1469
|
return { tags: null, context: null, shouldStrengthen: false, suggestedConnections: [], tagsToUpdate: [] };
|
|
1442
1470
|
}
|
|
1443
1471
|
}
|
|
@@ -1449,9 +1477,15 @@ async function llmConflictScan(contents) {
|
|
|
1449
1477
|
if (!raw) return [];
|
|
1450
1478
|
const cleaned = stripReasoning(raw);
|
|
1451
1479
|
const match = cleaned.match(/\[[\s\S]*\]/);
|
|
1452
|
-
if (!match)
|
|
1480
|
+
if (!match) {
|
|
1481
|
+
warn("[amem] llmConflictScan found no array in the response; this sweep reports no pairs");
|
|
1482
|
+
return [];
|
|
1483
|
+
}
|
|
1453
1484
|
const parsed = JSON.parse(match[0]);
|
|
1454
|
-
if (!Array.isArray(parsed))
|
|
1485
|
+
if (!Array.isArray(parsed)) {
|
|
1486
|
+
warn("[amem] llmConflictScan parsed a non-array; this sweep reports no pairs");
|
|
1487
|
+
return [];
|
|
1488
|
+
}
|
|
1455
1489
|
const pairs = [];
|
|
1456
1490
|
const seen = /* @__PURE__ */ new Set();
|
|
1457
1491
|
for (const item of parsed) {
|
|
@@ -1475,7 +1509,7 @@ async function llmConflictScan(contents) {
|
|
|
1475
1509
|
}
|
|
1476
1510
|
return pairs;
|
|
1477
1511
|
} catch (e) {
|
|
1478
|
-
|
|
1512
|
+
warn(`[amem] llmConflictScan failed: ${e.message}`);
|
|
1479
1513
|
return [];
|
|
1480
1514
|
}
|
|
1481
1515
|
}
|
|
@@ -1486,6 +1520,7 @@ var init_llm = __esm({
|
|
|
1486
1520
|
import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
|
|
1487
1521
|
import_openai = __toESM(require("openai"), 1);
|
|
1488
1522
|
init_prompts();
|
|
1523
|
+
init_config();
|
|
1489
1524
|
_override = {};
|
|
1490
1525
|
_warned = /* @__PURE__ */ new Set();
|
|
1491
1526
|
DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -1817,7 +1852,7 @@ async function addMemory(content, agentId = "main", opts) {
|
|
|
1817
1852
|
}
|
|
1818
1853
|
}
|
|
1819
1854
|
} catch (e) {
|
|
1820
|
-
|
|
1855
|
+
warn(`[amem] link/evolution phase failed: ${e.message}`);
|
|
1821
1856
|
}
|
|
1822
1857
|
console.log(`[done] Note added: ${note.id}`);
|
|
1823
1858
|
return note.id;
|
|
@@ -2083,8 +2118,8 @@ async function consolidateMemories(agentId, logger, storageCtx) {
|
|
|
2083
2118
|
const ctx = storageCtx ?? defaultCtx();
|
|
2084
2119
|
const log = {
|
|
2085
2120
|
info: (msg) => logger ? logger.info(msg) : console.log(msg),
|
|
2086
|
-
warn: (msg) => logger ? logger.warn(msg) :
|
|
2087
|
-
error: (msg) => logger ? logger.error(msg) :
|
|
2121
|
+
warn: (msg) => logger ? logger.warn(msg) : warn(msg),
|
|
2122
|
+
error: (msg) => logger ? logger.error(msg) : warn(msg)
|
|
2088
2123
|
};
|
|
2089
2124
|
log.info(`[Consolidation] Starting consolidation for agentId: ${agentId}`);
|
|
2090
2125
|
const rawNotes = await ctx.listNotes(agentId);
|
|
@@ -2488,7 +2523,7 @@ async function migrateCollection(opts) {
|
|
|
2488
2523
|
const refreshFields = opts.refreshFields !== false;
|
|
2489
2524
|
const dryRun = opts.dryRun !== false;
|
|
2490
2525
|
const log = opts.logger?.info ?? ((m) => console.log(m));
|
|
2491
|
-
const
|
|
2526
|
+
const warn2 = opts.logger?.warn ?? warn;
|
|
2492
2527
|
if (from === to) throw new Error(`migrate: source and target are the same collection ("${from}")`);
|
|
2493
2528
|
const model = getEmbeddingModel();
|
|
2494
2529
|
const targetDim = await getEmbeddingDim();
|
|
@@ -2556,7 +2591,7 @@ async function migrateCollection(opts) {
|
|
|
2556
2591
|
if (!note.context) note.context = built.context;
|
|
2557
2592
|
refreshed++;
|
|
2558
2593
|
} catch (e) {
|
|
2559
|
-
|
|
2594
|
+
warn2(`[migrate] re-extract failed for ${note.id.slice(0, 8)} \u2014 keeping as-is: ${e.message}`);
|
|
2560
2595
|
}
|
|
2561
2596
|
}
|
|
2562
2597
|
const point = noteToPoint({ ...note, embedding: await encode(buildEmbedText(note)) });
|
|
@@ -2569,7 +2604,7 @@ async function migrateCollection(opts) {
|
|
|
2569
2604
|
await flush();
|
|
2570
2605
|
const finalCount = await countPointsRaw(to);
|
|
2571
2606
|
if (finalCount !== notes.length) {
|
|
2572
|
-
|
|
2607
|
+
warn2(`[migrate] target holds ${finalCount} point(s) but the source had ${notes.length} \u2014 check before switching`);
|
|
2573
2608
|
}
|
|
2574
2609
|
log(`[migrate] done: ${migrated} written, ${alreadyDone.size} already present, ${refreshed} re-extracted.`);
|
|
2575
2610
|
return {
|
|
@@ -2624,6 +2659,7 @@ var init_migrate = __esm({
|
|
|
2624
2659
|
init_embedding();
|
|
2625
2660
|
init_llm();
|
|
2626
2661
|
init_memory();
|
|
2662
|
+
init_config();
|
|
2627
2663
|
}
|
|
2628
2664
|
});
|
|
2629
2665
|
|
|
@@ -2782,7 +2818,10 @@ function register(api) {
|
|
|
2782
2818
|
const pluginId = api.id ?? "openclaw-amem";
|
|
2783
2819
|
const convBlocked = isConvAccessBlocked(api.config, pluginId);
|
|
2784
2820
|
if (convBlocked) logger.warn(BLOCKED_WARNING_LOG);
|
|
2785
|
-
configure({
|
|
2821
|
+
configure({
|
|
2822
|
+
dataDir: path5.join(os2.homedir(), ".openclaw"),
|
|
2823
|
+
warn: (msg) => logger.warn(msg)
|
|
2824
|
+
});
|
|
2786
2825
|
const hasStrong = !!(pluginConfig.llmStrongProvider || pluginConfig.llmStrongModel || pluginConfig.llmStrongBaseURL);
|
|
2787
2826
|
if (pluginConfig.llmProvider || pluginConfig.llmModel || pluginConfig.llmBaseURL || pluginConfig.llmCrudRole || hasStrong) {
|
|
2788
2827
|
configureLlm({
|
|
@@ -3132,7 +3171,12 @@ ${text}${hookWarning}` }],
|
|
|
3132
3171
|
const msgs = event.messages || [];
|
|
3133
3172
|
const lastUser = [...msgs].reverse().find((m) => m.role === "user");
|
|
3134
3173
|
const lastAssistant = [...msgs].reverse().find((m) => m.role === "assistant");
|
|
3135
|
-
if (!lastUser || !lastAssistant)
|
|
3174
|
+
if (!lastUser || !lastAssistant) {
|
|
3175
|
+
logger.info(
|
|
3176
|
+
`openclaw-amem: agent_end skipped \u2014 no ${!lastUser ? "user" : "assistant"} message in ${msgs.length} messages`
|
|
3177
|
+
);
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3136
3180
|
const userText = typeof lastUser.content === "string" ? lastUser.content : lastUser.content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
3137
3181
|
const assistantText = typeof lastAssistant.content === "string" ? lastAssistant.content : lastAssistant.content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
3138
3182
|
if (!userText || !shouldProcessTurn2(userText)) return;
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "amem",
|
|
4
4
|
"description": "Catches memories that contradict each other. Notes rewrite themselves as new ones arrive, link into a graph, and stay separated per agent and per person. Runs on a small model. Requires a local Qdrant. 中文走 jieba 分词,提示词有中文版。",
|
|
5
5
|
"icon": "https://amem.owo.lc/logo.png",
|
|
6
|
-
"version": "2.1.
|
|
6
|
+
"version": "2.1.2",
|
|
7
7
|
"kind": "memory",
|
|
8
8
|
"contracts": {
|
|
9
9
|
"tools": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-amem",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Catches memories that contradict each other. Notes rewrite themselves as new ones arrive, link into a graph, and stay separated per agent and per person. Runs on a small model. Requires a local Qdrant. 中文走 jieba 分词,提示词有中文版。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@anthropic-ai/sdk": "^0.
|
|
19
|
+
"@anthropic-ai/sdk": "^0.122.0",
|
|
20
20
|
"@huggingface/transformers": "^4.2.0",
|
|
21
21
|
"@node-rs/jieba": "^2.0.1",
|
|
22
22
|
"@qdrant/js-client-rest": "^1.18.0",
|