@modusensus/dsh-mneme 0.7.9 → 0.7.11

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.
Files changed (155) hide show
  1. package/README.en.md +368 -0
  2. package/README.md +298 -167
  3. package/{dsh-mneme/cordis.patch.yml → cordis.patch.yml} +1 -0
  4. package/{dsh-mneme/lib → lib}/api.js +58 -284
  5. package/lib/client.js +2086 -0
  6. package/{dsh-mneme/src → lib}/config.js +6 -100
  7. package/{dsh-mneme/lib → lib}/dream/decisions.js +94 -143
  8. package/{dsh-mneme/src → lib}/dream/sleep.js +12 -152
  9. package/{dsh-mneme/lib → lib}/dream.js +22 -168
  10. package/{dsh-mneme/src → lib}/hot-memory.js +0 -7
  11. package/{dsh-mneme/src → lib}/index.js +6 -34
  12. package/{dsh-mneme/lib → lib}/inject.js +11 -53
  13. package/{dsh-mneme/src → lib}/mirror.js +1 -12
  14. package/{dsh-mneme/lib → lib}/quality-filter.js +1 -3
  15. package/{dsh-mneme/lib → lib}/reranker.js +1 -4
  16. package/{dsh-mneme/lib → lib}/service.js +20 -410
  17. package/{dsh-mneme/lib → lib}/settings.js +0 -70
  18. package/{dsh-mneme/lib → lib}/store.js +35 -450
  19. package/{dsh-mneme/lib → lib}/summarize.js +10 -10
  20. package/lib/tools.js +274 -0
  21. package/package.json +41 -19
  22. package/{dsh-mneme/scripts → scripts}/e2e-dsh.js +3 -3
  23. package/{dsh-mneme/scripts → scripts}/sync-lib.js +5 -10
  24. package/{dsh-mneme/src → src}/api.js +58 -284
  25. package/{dsh-mneme/lib → src}/config.js +6 -100
  26. package/{dsh-mneme/src → src}/dream/decisions.js +94 -143
  27. package/{dsh-mneme/lib → src}/dream/sleep.js +12 -152
  28. package/{dsh-mneme/src → src}/dream.js +22 -168
  29. package/{dsh-mneme/lib → src}/hot-memory.js +0 -7
  30. package/{dsh-mneme/lib → src}/index.js +6 -34
  31. package/{dsh-mneme/src → src}/inject.js +11 -53
  32. package/{dsh-mneme/lib → src}/mirror.js +1 -12
  33. package/{dsh-mneme/src → src}/quality-filter.js +1 -3
  34. package/{dsh-mneme/src → src}/reranker.js +1 -4
  35. package/{dsh-mneme/src → src}/service.js +20 -410
  36. package/{dsh-mneme/src → src}/settings.js +0 -70
  37. package/{dsh-mneme/src → src}/store.js +35 -450
  38. package/{dsh-mneme/src → src}/summarize.js +10 -10
  39. package/src/tools.js +274 -0
  40. package/{dsh-mneme/test → test}/api.test.js +1 -156
  41. package/{dsh-mneme/test → test}/client.test.js +22 -207
  42. package/{dsh-mneme/test → test}/config.test.js +0 -21
  43. package/{dsh-mneme/test → test}/dream.test.js +1 -160
  44. package/{dsh-mneme/test → test}/graph-api.test.js +0 -34
  45. package/{dsh-mneme/test → test}/hot-memory.test.js +0 -29
  46. package/test/inject.test.js +120 -0
  47. package/{dsh-mneme/test → test}/llm-audit.test.js +4 -4
  48. package/{dsh-mneme/test → test}/reasoning-effort.test.js +0 -27
  49. package/{dsh-mneme/test → test}/recall-layer.test.js +5 -26
  50. package/{dsh-mneme/test → test}/reranker.test.js +0 -43
  51. package/{dsh-mneme/test → test}/service-search.test.js +0 -25
  52. package/{dsh-mneme/test → test}/service.test.js +0 -106
  53. package/{dsh-mneme/test → test}/settings.test.js +0 -43
  54. package/{dsh-mneme/test → test}/sleep.test.js +4 -171
  55. package/{dsh-mneme/test → test}/store.test.js +0 -76
  56. package/{dsh-mneme/test → test}/summarize.test.js +16 -15
  57. package/test/tools.test.js +265 -0
  58. package/.github/workflows/publish.yml +0 -58
  59. package/.github/workflows/test.yml +0 -32
  60. package/CHANGELOG.md +0 -91
  61. package/CONTRIBUTING.md +0 -291
  62. package/SECURITY.md +0 -718
  63. package/docs/devlog/2026-08-14-dsh-mneme-dev-log.md +0 -247
  64. package/docs/devlog/2026-08-15-dsh-mneme-audit-stress-dev-log.md +0 -145
  65. package/docs/devlog/2026-08-15-dsh-mneme-pipeline-dev-log.md +0 -56
  66. package/docs/devlog/2026-08-15-dsh-mneme-reflection-dev-log.md +0 -77
  67. package/docs/devlog/2026-08-15-dsh-mneme-review-fixes-dev-log.md +0 -64
  68. package/docs/devlog/2026-08-15-dsh-mneme-semantic-dev-log.md +0 -90
  69. package/dsh-mneme/CHANGELOG.md +0 -406
  70. package/dsh-mneme/LICENSE +0 -21
  71. package/dsh-mneme/README.md +0 -498
  72. package/dsh-mneme/docs/AGENT_MEMORY_RESEARCH.md +0 -183
  73. package/dsh-mneme/docs/ENTITIES.md +0 -245
  74. package/dsh-mneme/docs/LOCAL_MODEL.md +0 -141
  75. package/dsh-mneme/docs/MIGRATION.md +0 -127
  76. package/dsh-mneme/docs/SEMANTIC.md +0 -256
  77. package/dsh-mneme/docs/SLEEP.md +0 -163
  78. package/dsh-mneme/lib/client.js +0 -2050
  79. package/dsh-mneme/lib/dream/tag-extractor.js +0 -156
  80. package/dsh-mneme/lib/heat.js +0 -136
  81. package/dsh-mneme/lib/parser/tag.js +0 -59
  82. package/dsh-mneme/lib/parser/wiki-link.js +0 -38
  83. package/dsh-mneme/lib/search/tag-boost.js +0 -61
  84. package/dsh-mneme/lib/tools.js +0 -458
  85. package/dsh-mneme/package-lock.json +0 -1936
  86. package/dsh-mneme/package.json +0 -83
  87. package/dsh-mneme/scripts/check-sync.js +0 -42
  88. package/dsh-mneme/src/client.js +0 -2050
  89. package/dsh-mneme/src/dream/tag-extractor.js +0 -156
  90. package/dsh-mneme/src/heat.js +0 -136
  91. package/dsh-mneme/src/parser/tag.js +0 -59
  92. package/dsh-mneme/src/parser/wiki-link.js +0 -38
  93. package/dsh-mneme/src/search/tag-boost.js +0 -61
  94. package/dsh-mneme/src/tools.js +0 -458
  95. package/dsh-mneme/test/boundary-v0625.test.js +0 -82
  96. package/dsh-mneme/test/directory.test.js +0 -134
  97. package/dsh-mneme/test/heat.test.js +0 -148
  98. package/dsh-mneme/test/inject.test.js +0 -206
  99. package/dsh-mneme/test/layered-types-stats.test.js +0 -144
  100. package/dsh-mneme/test/lib-smoke.test.js +0 -109
  101. package/dsh-mneme/test/normalize-decisions.test.js +0 -120
  102. package/dsh-mneme/test/provenance.test.js +0 -103
  103. package/dsh-mneme/test/recall-runs.test.js +0 -93
  104. package/dsh-mneme/test/sleep-heat.test.js +0 -112
  105. package/dsh-mneme/test/tag-boost.test.js +0 -125
  106. package/dsh-mneme/test/tag.test.js +0 -426
  107. package/dsh-mneme/test/tools.test.js +0 -674
  108. package/dsh-mneme/test/updated-at-semantics.test.js +0 -113
  109. package/dsh-mneme/test/wiki-link.test.js +0 -332
  110. package//346/250/252/345/271/205.png +0 -0
  111. /package/{dsh-mneme/lib → lib}/commands.js +0 -0
  112. /package/{dsh-mneme/lib → lib}/dream/clustering.js +0 -0
  113. /package/{dsh-mneme/lib → lib}/embedding.js +0 -0
  114. /package/{dsh-mneme/lib → lib}/entities/extractor.js +0 -0
  115. /package/{dsh-mneme/lib → lib}/local-embedder.js +0 -0
  116. /package/{dsh-mneme/lib → lib}/search/adaptive.js +0 -0
  117. /package/{dsh-mneme/lib → lib}/search/bm25.js +0 -0
  118. /package/{dsh-mneme/lib → lib}/vector-index.js +0 -0
  119. /package/{dsh-mneme/scripts → scripts}/benchmark-embed.js +0 -0
  120. /package/{dsh-mneme/scripts → scripts}/benchmark-recall.js +0 -0
  121. /package/{dsh-mneme/scripts → scripts}/benchmark-rerank.js +0 -0
  122. /package/{dsh-mneme/scripts → scripts}/stress-dsh.js +0 -0
  123. /package/{dsh-mneme/src → src}/commands.js +0 -0
  124. /package/{dsh-mneme/src → src}/dream/clustering.js +0 -0
  125. /package/{dsh-mneme/src → src}/embedding.js +0 -0
  126. /package/{dsh-mneme/src → src}/entities/extractor.js +0 -0
  127. /package/{dsh-mneme/src → src}/local-embedder.js +0 -0
  128. /package/{dsh-mneme/src → src}/search/adaptive.js +0 -0
  129. /package/{dsh-mneme/src → src}/search/bm25.js +0 -0
  130. /package/{dsh-mneme/src → src}/vector-index.js +0 -0
  131. /package/{dsh-mneme/test → test}/audit.test.js +0 -0
  132. /package/{dsh-mneme/test → test}/benchmark.test.js +0 -0
  133. /package/{dsh-mneme/test → test}/clustering.test.js +0 -0
  134. /package/{dsh-mneme/test → test}/commands.test.js +0 -0
  135. /package/{dsh-mneme/test → test}/conflict-freeze.test.js +0 -0
  136. /package/{dsh-mneme/test → test}/entities.test.js +0 -0
  137. /package/{dsh-mneme/test → test}/epistemic.test.js +0 -0
  138. /package/{dsh-mneme/test → test}/fnew-0112.test.js +0 -0
  139. /package/{dsh-mneme/test → test}/fnew-03.test.js +0 -0
  140. /package/{dsh-mneme/test → test}/helpers/dream-mock.js +0 -0
  141. /package/{dsh-mneme/test → test}/local-embedder.test.js +0 -0
  142. /package/{dsh-mneme/test → test}/mirror-dirty.test.js +0 -0
  143. /package/{dsh-mneme/test → test}/mirror-edit-digest.test.js +0 -0
  144. /package/{dsh-mneme/test → test}/mirror-generation.test.js +0 -0
  145. /package/{dsh-mneme/test → test}/mirror.test.js +0 -0
  146. /package/{dsh-mneme/test → test}/peer-blockers.test.js +0 -0
  147. /package/{dsh-mneme/test → test}/policy-epoch.test.js +0 -0
  148. /package/{dsh-mneme/test → test}/quality-filter.test.js +0 -0
  149. /package/{dsh-mneme/test → test}/recall-evals.test.js +0 -0
  150. /package/{dsh-mneme/test → test}/receipt-chain.test.js +0 -0
  151. /package/{dsh-mneme/test → test}/reflection.test.js +0 -0
  152. /package/{dsh-mneme/test → test}/search-fusion.test.js +0 -0
  153. /package/{dsh-mneme/test → test}/semantic.test.js +0 -0
  154. /package/{dsh-mneme/test → test}/stress.test.js +0 -0
  155. /package/{dsh-mneme/test → test}/vector-index.test.js +0 -0
