@modusensus/dsh-mneme 0.9.0 → 0.9.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 +82 -19
- package/bin/cli.mjs +69 -9
- package/docs/CONFIGURATION.md +48 -0
- package/docs/SEMANTIC.md +3 -3
- package/lib/api-standalone.js +78 -12
- package/lib/api.js +339 -59
- package/lib/candidates.js +454 -454
- package/lib/client.js +669 -256
- package/lib/config.js +50 -2
- package/lib/dream/sleep.js +35 -12
- package/lib/dream.js +86 -14
- package/lib/embedding.js +153 -22
- package/lib/index.js +32 -0
- package/lib/inject.js +34 -3
- package/lib/lang.js +124 -0
- package/lib/pipeline-activities.js +178 -0
- package/lib/pipeline-attribution.js +165 -0
- package/lib/pipeline-command.js +279 -0
- package/lib/pipeline-confirm.js +142 -0
- package/lib/pipeline-exec.js +174 -0
- package/lib/pipeline-nudge.js +1281 -0
- package/lib/pipeline-observations.js +181 -0
- package/lib/pipeline-observe.js +133 -0
- package/lib/pipeline-runner.js +346 -0
- package/lib/pipeline-runs.js +442 -0
- package/lib/pipeline.js +128 -7
- package/lib/scope-state.js +277 -0
- package/lib/semantic.js +66 -10
- package/lib/service.js +80 -3
- package/lib/settings.js +139 -1
- package/lib/store.js +216 -3
- package/lib/summarize.js +2 -3
- package/lib/tools.js +71 -0
- package/package.json +3 -3
- package/scripts/changelog-prep.mjs +62 -7
- package/scripts/check-sync.js +8 -6
- package/scripts/release-docs.mjs +204 -0
- package/scripts/release-notes.mjs +162 -0
- package/src/api-standalone.js +78 -12
- package/src/api.js +339 -59
- package/src/candidates.js +454 -454
- package/src/config.js +50 -2
- package/src/dream/sleep.js +35 -12
- package/src/dream.js +86 -14
- package/src/embedding.js +153 -22
- package/src/index.js +32 -0
- package/src/inject.js +34 -3
- package/src/lang.js +124 -0
- package/src/pipeline-activities.js +178 -0
- package/src/pipeline-attribution.js +165 -0
- package/src/pipeline-command.js +279 -0
- package/src/pipeline-confirm.js +142 -0
- package/src/pipeline-exec.js +174 -0
- package/src/pipeline-nudge.js +1281 -0
- package/src/pipeline-observations.js +181 -0
- package/src/pipeline-observe.js +133 -0
- package/src/pipeline-runner.js +346 -0
- package/src/pipeline-runs.js +442 -0
- package/src/pipeline.js +128 -7
- package/src/scope-state.js +277 -0
- package/src/semantic.js +66 -10
- package/src/service.js +80 -3
- package/src/settings.js +139 -1
- package/src/store.js +216 -3
- package/src/summarize.js +2 -3
- package/src/tools.js +71 -0
- package/test/api-hardening.test.js +173 -0
- package/test/api.test.js +210 -1
- package/test/changelog-prep.test.js +74 -8
- package/test/cli-enums.test.js +37 -0
- package/test/cli-flag-value.test.js +86 -0
- package/test/client.test.js +216 -6
- package/test/distill-tx-embed-gap.test.js +110 -0
- package/test/docs-config-keys.test.js +65 -0
- package/test/dream-maxtokens-fallback.test.js +132 -0
- package/test/embed-text-reasons.test.js +112 -0
- package/test/helpers/write-admission-samples.js +6 -2
- package/test/lib-smoke.test.js +20 -0
- package/test/persona-api.test.js +172 -0
- package/test/persona-flag.test.js +36 -0
- package/test/persona-inject.test.js +145 -0
- package/test/persona-settings.test.js +145 -0
- package/test/pipeline-activities.test.js +155 -0
- package/test/pipeline-attribution.test.js +175 -0
- package/test/pipeline-command.test.js +243 -0
- package/test/pipeline-confirm.test.js +148 -0
- package/test/pipeline-exec.test.js +172 -0
- package/test/pipeline-nudge.test.js +1036 -0
- package/test/pipeline-observations.test.js +157 -0
- package/test/pipeline-observe.test.js +127 -0
- package/test/pipeline-r1r2.test.js +149 -0
- package/test/pipeline-runner.test.js +442 -0
- package/test/pipeline-runs.test.js +767 -0
- package/test/pipeline.test.js +137 -6
- package/test/readme-claims.test.js +68 -0
- package/test/reindex-backfill.test.js +83 -4
- package/test/release-docs.test.js +130 -0
- package/test/release-notes.test.js +240 -0
- package/test/release-workflow.test.js +16 -0
- package/test/route-policy.test.js +113 -0
- package/test/scope-state.test.js +481 -0
- package/test/security-doc.test.js +139 -0
- package/test/sensitive-scan.test.js +44 -0
- package/test/tools-dto-schema-matrix.test.js +12 -3
- package/test/tools.test.js +6 -6
- package/test/workflow-guards.test.js +156 -0
package/lib/api-standalone.js
CHANGED
|
@@ -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
|
-
/**
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
req.on("
|
|
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
|
-
|
|
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
|
-
|
|
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);
|