@modusensus/dsh-mneme 0.8.16 → 0.9.1

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 (135) hide show
  1. package/README.md +91 -16
  2. package/assets/icon.svg +2 -2
  3. package/bin/cli.mjs +8 -2
  4. package/docs/CONFIGURATION.md +27 -1
  5. package/docs/CONTINUITY.md +18 -3
  6. package/docs/SEMANTIC.md +3 -3
  7. package/docs/WORKFLOWS.md +299 -0
  8. package/lib/api-standalone.js +78 -12
  9. package/lib/api.js +403 -62
  10. package/lib/candidates.js +454 -0
  11. package/lib/capabilities.js +819 -0
  12. package/lib/client.js +2294 -423
  13. package/lib/config.js +63 -6
  14. package/lib/continuity.js +35 -6
  15. package/lib/dream/decisions.js +17 -1
  16. package/lib/dream/sleep.js +10 -2
  17. package/lib/dream.js +5 -1
  18. package/lib/embedding.js +116 -15
  19. package/lib/entities/extractor.js +4 -0
  20. package/lib/index.js +81 -12
  21. package/lib/inject.js +40 -4
  22. package/lib/lang.js +124 -0
  23. package/lib/pipeline-activities.js +178 -0
  24. package/lib/pipeline-attribution.js +165 -0
  25. package/lib/pipeline-command.js +279 -0
  26. package/lib/pipeline-confirm.js +142 -0
  27. package/lib/pipeline-exec.js +174 -0
  28. package/lib/pipeline-nudge.js +1281 -0
  29. package/lib/pipeline-observations.js +181 -0
  30. package/lib/pipeline-observe.js +133 -0
  31. package/lib/pipeline-runner.js +346 -0
  32. package/lib/pipeline-runs.js +442 -0
  33. package/lib/pipeline.js +655 -0
  34. package/lib/scope.js +16 -4
  35. package/lib/semantic.js +66 -10
  36. package/lib/serve.js +1 -1
  37. package/lib/service.js +59 -18
  38. package/lib/session-usage.js +350 -0
  39. package/lib/settings.js +138 -1
  40. package/lib/store.js +132 -6
  41. package/lib/summarize.js +30 -0
  42. package/lib/tools.js +11 -2
  43. package/package.json +3 -3
  44. package/scripts/changelog-prep.mjs +62 -7
  45. package/scripts/check-sync.js +8 -6
  46. package/scripts/publish-gate.mjs +197 -0
  47. package/scripts/release-docs.mjs +204 -0
  48. package/scripts/release-notes.mjs +162 -0
  49. package/src/api-standalone.js +78 -12
  50. package/src/api.js +403 -62
  51. package/src/candidates.js +454 -0
  52. package/src/capabilities.js +819 -0
  53. package/src/config.js +63 -6
  54. package/src/continuity.js +35 -6
  55. package/src/dream/decisions.js +17 -1
  56. package/src/dream/sleep.js +10 -2
  57. package/src/dream.js +5 -1
  58. package/src/embedding.js +116 -15
  59. package/src/entities/extractor.js +4 -0
  60. package/src/index.js +81 -12
  61. package/src/inject.js +40 -4
  62. package/src/lang.js +124 -0
  63. package/src/pipeline-activities.js +178 -0
  64. package/src/pipeline-attribution.js +165 -0
  65. package/src/pipeline-command.js +279 -0
  66. package/src/pipeline-confirm.js +142 -0
  67. package/src/pipeline-exec.js +174 -0
  68. package/src/pipeline-nudge.js +1281 -0
  69. package/src/pipeline-observations.js +181 -0
  70. package/src/pipeline-observe.js +133 -0
  71. package/src/pipeline-runner.js +346 -0
  72. package/src/pipeline-runs.js +442 -0
  73. package/src/pipeline.js +655 -0
  74. package/src/scope.js +16 -4
  75. package/src/semantic.js +66 -10
  76. package/src/serve.js +1 -1
  77. package/src/service.js +59 -18
  78. package/src/session-usage.js +350 -0
  79. package/src/settings.js +138 -1
  80. package/src/store.js +132 -6
  81. package/src/summarize.js +30 -0
  82. package/src/tools.js +11 -2
  83. package/test/api-hardening.test.js +173 -0
  84. package/test/api.test.js +244 -7
  85. package/test/candidates.test.js +345 -0
  86. package/test/capabilities.test.js +577 -0
  87. package/test/changelog-prep.test.js +74 -8
  88. package/test/cli-enums.test.js +37 -0
  89. package/test/client.test.js +510 -2
  90. package/test/continuity.test.js +84 -0
  91. package/test/docs-config-keys.test.js +65 -0
  92. package/test/embed-ready-poll.test.js +94 -0
  93. package/test/entities.test.js +13 -2
  94. package/test/failure-query.test.js +147 -0
  95. package/test/failure-retention.test.js +105 -0
  96. package/test/graph-api.test.js +36 -2
  97. package/test/helpers/write-admission-samples.js +6 -2
  98. package/test/inject-recall.test.js +32 -4
  99. package/test/lib-smoke.test.js +20 -0
  100. package/test/llm-audit.test.js +92 -1
  101. package/test/mint-source.test.js +202 -0
  102. package/test/persona-api.test.js +172 -0
  103. package/test/persona-flag.test.js +36 -0
  104. package/test/persona-inject.test.js +145 -0
  105. package/test/persona-settings.test.js +145 -0
  106. package/test/pipeline-activities.test.js +155 -0
  107. package/test/pipeline-attribution.test.js +175 -0
  108. package/test/pipeline-command.test.js +243 -0
  109. package/test/pipeline-confirm.test.js +148 -0
  110. package/test/pipeline-exec.test.js +172 -0
  111. package/test/pipeline-nudge.test.js +1036 -0
  112. package/test/pipeline-observations.test.js +157 -0
  113. package/test/pipeline-observe.test.js +127 -0
  114. package/test/pipeline-r1r2.test.js +149 -0
  115. package/test/pipeline-runner.test.js +442 -0
  116. package/test/pipeline-runs.test.js +767 -0
  117. package/test/pipeline.test.js +636 -0
  118. package/test/placement.test.js +1 -1
  119. package/test/publish-gate.test.js +272 -0
  120. package/test/recall-retention.test.js +128 -0
  121. package/test/reindex-backfill.test.js +83 -4
  122. package/test/release-docs.test.js +130 -0
  123. package/test/release-notes.test.js +240 -0
  124. package/test/release-workflow.test.js +93 -0
  125. package/test/route-policy.test.js +113 -0
  126. package/test/scope-distill-stamp.test.js +135 -0
  127. package/test/security-doc.test.js +139 -0
  128. package/test/sensitive-scan.test.js +44 -0
  129. package/test/serve.test.js +1 -1
  130. package/test/session-usage.test.js +308 -0
  131. package/test/store-write-path-columns.test.js +191 -0
  132. package/test/summarize.test.js +176 -0
  133. package/test/supersede-state-tracking.test.js +4 -4
  134. package/test/tools.test.js +16 -1
  135. package/test/workflow-guards.test.js +156 -0