@@ -88,23 +88,20 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
88
88
  const type = url.searchParams.get("type") ?? undefined;
89
89
  const limit = Number(url.searchParams.get("limit") ?? 50);
90
90
  const offset = Number(url.searchParams.get("offset") ?? 0);
91
- const items = service.toApiList(service.list({ type, limit, offset }));
92
- sendJson(res, 200, { items, total: service.count(type) });
93
- } catch {
94
- sendJson(res, 500, { error: "internal" });
95
- }
96
- }
97
- });
98
-
99
- register({
100
- kind: "exact",
101
- path: "/api/dsh-mneme/stats",
102
- handler(req, res) {
103
- try {
104
- const url = new URL(req.url, "http://localhost");
105
- const raw = url.searchParams.get("days");
106
- const days = Math.min(30, Math.max(1, parseInt(raw ?? "7", 10) || 7));
107
- sendJson(res, 200, service.stats({ days }));
91
+ // order=chrono: pure newest-first for paged browsing; default keeps
92
+ // the importance-ranked order other callers rely on.
93
+ const order = url.searchParams.get("order") ?? undefined;
94
+ // minImportance: numeric lower bound on importance (absent/NaN → no
95
+ // floor); source: exact match on the source column (empty → no filter).
96
+ const minRaw = url.searchParams.get("minImportance");
97
+ const minImportance = minRaw !== null && minRaw !== "" && !Number.isNaN(Number(minRaw))
98
+ ? Number(minRaw)
99
+ : undefined;
100
+ const source = url.searchParams.get("source") || undefined;
101
+ const items = service.toApiList(service.list({ type, limit, offset, order, minImportance, source }));
102
+ // Total honors the same filters as the rows, or the pager's
103
+ // has-more math breaks whenever minImportance/source is active.
104
+ sendJson(res, 200, { items, total: service.count(type, { minImportance, source }) });
108
105
  } catch {
109
106
  sendJson(res, 500, { error: "internal" });
110
107
  }
@@ -175,6 +172,40 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
175
172
  }
176
173
  });
