@modusensus/dsh-mneme 0.7.10 → 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 (154) hide show
  1. package/README.en.md +368 -0
  2. package/README.md +298 -169
  3. package/{dsh-mneme/cordis.patch.yml → cordis.patch.yml} +1 -0
  4. package/{dsh-mneme/lib → lib}/api.js +50 -295
  5. package/{dsh-mneme/lib → lib}/client.js +780 -936
  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/lib → 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/lib → 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 -4
  23. package/{dsh-mneme/scripts → scripts}/sync-lib.js +5 -10
  24. package/{dsh-mneme/src → src}/api.js +50 -295
  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/src → 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/src → 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 +10 -206
  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 -293
  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 -419
  70. package/dsh-mneme/LICENSE +0 -21
  71. package/dsh-mneme/README.md +0 -501
  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/dream/tag-extractor.js +0 -156
  79. package/dsh-mneme/lib/heat.js +0 -136
  80. package/dsh-mneme/lib/parser/tag.js +0 -59
  81. package/dsh-mneme/lib/parser/wiki-link.js +0 -38
  82. package/dsh-mneme/lib/search/tag-boost.js +0 -61
  83. package/dsh-mneme/lib/tools.js +0 -458
  84. package/dsh-mneme/package-lock.json +0 -1936
  85. package/dsh-mneme/package.json +0 -83
  86. package/dsh-mneme/scripts/check-sync.js +0 -42
  87. package/dsh-mneme/src/client.js +0 -2242
  88. package/dsh-mneme/src/dream/tag-extractor.js +0 -156
  89. package/dsh-mneme/src/heat.js +0 -136
  90. package/dsh-mneme/src/parser/tag.js +0 -59
  91. package/dsh-mneme/src/parser/wiki-link.js +0 -38
  92. package/dsh-mneme/src/search/tag-boost.js +0 -61
  93. package/dsh-mneme/src/tools.js +0 -458
  94. package/dsh-mneme/test/boundary-v0625.test.js +0 -82
  95. package/dsh-mneme/test/directory.test.js +0 -134
  96. package/dsh-mneme/test/heat.test.js +0 -148
  97. package/dsh-mneme/test/inject.test.js +0 -206
  98. package/dsh-mneme/test/layered-types-stats.test.js +0 -144
  99. package/dsh-mneme/test/lib-smoke.test.js +0 -109
  100. package/dsh-mneme/test/normalize-decisions.test.js +0 -120
  101. package/dsh-mneme/test/provenance.test.js +0 -103
  102. package/dsh-mneme/test/recall-runs.test.js +0 -93
  103. package/dsh-mneme/test/sleep-heat.test.js +0 -112
  104. package/dsh-mneme/test/tag-boost.test.js +0 -125
  105. package/dsh-mneme/test/tag.test.js +0 -426
  106. package/dsh-mneme/test/tools.test.js +0 -674
  107. package/dsh-mneme/test/updated-at-semantics.test.js +0 -113
  108. package/dsh-mneme/test/wiki-link.test.js +0 -332
  109. package//346/250/252/345/271/205.png +0 -0
  110. /package/{dsh-mneme/lib → lib}/commands.js +0 -0
  111. /package/{dsh-mneme/lib → lib}/dream/clustering.js +0 -0
  112. /package/{dsh-mneme/lib → lib}/embedding.js +0 -0
  113. /package/{dsh-mneme/lib → lib}/entities/extractor.js +0 -0
  114. /package/{dsh-mneme/lib → lib}/local-embedder.js +0 -0
  115. /package/{dsh-mneme/lib → lib}/search/adaptive.js +0 -0
  116. /package/{dsh-mneme/lib → lib}/search/bm25.js +0 -0
  117. /package/{dsh-mneme/lib → lib}/vector-index.js +0 -0
  118. /package/{dsh-mneme/scripts → scripts}/benchmark-embed.js +0 -0
  119. /package/{dsh-mneme/scripts → scripts}/benchmark-recall.js +0 -0
  120. /package/{dsh-mneme/scripts → scripts}/benchmark-rerank.js +0 -0
  121. /package/{dsh-mneme/scripts → scripts}/stress-dsh.js +0 -0
  122. /package/{dsh-mneme/src → src}/commands.js +0 -0
  123. /package/{dsh-mneme/src → src}/dream/clustering.js +0 -0
  124. /package/{dsh-mneme/src → src}/embedding.js +0 -0
  125. /package/{dsh-mneme/src → src}/entities/extractor.js +0 -0
  126. /package/{dsh-mneme/src → src}/local-embedder.js +0 -0
  127. /package/{dsh-mneme/src → src}/search/adaptive.js +0 -0
  128. /package/{dsh-mneme/src → src}/search/bm25.js +0 -0
  129. /package/{dsh-mneme/src → src}/vector-index.js +0 -0
  130. /package/{dsh-mneme/test → test}/audit.test.js +0 -0
  131. /package/{dsh-mneme/test → test}/benchmark.test.js +0 -0
  132. /package/{dsh-mneme/test → test}/clustering.test.js +0 -0
  133. /package/{dsh-mneme/test → test}/commands.test.js +0 -0
  134. /package/{dsh-mneme/test → test}/conflict-freeze.test.js +0 -0
  135. /package/{dsh-mneme/test → test}/entities.test.js +0 -0
  136. /package/{dsh-mneme/test → test}/epistemic.test.js +0 -0
  137. /package/{dsh-mneme/test → test}/fnew-0112.test.js +0 -0
  138. /package/{dsh-mneme/test → test}/fnew-03.test.js +0 -0
  139. /package/{dsh-mneme/test → test}/helpers/dream-mock.js +0 -0
  140. /package/{dsh-mneme/test → test}/local-embedder.test.js +0 -0
  141. /package/{dsh-mneme/test → test}/mirror-dirty.test.js +0 -0
  142. /package/{dsh-mneme/test → test}/mirror-edit-digest.test.js +0 -0
  143. /package/{dsh-mneme/test → test}/mirror-generation.test.js +0 -0
  144. /package/{dsh-mneme/test → test}/mirror.test.js +0 -0
  145. /package/{dsh-mneme/test → test}/peer-blockers.test.js +0 -0
  146. /package/{dsh-mneme/test → test}/policy-epoch.test.js +0 -0
  147. /package/{dsh-mneme/test → test}/quality-filter.test.js +0 -0
  148. /package/{dsh-mneme/test → test}/recall-evals.test.js +0 -0
  149. /package/{dsh-mneme/test → test}/receipt-chain.test.js +0 -0
  150. /package/{dsh-mneme/test → test}/reflection.test.js +0 -0
  151. /package/{dsh-mneme/test → test}/search-fusion.test.js +0 -0
  152. /package/{dsh-mneme/test → test}/semantic.test.js +0 -0
  153. /package/{dsh-mneme/test → test}/stress.test.js +0 -0
  154. /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,107 +468,6 @@ 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.