@@ -23,6 +23,12 @@ import { parseReclaimJson, readReclaimFields, reclaimReply, RECLAIM_UNAVAILABLE
23
23
  const DEFAULT_PORT = 8790;
24
24
  const DEFAULT_HOST = "127.0.0.1";
25
25
  const MAX_PORT_ATTEMPTS = 20;
26
+ // 请求体与分页上界(防御,不是功能):与宿主内 api.js 的 MAX_REQUEST_BODY_BYTES /
27
+ // MAX_PAGE_LIMIT / MAX_SEARCH_TOP_K 同值同义——两张数据面的口径必须一致,否则
28
+ // 「从哪张脸进来」会决定一次请求能吃掉多少内存。
29
+ const MAX_REQUEST_BODY_BYTES = 4 * 1024 * 1024;
30
+ const MAX_PAGE_LIMIT = 1000;
31
+ const MAX_SEARCH_TOP_K = 200;
26
32
 
27
33
  /**
28
34
  * Listen with automatic EADDRINUSE recovery. Tries the configured port, then
@@ -86,13 +92,35 @@ function isAuthorized(req, apiToken) {
86
92
  return timingSafeEqual(Buffer.from(token), Buffer.from(apiToken));
87
93
  }
88
94
 
89
- /** Collect the request body as text (tolerant of transport errors). */
90
- function readBody(req) {
95
+ /**
96
+ * Collect the request body as text, or null when it exceeds the cap.
97
+ *
98
+ * 超限时**必须回 null 而不是空串**:空串经 parseBody 变成 {},而 /profile、
99
+ * /rules、/persona 的 PUT 会把「字段缺失」当成「用户要清空」——一个 5 MiB 的
100
+ * 请求就能把画像/规则/人格静默清掉。返回 null 让调用点在碰任何 store 之前
101
+ * 直接 413(2026-10 评审指出的数据损坏路径)。
102
+ * 连接不 destroy:调用方要能收到明确的 413,而不是被掐断。
103
+ */
104
+ function readBody(req, res) {
91
105
  return new Promise((resolve) => {
92
106
  let body = "";
93
- req.on("data", (chunk) => { body += chunk; });
94
- req.on("end", () => resolve(body));
95
- req.on("error", () => resolve(""));
107
+ let bytes = 0;
108
+ let overflow = false;
109
+ req.on("data", (chunk) => {
110
+ if (overflow) return;
111
+ bytes += chunk.length;
112
+ if (bytes > MAX_REQUEST_BODY_BYTES) {
113
+ overflow = true;
114
+ body = "";
115
+ sendJson(res, 413, { error: "payload-too-large", limit: MAX_REQUEST_BODY_BYTES });
116
+ return;
117
+ }
118
+ body += chunk;
119
+ });
120
+ req.on("end", () => resolve(overflow ? null : body));
121
+ // 超限后再来 transport error 也必须保留超限状态:否则调用点会拿着空串
122
+ // 继续往 store 写(PUT /persona 把 "" 解析成 {},覆盖既有设置)。
123
+ req.on("error", () => resolve(overflow ? null : ""));
96
124
  });
97
125
  }
98
126
 
@@ -323,6 +351,38 @@ export function createStandaloneApi({ service, store, config = {}, logger, setti
323
351
  return;
324
352
  }
325
353
 
354
+ // --- GET /persona: agent persona(人格定义页,beta;v1.2 多卡形) --------
355
+ if (req.method === "GET" && pathname === "/persona") {
356
+ sendJson(res, 200, { persona: settings?.getPersona?.() ?? { default: {}, byAgent: {} } });
357
+ return;
358
+ }
359
+
360
+ // --- PUT /persona: write default (single-object form) or full multi-card
361
+ // 形状判定在 setPersona 内(settings.js 双形兼容:单对象=合并 default、
362
+ // 多卡=全量替换);坏 JSON 回 400(与 /memories 写路径同口径)。
363
+ if (req.method === "PUT" && pathname === "/persona") {
364
+ void readBody(req, res).then((text) => {
365
+ if (text === null) return;
366
+ let body;
367
+ try {
368
+ body = JSON.parse(text || "{}");
369
+ } catch {
370
+ sendJson(res, 400, { error: "invalid-json" });
371
+ return;
372
+ }
373
+ const persona = body && typeof body === "object" && !Array.isArray(body) && body.persona && typeof body.persona === "object"
374
+ ? body.persona
375
+ : body;
376
+ try {
377
+ settings?.setPersona?.(persona && typeof persona === "object" ? persona : {});
378
+ sendJson(res, 200, { persona: settings?.getPersona?.() ?? { default: {}, byAgent: {} } });
379
+ } catch {
380
+ sendJson(res, 500, { error: "internal" });
381
+ }
382
+ });
383
+ return;
384
+ }
385
+
326
386
  // --- GET /rules: agent behavior rules -----------------------------------
327
387
  if (req.method === "GET" && pathname === "/rules") {
328
388
  sendJson(res, 200, { rules: settings?.getRules?.() ?? [] });
@@ -333,7 +393,8 @@ export function createStandaloneApi({ service, store, config = {}, logger, setti
333
393
  // internal /api/dsh-mneme/list) ------------------------------------
334
394
  if (req.method === "GET" && pathname === "/memories") {
335
395
  const type = url.searchParams.get("type") ?? undefined;
336
- const limit = Number(url.searchParams.get("limit") ?? 50);
396
+ // 上界是防御:limit 直接进 SQL LIMIT(见 MAX_PAGE_LIMIT 注释)。
397
+ const limit = Math.min(Number(url.searchParams.get("limit") ?? 50), MAX_PAGE_LIMIT);
337
398
  const offset = Number(url.searchParams.get("offset") ?? 0);
338
399
  const order = url.searchParams.get("order") ?? undefined;
339
400
  const minRaw = url.searchParams.get("minImportance");
@@ -384,7 +445,8 @@ export function createStandaloneApi({ service, store, config = {}, logger, setti
384
445
  // 整个异步回调套 try/catch:service 抛错不能变成未处理拒绝(Node 默认
385
446
  // 策略下会终止宿主进程)或悬着不回的请求(外层同步 try 捕不到这里)。
386
447
  if (req.method === "PUT") {
387
- void readBody(req).then(async (text) => {
448
+ void readBody(req, res).then(async (text) => {
449
+ if (text === null) return;
388
450
  try {
389
451
  await handlePutBody(res, service, logger, id, text);
390
452
  } catch (error) {
@@ -400,7 +462,8 @@ export function createStandaloneApi({ service, store, config = {}, logger, setti
400
462
 
401
463
  // --- POST /memories: save with title-dedupe (safer than raw save) ------
402
464
  if (req.method === "POST" && pathname === "/memories") {
403
- void readBody(req).then((text) => {
465
+ void readBody(req, res).then((text) => {
466
+ if (text === null) return;
404
467
  let body;
405
468
  try {
406
469
  body = JSON.parse(text || "{}");
@@ -488,7 +551,8 @@ export function createStandaloneApi({ service, store, config = {}, logger, setti
488
551
  // /api/dsh-mneme/maintenance/reclaim 共用 src/reclaim-http.js,两张皮只有鉴权
489
552
  // 与路径不同。这条曾经是整段复制的,于是面板侧修好之后这边还停在旧形态(#411)。
490
553
  if (req.method === "POST" && pathname === "/maintenance/reclaim") {
491
- void readBody(req).then((text) => {
554
+ void readBody(req, res).then((text) => {
555
+ if (text === null) return;
492
556
  const parsed = parseReclaimJson(text);
493
557
  if (!parsed.ok) {
494
558
  sendJson(res, 400, { error: parsed.error });
@@ -521,7 +585,8 @@ export function createStandaloneApi({ service, store, config = {}, logger, setti
521
585
  // 显式收 dir(插件没有工作区根目录概念,不猜路径);确定性解析零 LLM;
522
586
  // 幂等骑 saveWithDedupe 的 (type, title, scope) 去重 + _overwrite 刷新。
523
587
  if (req.method === "POST" && pathname === "/bootstrap") {
524
- void readBody(req).then((text) => {
588
+ void readBody(req, res).then((text) => {
589
+ if (text === null) return;
525
590
  let body;
526
591
  try {
527
592
  body = JSON.parse(text || "{}");
@@ -554,7 +619,8 @@ export function createStandaloneApi({ service, store, config = {}, logger, setti
554
619
  // --- GET /search: unified recall pipeline (keyword + vector + BM25) ----
555
620
  if (req.method === "GET" && pathname === "/search") {
556
621
  const q = url.searchParams.get("q") ?? "";
557
- const limit = Number(url.searchParams.get("topK") ?? url.searchParams.get("limit") ?? 20);
622
+ // 上界是防御:topK 直接进召回(见 MAX_SEARCH_TOP_K 注释)。
623
+ const limit = Math.min(Number(url.searchParams.get("topK") ?? url.searchParams.get("limit") ?? 20), MAX_SEARCH_TOP_K);
558
624
  const mode = url.searchParams.get("mode") ?? "auto";
559
625
  const rerank = url.searchParams.get("rerank") !== "false";
560
626
  const occurredFrom = url.searchParams.get("occurred_from") ?? null;
@@ -599,7 +665,7 @@ export function createStandaloneApi({ service, store, config = {}, logger, setti
599
665
  if (req.method === "GET" && pathname === "/context") {
600
666
  const q = (url.searchParams.get("q") ?? "").trim();
601
667
  const maxItemsRaw = Number(url.searchParams.get("topK") ?? url.searchParams.get("limit") ?? 5);
602
- const maxItems = Number.isInteger(maxItemsRaw) && maxItemsRaw > 0 ? maxItemsRaw : 5;
668
+ const maxItems = Number.isInteger(maxItemsRaw) && maxItemsRaw > 0 ? Math.min(maxItemsRaw, MAX_SEARCH_TOP_K) : 5;
603
669
  // threshold 是 importance 阈值(injectCandidates 语义),与 /search 的
604
670
  // 相似度 threshold 同名不同义——文档已点名,勿混。
605
671
  const thresholdRaw = Number(url.searchParams.get("threshold") ?? 3);