177
174
 
175
+ // --- delete one memory by id ---
176
+ // Mutation route: apiToken-gated like the profile/rules writes above. POST
177
+ // body is JSON { id }. store.remove deletes silently, so existence is checked
178
+ // up front to give clients a distinguishable 404 instead of a fake success.
179
+ register({
180
+ kind: "exact",
181
+ path: "/api/dsh-mneme/delete",
182
+ handler(req, res) {
183
+ try {
184
+ if (req.method !== "POST") {
185
+ sendJson(res, 404, { error: "not-found" });
186
+ return;
187
+ }
188
+ if (!requireAuth(req, res, apiToken)) return;
189
+ return readBody(req).then((text) => {
190
+ const body = parseBody(text);
191
+ const id = typeof body.id === "string" ? body.id.trim() : "";
192
+ if (!id) {
193
+ sendJson(res, 400, { error: "missing-id" });
194
+ return;
195
+ }
196
+ if (!service.getById(id)) {
197
+ sendJson(res, 404, { error: "not-found" });
198
+ return;
199
+ }
200
+ service.remove(id);
201
+ sendJson(res, 200, { ok: true });
202
+ });
203
+ } catch {
204
+ sendJson(res, 500, { error: "internal" });
205
+ }
206
+ }
207
+ });
208
+
178
209
  // --- rules ---
