@modusensus/dsh-mneme 0.6.5 → 0.6.6
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/lib/dream/tag-extractor.js +8 -1
- package/lib/service.js +3 -1
- package/package.json +1 -1
- package/src/dream/tag-extractor.js +8 -1
- package/src/service.js +3 -1
- package/test/tag.test.js +18 -0
|
@@ -78,8 +78,12 @@ export async function runAutoTag({ ctx, service, config, route }) {
|
|
|
78
78
|
? config.autoTagMaxPerRun
|
|
79
79
|
: 10;
|
|
80
80
|
// Retained = post-consolidation active memories, newest first, capped.
|
|
81
|
+
// Skip memories that already carry live tags — otherwise every run re-picks
|
|
82
|
+
// the same "newest" batch (tagging doesn't bump updated_at) and older
|
|
83
|
+
// memories starve; manual tags are also left untouched this way.
|
|
81
84
|
const memories = service.all()
|
|
82
85
|
.filter((m) => !m.forgotten && !m.archived && !m.session_disposed_at && m.type !== "summary")
|
|
86
|
+
.filter((m) => (service.getMemoryTags?.(m.id) ?? []).length === 0)
|
|
83
87
|
.sort((a, b) => {
|
|
84
88
|
const ta = String(a.updated_at ?? "");
|
|
85
89
|
const tb = String(b.updated_at ?? "");
|
|
@@ -137,7 +141,10 @@ export async function runAutoTag({ ctx, service, config, route }) {
|
|
|
137
141
|
try {
|
|
138
142
|
service.transaction(() => {
|
|
139
143
|
for (const { id, tags } of toWrite) {
|
|
140
|
-
|
|
144
|
+
// Merge with existing live tags (never clobber manual tags that landed
|
|
145
|
+
// between selection and write).
|
|
146
|
+
const existing = service.getMemoryTags?.(id) ?? [];
|
|
147
|
+
service.applyMemoryTags(id, [...new Set([...existing, ...tags])]);
|
|
141
148
|
tagged++;
|
|
142
149
|
}
|
|
143
150
|
});
|
package/lib/service.js
CHANGED
|
@@ -373,7 +373,9 @@ export function createService({ store, mirror, config, onWrite, logger }) {
|
|
|
373
373
|
.sort((a, b) => b.score - a.score)
|
|
374
374
|
: hits.map((m) => ({ ...m, source: "tag" }));
|
|
375
375
|
const result = hits.slice(0, topK);
|
|
376
|
-
|
|
376
|
+
// Only count toward recall stats/forgetting when the caller asked for
|
|
377
|
+
// recording — the panel's `tag:` search must not pollute the curve.
|
|
378
|
+
if (options.recordRecall) touchRecalled(result);
|
|
377
379
|
return result;
|
|
378
380
|
}
|
|
379
381
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modusensus/dsh-mneme",
|
|
3
3
|
"description": "Cross-session memory plugin for DeepSeek Harness with autoDream consolidation: SQLite store, Markdown mirrors, 7 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.6",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -78,8 +78,12 @@ export async function runAutoTag({ ctx, service, config, route }) {
|
|
|
78
78
|
? config.autoTagMaxPerRun
|
|
79
79
|
: 10;
|
|
80
80
|
// Retained = post-consolidation active memories, newest first, capped.
|
|
81
|
+
// Skip memories that already carry live tags — otherwise every run re-picks
|
|
82
|
+
// the same "newest" batch (tagging doesn't bump updated_at) and older
|
|
83
|
+
// memories starve; manual tags are also left untouched this way.
|
|
81
84
|
const memories = service.all()
|
|
82
85
|
.filter((m) => !m.forgotten && !m.archived && !m.session_disposed_at && m.type !== "summary")
|
|
86
|
+
.filter((m) => (service.getMemoryTags?.(m.id) ?? []).length === 0)
|
|
83
87
|
.sort((a, b) => {
|
|
84
88
|
const ta = String(a.updated_at ?? "");
|
|
85
89
|
const tb = String(b.updated_at ?? "");
|
|
@@ -137,7 +141,10 @@ export async function runAutoTag({ ctx, service, config, route }) {
|
|
|
137
141
|
try {
|
|
138
142
|
service.transaction(() => {
|
|
139
143
|
for (const { id, tags } of toWrite) {
|
|
140
|
-
|
|
144
|
+
// Merge with existing live tags (never clobber manual tags that landed
|
|
145
|
+
// between selection and write).
|
|
146
|
+
const existing = service.getMemoryTags?.(id) ?? [];
|
|
147
|
+
service.applyMemoryTags(id, [...new Set([...existing, ...tags])]);
|
|
141
148
|
tagged++;
|
|
142
149
|
}
|
|
143
150
|
});
|
package/src/service.js
CHANGED
|
@@ -373,7 +373,9 @@ export function createService({ store, mirror, config, onWrite, logger }) {
|
|
|
373
373
|
.sort((a, b) => b.score - a.score)
|
|
374
374
|
: hits.map((m) => ({ ...m, source: "tag" }));
|
|
375
375
|
const result = hits.slice(0, topK);
|
|
376
|
-
|
|
376
|
+
// Only count toward recall stats/forgetting when the caller asked for
|
|
377
|
+
// recording — the panel's `tag:` search must not pollute the curve.
|
|
378
|
+
if (options.recordRecall) touchRecalled(result);
|
|
377
379
|
return result;
|
|
378
380
|
}
|
|
379
381
|
|
package/test/tag.test.js
CHANGED
|
@@ -186,6 +186,24 @@ test("runAutoTag writes validated tags into entity_attrs", async () => {
|
|
|
186
186
|
store.close();
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
+
test("runAutoTag: skips already-tagged memories, merges into fresh ones", async () => {
|
|
190
|
+
const { store, service } = makeService();
|
|
191
|
+
const a = seed(service, "Linux", "内核");
|
|
192
|
+
const b = seed(service, "考研", "公共管理");
|
|
193
|
+
store.setMemoryTags(a.id, ["已有"]); // manual tag
|
|
194
|
+
const text = JSON.stringify([
|
|
195
|
+
{ id: a.id, tags: ["linux"] },
|
|
196
|
+
{ id: b.id, tags: ["考研"] }
|
|
197
|
+
]);
|
|
198
|
+
const ctx = makeCtx([text]);
|
|
199
|
+
const result = await runAutoTag({ ctx, service, config: { dreamProvider: "d", dreamModel: "m" } });
|
|
200
|
+
assert.equal(result.ok, true);
|
|
201
|
+
assert.equal(result.tagged, 1, "only the untagged memory is tagged");
|
|
202
|
+
assert.deepEqual(store.getMemoryTags(a.id), ["已有"], "manual tags left untouched");
|
|
203
|
+
assert.deepEqual(store.getMemoryTags(b.id), ["考研"]);
|
|
204
|
+
store.close();
|
|
205
|
+
});
|
|
206
|
+
|
|
189
207
|
test("runAutoTag fail-safe: garbage / aborted LLM output never throws and writes nothing", async () => {
|
|
190
208
|
const { store, service } = makeService();
|
|
191
209
|
const a = seed(service, "Alpha", "正文");
|