543
- register({
544
- kind: "exact",
545
- path: "/api/dsh-mneme/wikilinks/backlinks",
546
- handler(req, res) {
547
- try {
548
- 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
- });
634
- } catch {
635
- sendJson(res, 500, { error: "internal" });
636
- }
637
- }
638
- });
639
-
640
471
  // --- entity directory (entity gene v0.3.0): read-only list for the UI ---
641
472
  // Flat entity list (newest-seen first). Attributes and relations stay on
642
473
  // their own per-entity endpoints, the directory only carries the columns
@@ -741,84 +572,8 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
741
572
  }
742
573
  });
743
574
 
744
- // --- memory tags (v0.6.2) ------------------------------------------------
745
- // GET /api/dsh-mneme/memory/tags?id=<memoryId> → live entity_attrs-backed
746
- // tag set for one memory plus the manualTagEnabled gate (so the panel
747
- // can hide tag editing when the manual path is off). Read-only, stays
748
- // open when apiToken is set (like list/search/semantic).
749
- // POST /api/dsh-mneme/memory/tags { id, tags } → overwrite the live tag set
750
- // via service.setMemoryTags (manualTagEnabled gate); 409 when the gate
751
- // is closed. Auth-gated like the other write endpoints.
752
- register({
753
- kind: "exact",
754
- path: "/api/dsh-mneme/memory/tags",
755
- handler(req, res) {
756
- try {
757
- if (req.method === "POST" || req.method === "PUT") {
758
- if (!requireAuth(req, res, apiToken)) return;
759
- return readBody(req).then((text) => {
760
- const body = parseBody(text);
761
- const id = typeof body.id === "string" ? body.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 result = service.setMemoryTags(id, Array.isArray(body.tags) ? body.tags : []);
772
- if (result?.ok === false) {
773
- sendJson(res, 409, { error: result.error || "tags-disabled" });
774
- return;
775
- }
776
- sendJson(res, 200, { ok: true, memoryId: id, tags: result?.tags ?? [] });
777
- });
778
- }
779
- const url = new URL(req.url, "http://localhost");
780
- const id = (url.searchParams.get("id") ?? "").trim();
781
- if (!id) {
782
- sendJson(res, 400, { error: "missing-id" });
783
- return;
784
- }
785
- const memory = service.getById?.(id) ?? null;
786
- if (!memory) {
787
- sendJson(res, 404, { error: "memory-not-found" });
788
- return;
789
- }
790
- const tags = service.getMemoryTags?.(id) ?? [];
791
- sendJson(res, 200, {
792
- memoryId: id,
793
- tags: Array.isArray(tags) ? tags : [],
794
- manualTagEnabled: service.manualTagEnabled?.() ?? true
795
- });
796
- } catch {
797
- sendJson(res, 500, { error: "internal" });
798
- }
799
- }
800
- });
801
-
802
- // --- directory view (v0.6.3) ---------------------------------------------
803
- // GET /api/dsh-mneme/directory → memories grouped by tag as
804
- // { groups: [{ tag, memories: [...] }], untagged: [...] }. Live-only
805
- // (forgotten/archived/session-disposed excluded), groups tag-sorted, members
806
- // importance+updated DESC. Read-only, stays open when apiToken is set.
807
- register({
808
- kind: "exact",
809
- path: "/api/dsh-mneme/directory",
810
- handler(req, res) {
811
- try {
812
- const dir = service.getDirectory?.() ?? { groups: [], untagged: [] };
813
- sendJson(res, 200, dir);
814
- } catch {
815
- sendJson(res, 500, { error: "internal" });
816
- }
817
- }
818
- });
819
-
820
575
  return {
821
- routes: 20,
576
+ routes: 15,
822
577
  dispose: () => {
823
578
  for (const dispose of disposers) dispose();
824
579
  }