179
210
  register({
180
211
  kind: "exact",
@@ -233,102 +264,6 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
233
264
  }
234
265
  });
235
266
 
236
- // --- app config read/write (partial: pass only the fields to change) ---
237
- register({
238
- kind: "exact",
239
- path: "/api/dsh-mneme/config",
240
- handler: async (req, res) => {
241
- try {
242
- if (!requireAuth(req, res, apiToken)) return;
243
- if (req.method === "PUT" || req.method === "POST") {
244
- const body = parseBody(await readBody(req));
245
- const patch = {};
246
- const setIfBoolean = (key) => {
247
- if (Object.prototype.hasOwnProperty.call(body, key)) {
248
- if (typeof body[key] !== "boolean") {
249
- sendJson(res, 400, { error: `${key} must be a boolean` });
250
- return false;
251
- }
252
- patch[key] = body[key];
253
- }
254
- return true;
255
- };
256
- if (!setIfBoolean("autoTagEnabled") || !setIfBoolean("manualTagEnabled") || !setIfBoolean("showSidebarTrigger")) return;
257
- if (Object.keys(patch).length === 0) {
258
- sendJson(res, 400, { error: "no valid config field provided" });
259
- return;
260
- }
261
- // Each setter ignores the keys it doesn't own, so one patch can feed
262
- // both stores safely (tag toggles → "autoTag", UI prefs → "ui").
263
- settings.setAutoTagConfig(patch);
264
- settings.setUiConfig(patch);
265
- // Respond with the effective (settings-over-config merged) toggles
266
- // so the panel always sees booleans that actually gate runtime
267
- // behaviour (issue #31).
268
- sendJson(res, 200, { config: service.getAppConfig() });
269
- return;
270
- }
271
- sendJson(res, 200, { config: service.getAppConfig() });
272
- } catch {
273
- sendJson(res, 500, { error: "internal" });
274
- }
275
- }
276
- });
277
-
278
- // --- delete a memory by exact id or best query match (mirrors memory_delete tool) ---
279
- register({
280
- kind: "exact",
281
- path: "/api/dsh-mneme/memories",
282
- handler: async (req, res) => {
283
- try {
284
- if (!requireAuth(req, res, apiToken)) return;
285
- if (req.method !== "DELETE") {
286
- sendJson(res, 405, { error: "method not allowed" });
287
- return;
288
- }
289
- const body = parseBody(await readBody(req));
290
- const hasId = Object.prototype.hasOwnProperty.call(body, "id");
291
- const hasQuery = Object.prototype.hasOwnProperty.call(body, "query");
292
- if ((hasId && hasQuery) || (!hasId && !hasQuery)) {
293
- sendJson(res, 400, { error: "provide exactly one of id or query" });
294
- return;
295
- }
296
- if (hasId) {
297
- if (typeof body.id !== "string" || !body.id.trim()) {
298
- sendJson(res, 400, { error: "invalid id" });
299
- return;
300
- }
301
- const mem = service.getById(body.id);
302
- if (!mem) {
303
- sendJson(res, 200, { deleted: false });
304
- return;
305
- }
306
- service.remove(body.id);
307
- sendJson(res, 200, { deleted: true, id: body.id });
308
- return;
309
- }
310
- if (typeof body.query !== "string" || !body.query.trim()) {
311
- sendJson(res, 400, { error: "invalid query" });
312
- return;
313
- }
314
- const results = await service.searchMemories(body.query, { mode: "auto", topK: 1, useRerank: true });
315
- if (!Array.isArray(results) || results.length === 0) {
316
- sendJson(res, 200, { deleted: false });
317
- return;
318
- }
319
- const mem = results[0];
320
- if (!mem || !mem.id) {
321
- sendJson(res, 200, { deleted: false });
322
- return;
323
- }
324
- service.remove(mem.id);
325
- sendJson(res, 200, { deleted: true, id: mem.id });
326
- } catch {
327
- sendJson(res, 500, { error: "internal" });
328
- }
329
- }
330
- });
331
-
332
267
  // --- vector re-index (backfill embeddings for rows missing them) ---
333
268
  register({
334
269
  kind: "exact",
@@ -479,10 +414,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
479
414
  name: n.name,
480
415
  type: n.type ?? null,
481
416
  mention_count: n.mention_count ?? 1,
482
- distance: n.distance,
483
- // v0.7.0 实体热投影:实体热 = 关联记忆 heat 聚合(max),前端据此
484
- // 缩放节点大小/明暗。heatEnabled=false 时 entityHeat 返回 null。
485
- heat: service.entityHeat?.(n.id) ?? null
417
+ distance: n.distance
486
418
  })),
487
419
  edges: [...edgeMap.values()].map((e) => ({
488
420
  id: e.id,
@@ -536,101 +468,19 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
536
468
  }
537
469
  });
538
470
 
539
- // --- wiki-link back links (v0.6.1) --------------------------------------
540
- // Read-only like the graph endpoints, so it stays open when apiToken is set.
541
- // GET /api/dsh-mneme/wikilinks/backlinks?id=<memoryId> memories whose
542
- // content carries a [[wiki-link]] resolving to the given memory.
471
+ // --- entity directory (entity gene v0.3.0): read-only list for the UI ---
472
+ // Flat entity list (newest-seen first). Attributes and relations stay on
473
+ // their own per-entity endpoints, the directory only carries the columns
474
+ // the rail needs: name, type, mention_count, first/last seen.
543
475
  register({
544
476
  kind: "exact",
545
- path: "/api/dsh-mneme/wikilinks/backlinks",
477
+ path: "/api/dsh-mneme/entities",
546
478
  handler(req, res) {
547
479
  try {
548
480
  const url = new URL(req.url, "http://localhost");
549
- const id = (url.searchParams.get("id") ?? "").trim();
550
- if (!id) {
551
- sendJson(res, 400, { error: "missing-id" });
552
- return;
553
- }
554
- const memory = service.getById?.(id) ?? null;
555
- const backlinks = (service.getBacklinks?.(id) ?? []).map(({ source, relation }) => ({
556
- id: source.id,
557
- title: source.title,
558
- type: source.type,
559
- created_at: relation.created_at
560
- }));
561
- sendJson(res, 200, {
562
- memoryId: id,
563
- memory: memory ? { id: memory.id, title: memory.title, type: memory.type } : null,
564
- backlinks
565
- });
566
- } catch {
567
- sendJson(res, 500, { error: "internal" });
568
- }
569
- }
570
- });
571
-
572
- // --- wiki-link forward links (v0.6.1) -----------------------------------
573
- // GET /api/dsh-mneme/wikilinks/forward?id=<memoryId> → memories the given
574
- // memory explicitly links to. Unresolved target titles surface with id:null.
575
- register({
576
- kind: "exact",
577
- path: "/api/dsh-mneme/wikilinks/forward",
578
- handler(req, res) {
579
- try {
580
- const url = new URL(req.url, "http://localhost");
581
- const id = (url.searchParams.get("id") ?? "").trim();
582
- if (!id) {
583
- sendJson(res, 400, { error: "missing-id" });
584
- return;
585
- }
586
- const memory = service.getById?.(id) ?? null;
587
- const links = (service.getForwardLinks?.(id) ?? []).map(({ target, relation }) => ({
588
- id: target?.id ?? null,
589
- title: target?.title ?? relation.to_entity,
590
- type: target?.type ?? null,
591
- created_at: relation.created_at
592
- }));
593
- sendJson(res, 200, {
594
- memoryId: id,
595
- memory: memory ? { id: memory.id, title: memory.title, type: memory.type } : null,
596
- links
597
- });
598
- } catch {
599
- sendJson(res, 500, { error: "internal" });
600
- }
601
- }
602
- });
603
-
604
- // --- wiki-link resolve (v0.6.1) -----------------------------------------
605
- // GET /api/dsh-mneme/wikilinks/resolve?title=<title> → case-insensitive exact
606
- // title match against the memories table (the resolution used when writing
607
- // links_to relations). 404 when no memory matches.
608
- register({
609
- kind: "exact",
610
- path: "/api/dsh-mneme/wikilinks/resolve",
611
- handler(req, res) {
612
- try {
613
- const url = new URL(req.url, "http://localhost");
614
- const title = (url.searchParams.get("title") ?? "").trim();
615
- if (!title) {
616
- sendJson(res, 400, { error: "missing-title" });
617
- return;
618
- }
619
- const memory = service.resolveWikiLink?.(title) ?? null;
620
- if (!memory) {
621
- sendJson(res, 404, { error: "memory-not-found", title });
622
- return;
623
- }
624
- // Note: no `source` field — it may carry file paths/internal host info
625
- // and this endpoint is read-only without auth when apiToken is set.
626
- sendJson(res, 200, {
627
- title,
628
- memory: {
629
- id: memory.id,
630
- title: memory.title,
631
- type: memory.type
632
- }
633
- });
481
+ const limit = Math.min(1000, Math.max(1, Number(url.searchParams.get("limit") ?? 500) || 500));
482
+ const entities = service.listEntities?.({ limit }) ?? [];
483
+ sendJson(res, 200, { entities, total: entities.length });
634
484
  } catch {
635
485
  sendJson(res, 500, { error: "internal" });
636
486
  }
@@ -722,84 +572,8 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
722
572
  }
723
573
  });
724
574
 
725
- // --- memory tags (v0.6.2) ------------------------------------------------
726
- // GET /api/dsh-mneme/memory/tags?id=<memoryId> → live entity_attrs-backed
727
- // tag set for one memory plus the manualTagEnabled gate (so the panel
728
- // can hide tag editing when the manual path is off). Read-only, stays
729
- // open when apiToken is set (like list/search/semantic).
730
- // POST /api/dsh-mneme/memory/tags { id, tags } → overwrite the live tag set
731
- // via service.setMemoryTags (manualTagEnabled gate); 409 when the gate
732
- // is closed. Auth-gated like the other write endpoints.
733
- register({
734
- kind: "exact",
735
- path: "/api/dsh-mneme/memory/tags",
736
- handler(req, res) {
737
- try {
738
- if (req.method === "POST" || req.method === "PUT") {
739
- if (!requireAuth(req, res, apiToken)) return;
740
- return readBody(req).then((text) => {
741
- const body = parseBody(text);
742
- const id = typeof body.id === "string" ? body.id.trim() : "";
743
- if (!id) {
744
- sendJson(res, 400, { error: "missing-id" });
745
- return;
746
- }
747
- const memory = service.getById?.(id) ?? null;
748
- if (!memory) {
749
- sendJson(res, 404, { error: "memory-not-found" });
750
- return;
751
- }
752
- const result = service.setMemoryTags(id, Array.isArray(body.tags) ? body.tags : []);
753
- if (result?.ok === false) {
754
- sendJson(res, 409, { error: result.error || "tags-disabled" });
755
- return;
756
- }
757
- sendJson(res, 200, { ok: true, memoryId: id, tags: result?.tags ?? [] });
758
- });
759
- }
760
- const url = new URL(req.url, "http://localhost");
761
- const id = (url.searchParams.get("id") ?? "").trim();
762
- if (!id) {
763
- sendJson(res, 400, { error: "missing-id" });
764
- return;
765
- }
766
- const memory = service.getById?.(id) ?? null;
767
- if (!memory) {
768
- sendJson(res, 404, { error: "memory-not-found" });
769
- return;
770
- }
771
- const tags = service.getMemoryTags?.(id) ?? [];
772
- sendJson(res, 200, {
773
- memoryId: id,
774
- tags: Array.isArray(tags) ? tags : [],
775
- manualTagEnabled: service.manualTagEnabled?.() ?? true
776
- });
777
- } catch {
778
- sendJson(res, 500, { error: "internal" });
779
- }
780
- }
781
- });
782
-
783
- // --- directory view (v0.6.3) ---------------------------------------------
784
- // GET /api/dsh-mneme/directory → memories grouped by tag as
785
- // { groups: [{ tag, memories: [...] }], untagged: [...] }. Live-only
786
- // (forgotten/archived/session-disposed excluded), groups tag-sorted, members
787
- // importance+updated DESC. Read-only, stays open when apiToken is set.
788
- register({
789
- kind: "exact",
790
- path: "/api/dsh-mneme/directory",
791
- handler(req, res) {
792
- try {
793
- const dir = service.getDirectory?.() ?? { groups: [], untagged: [] };
794
- sendJson(res, 200, dir);
795
- } catch {
796
- sendJson(res, 500, { error: "internal" });
797
- }
798
- }
799
- });
800
-
801
575
  return {
802
- routes: 19,
576
+ routes: 15,
803
577
  dispose: () => {
804
578
  for (const dispose of disposers) dispose();
805
579
  }