@modusensus/dsh-mneme 0.12.0 → 0.13.0

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.md +48 -19
  2. package/docs/CONFIGURATION.md +43 -4
  3. package/docs/DIRECTORIES.md +114 -0
  4. package/docs/DISTILL.md +64 -0
  5. package/docs/DSH-GO.md +97 -0
  6. package/docs/ENTITIES.md +5 -3
  7. package/docs/FORGET.md +77 -0
  8. package/docs/KB.md +110 -0
  9. package/docs/SLEEP.md +28 -3
  10. package/docs/STORAGE.md +23 -0
  11. package/lib/api-standalone.js +4 -1
  12. package/lib/api.js +581 -29
  13. package/lib/attachment-text.js +122 -0
  14. package/lib/choro-command.js +9 -2
  15. package/lib/client.js +3537 -1205
  16. package/lib/config.js +139 -1
  17. package/lib/continuity.js +7 -6
  18. package/lib/diary/auto.js +190 -0
  19. package/lib/diary/compose.js +440 -0
  20. package/lib/diary/dir.js +59 -0
  21. package/lib/diary/export.js +97 -0
  22. package/lib/diary/index.js +26 -0
  23. package/lib/diary/library.js +189 -0
  24. package/lib/diary/qq-inbox.js +142 -0
  25. package/lib/diary/rollover.js +61 -0
  26. package/lib/diary/session-provider.js +122 -0
  27. package/lib/dream/decisions.js +56 -5
  28. package/lib/dream/sleep.js +616 -15
  29. package/lib/dream.js +45 -13
  30. package/lib/dsh-go/host.js +162 -0
  31. package/lib/dsh-go/panel.js +2091 -0
  32. package/lib/dsh-go/text.js +342 -0
  33. package/lib/dsh-go/views/common.js +68 -0
  34. package/lib/dsh-go/views/entity.js +48 -0
  35. package/lib/dsh-go/views/memory.js +198 -0
  36. package/lib/dsh-go/views/root.js +30 -0
  37. package/lib/dsh-go/views/settings.js +398 -0
  38. package/lib/dsh-go/views/status.js +51 -0
  39. package/lib/embedding.js +42 -8
  40. package/lib/entities/extractor.js +36 -7
  41. package/lib/hot-memory.js +8 -9
  42. package/lib/index.js +92 -2
  43. package/lib/inject.js +189 -18
  44. package/lib/kb/index.js +309 -0
  45. package/lib/kb/routes.js +220 -0
  46. package/lib/kb/tools.js +148 -0
  47. package/lib/lang.js +87 -4
  48. package/lib/peak-hours.js +57 -2
  49. package/lib/semantic.js +347 -60
  50. package/lib/service.js +193 -66
  51. package/lib/settings.js +112 -14
  52. package/lib/store.js +347 -21
  53. package/lib/summarize.js +38 -11
  54. package/lib/tools.js +35 -4
  55. package/lib/user-settings.js +128 -32
  56. package/lib/vector-index.js +76 -6
  57. package/lib/wiki/cli-hint.js +90 -0
  58. package/lib/wiki/queries.js +116 -2
  59. package/lib/wiki/t1-jobs.js +232 -0
  60. package/lib/wiki/t1.js +7 -1
  61. package/package.json +25 -1
  62. package/src/api-standalone.js +4 -1
  63. package/src/api.js +581 -29
  64. package/src/attachment-text.js +122 -0
  65. package/src/choro-command.js +9 -2
  66. package/src/config.js +139 -1
  67. package/src/continuity.js +7 -6
  68. package/src/diary/auto.js +190 -0
  69. package/src/diary/compose.js +440 -0
  70. package/src/diary/dir.js +59 -0
  71. package/src/diary/export.js +97 -0
  72. package/src/diary/index.js +26 -0
  73. package/src/diary/library.js +189 -0
  74. package/src/diary/qq-inbox.js +142 -0
  75. package/src/diary/rollover.js +61 -0
  76. package/src/diary/session-provider.js +122 -0
  77. package/src/dream/decisions.js +56 -5
  78. package/src/dream/sleep.js +616 -15
  79. package/src/dream.js +45 -13
  80. package/src/dsh-go/host.js +162 -0
  81. package/src/dsh-go/panel.js +2091 -0
  82. package/src/dsh-go/text.js +342 -0
  83. package/src/dsh-go/views/common.js +68 -0
  84. package/src/dsh-go/views/entity.js +48 -0
  85. package/src/dsh-go/views/memory.js +198 -0
  86. package/src/dsh-go/views/root.js +30 -0
  87. package/src/dsh-go/views/settings.js +398 -0
  88. package/src/dsh-go/views/status.js +51 -0
  89. package/src/embedding.js +42 -8
  90. package/src/entities/extractor.js +36 -7
  91. package/src/hot-memory.js +8 -9
  92. package/src/index.js +92 -2
  93. package/src/inject.js +189 -18
  94. package/src/kb/index.js +309 -0
  95. package/src/kb/routes.js +220 -0
  96. package/src/kb/tools.js +148 -0
  97. package/src/lang.js +87 -4
  98. package/src/peak-hours.js +57 -2
  99. package/src/semantic.js +347 -60
  100. package/src/service.js +193 -66
  101. package/src/settings.js +112 -14
  102. package/src/store.js +347 -21
  103. package/src/summarize.js +38 -11
  104. package/src/tools.js +35 -4
  105. package/src/user-settings.js +128 -32
  106. package/src/vector-index.js +76 -6
  107. package/src/wiki/cli-hint.js +90 -0
  108. package/src/wiki/queries.js +116 -2
  109. package/src/wiki/t1-jobs.js +232 -0
  110. package/src/wiki/t1.js +7 -1
  111. package/test/api-routes-gaps.test.js +63 -0
  112. package/test/api.test.js +375 -4
  113. package/test/attachment-text.test.js +140 -0
  114. package/test/bm25-corpus-cache.test.js +145 -0
  115. package/test/capacity-warning.test.js +262 -0
  116. package/test/client.test.js +779 -154
  117. package/test/conflict-freeze.test.js +45 -1
  118. package/test/continuity.test.js +8 -1
  119. package/test/diary-library.test.js +187 -0
  120. package/test/diary.test.js +576 -0
  121. package/test/dream-maxtokens-fallback.test.js +3 -3
  122. package/test/dream.test.js +62 -1
  123. package/test/dsh-go-host.test.js +134 -0
  124. package/test/dsh-go-panel-actions.test.js +854 -0
  125. package/test/dsh-go-panel.test.js +251 -0
  126. package/test/embedding-ready.test.js +33 -3
  127. package/test/entities.test.js +73 -0
  128. package/test/forget-corrections.test.js +70 -0
  129. package/test/image-memory.test.js +171 -0
  130. package/test/issue-617-step1-query-and-fusion.test.js +361 -0
  131. package/test/kb-api-wiring.test.js +128 -0
  132. package/test/kb-collections.test.js +272 -0
  133. package/test/kb-routes.test.js +379 -0
  134. package/test/mint-source.test.js +26 -0
  135. package/test/peak-hours-observability.test.js +70 -0
  136. package/test/persona-api.test.js +3 -3
  137. package/test/persona-by-session.test.js +568 -0
  138. package/test/persona-settings.test.js +7 -7
  139. package/test/reflection-update-gate.test.js +129 -0
  140. package/test/reindex-backfill.test.js +334 -0
  141. package/test/reindex-scheduler.test.js +369 -0
  142. package/test/scope-candidates.test.js +249 -0
  143. package/test/sleep-action-set.test.js +14 -0
  144. package/test/sleep-heat.test.js +49 -0
  145. package/test/sleep.test.js +598 -0
  146. package/test/standalone-api.test.js +24 -0
  147. package/test/store.test.js +20 -0
  148. package/test/summarize.test.js +26 -0
  149. package/test/user-settings-segments.test.js +528 -0
  150. package/test/vector-index.test.js +120 -6
  151. package/test/wiki-api.test.js +355 -0
  152. package/test/wiki-cli-hint.test.js +118 -0
  153. package/test/wiki-queries.test.js +7 -0
  154. package/test/wiki-t1-jobs.test.js +125 -0
@@ -0,0 +1,232 @@
1
+ // dsh-mneme/src/wiki/t1-jobs.js
2
+ // T1 面板后台任务的进程账与状态机(契约 §3.2)。
3
+ //
4
+ // 为什么住在这里而不是 api.js:T1 要 spawn 子进程,而 api.js 刻意只依赖少数 node
5
+ // 内置(不 import child_process)——HTTP 面只做参数解析与闸门分叉,进程生命周期与
6
+ // 状态账归 wiki 层。副作用是单测可以在 API 侧注入假 job 对象,永不真跑一次 T1
7
+ // (真跑要花模型额度,测试里花钱是不可接受的)。
8
+ //
9
+ // 为什么 spawn CLI 而不是宿主进程内直接 createT1():单模块 30–240s,挂在宿主事件
10
+ // 循环上会拖住整个插件;而 bin/dsh-mneme-choro.mjs --t1 已经是那条唯一收费入口,
11
+ // 面板再走一遍编排逻辑等于造第二条路径,两边迟早不一致(收据形状、降级语义都会漂)。
12
+ //
13
+ // stdout 契约(防一类回归):CLI 的 --json 用 JSON.stringify(receipt, null, 2) 输出
14
+ // ——**多行 JSON,不是 NDJSON**。所以 ring buffer 按行收(面板要的是人能看的日志尾巴),
15
+ // 回执解析另留一份可截断的全文,别指望从 200 行里拼出完整 JSON。
16
+ import { spawn as nodeSpawn } from "node:child_process";
17
+ import { randomUUID } from "node:crypto";
18
+ import { dirname, join } from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ // lib/wiki/t1-jobs.js 与 src/wiki/t1-jobs.js 到 bin/ 都是上两级:sync 只换目录名,
22
+ // 深度不变,所以这里可以硬编码相对位置。
23
+ const DEFAULT_SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "bin", "dsh-mneme-choro.mjs");
24
+
25
+ // 日志尾巴的上限取 200 行:面板是个窄弹层,再多也没人往下翻;而上限存在的真理由
26
+ // 是长任务会往 stdout 泼几万行,不封顶就是内存泄漏。
27
+ const DEFAULT_RING = 200;
28
+ // 已完成任务保留条数:status 要能被反复轮询(面板 2s 一次),但也不能无限留。
29
+ const DEFAULT_FINISHED = 20;
30
+
31
+ /** 逐行切分:chunk 边界可能落在一行中间,所以每路各留一个未完成余量。 */
32
+ function makeLineSplitter(onLine) {
33
+ let rest = "";
34
+ return {
35
+ push(chunk) {
36
+ rest += chunk;
37
+ const parts = rest.split(/\r?\n/);
38
+ rest = parts.pop() ?? "";
39
+ for (const line of parts) onLine(line);
40
+ },
41
+ flush() {
42
+ if (rest !== "") { onLine(rest); rest = ""; }
43
+ }
44
+ };
45
+ }
46
+
47
+ /**
48
+ * @param {object} opts
49
+ * @param {object} opts.store createStore 产物(只需要它来定位 bin 之外的路径时用;目前不读库)
50
+ * @param {Function} [opts.spawn] 注入的 spawn(测试注假实现,绝不真起进程)
51
+ * @param {string} [opts.nodePath] 跑脚本的解释器,缺省 process.execPath
52
+ * @param {string} [opts.scriptPath] CLI 路径,缺省按 lib/src 同深度推导
53
+ * @param {string|null} [opts.dbPath] 透传 --db;不传则 CLI 用自己的缺省记忆库
54
+ * @param {() => string} [opts.now]
55
+ * @param {number} [opts.ringLimit]
56
+ * @param {number} [opts.maxFinished]
57
+ */
58
+ export function createT1Jobs({
59
+ store = null,
60
+ spawn: spawnFn = nodeSpawn,
61
+ nodePath = process.execPath,
62
+ scriptPath = DEFAULT_SCRIPT,
63
+ dbPath = null,
64
+ now = () => new Date().toISOString(),
65
+ ringLimit = DEFAULT_RING,
66
+ maxFinished = DEFAULT_FINISHED,
67
+ env = process.env
68
+ } = {}) {
69
+ const jobs = new Map();
70
+ // 同一个仓库同时只许一个 T1:两个进程同时往同一批页面写,claims 与 status 会
71
+ // 交错成没人能解释的中间态(T1 的写路径没有跨进程锁,这是硬事实不是保守)。
72
+ const runningByRepo = new Map();
73
+
74
+ function pushLine(job, line) {
75
+ job.logTail.push(line);
76
+ if (job.logTail.length > ringLimit) job.logTail.splice(0, job.logTail.length - ringLimit);
77
+ }
78
+
79
+ /** 回执解析:只认 JSON.parsable 的完整 stdout;解析不出来就保持 0/0/0 不瞎猜。 */
80
+ function readCounts(job) {
81
+ const text = job.stdoutAll.trim();
82
+ if (text === "") return null;
83
+ let receipt;
84
+ try { receipt = JSON.parse(text); } catch { return null; }
85
+ if (!receipt || typeof receipt !== "object") return null;
86
+ if (Array.isArray(receipt.enriched) || Array.isArray(receipt.failed)) {
87
+ const done = Array.isArray(receipt.enriched) ? receipt.enriched.length : 0;
88
+ const failed = Array.isArray(receipt.failed) ? receipt.failed.length : 0;
89
+ return { total: done + failed, done, failed };
90
+ }
91
+ if (Array.isArray(receipt.candidates)) {
92
+ return { total: receipt.candidates.length, done: 0, failed: 0 };
93
+ }
94
+ return null;
95
+ }
96
+
97
+ function trimFinished() {
98
+ const finished = [...jobs.values()].filter((j) => j.state !== "running");
99
+ if (finished.length <= maxFinished) return;
100
+ finished.sort((a, b) => String(a.finishedAt).localeCompare(String(b.finishedAt)));
101
+ for (const job of finished.slice(0, finished.length - maxFinished)) jobs.delete(job.jobId);
102
+ }
103
+
104
+ function finish(job, state, error) {
105
+ if (job.state !== "running") return; // close 与 error 可能都到,只认第一次结算
106
+ job.state = state;
107
+ job.finishedAt = now();
108
+ if (error) job.error = error;
109
+ const counts = readCounts(job);
110
+ if (counts) job.modules = counts;
111
+ const key = String(job.repoKey);
112
+ if (runningByRepo.get(key) === job.jobId) runningByRepo.delete(key);
113
+ trimFinished();
114
+ }
115
+
116
+ /**
117
+ * 起一个后台 T1。返回 {started:true, jobId} 或 {started:false, code, jobId?}——
118
+ * 路由据此分叉 200 / 409,本层不碰 HTTP。
119
+ */
120
+ function start({ repo = null, module = null, retryFailed = false } = {}) {
121
+ const rootPath = typeof repo?.root_path === "string" ? repo.root_path : "";
122
+ if (rootPath === "") return { started: false, code: "repo-invalid" };
123
+ const repoKey = String(repo.id ?? rootPath);
124
+ const existing = runningByRepo.get(repoKey);
125
+ if (existing) {
126
+ const job = jobs.get(existing);
127
+ // runningByRepo 里还留着但 job 已经结算(理论不可达)时不该把它当占用,
128
+ // 否则这个仓库会被一条幽灵记录永久锁死。
129
+ if (job && job.state === "running") return { started: false, code: "job-running", jobId: existing };
130
+ runningByRepo.delete(repoKey);
131
+ }
132
+
133
+ const jobId = randomUUID();
134
+ const job = {
135
+ jobId,
136
+ repoKey,
137
+ repo: { id: repo.id ?? null, name: repo.name ?? null, rootPath },
138
+ state: "running",
139
+ startedAt: now(),
140
+ finishedAt: null,
141
+ modules: { total: 0, done: 0, failed: 0 },
142
+ logTail: [],
143
+ error: null,
144
+ stdoutAll: "",
145
+ stderrTail: []
146
+ };
147
+ jobs.set(jobId, job);
148
+ runningByRepo.set(repoKey, jobId);
149
+
150
+ const args = [scriptPath, rootPath, "--t1", "--apply", "--json"];
151
+ if (module) args.push("--module", String(module));
152
+ if (retryFailed === true) args.push("--retry-failed");
153
+ if (dbPath) args.push("--db", String(dbPath));
154
+
155
+ let child;
156
+ try {
157
+ child = spawnFn(nodePath, args, {
158
+ // Electron 宿主里 process.execPath 是 electron 本体;不设这个变量它会去开
159
+ // 窗口而不是跑 Node 脚本(t1.js 的真实 runner 用的是同一招)。
160
+ env: { ...env, ELECTRON_RUN_AS_NODE: "1" },
161
+ stdio: ["ignore", "pipe", "pipe"],
162
+ windowsHide: true
163
+ });
164
+ } catch (err) {
165
+ finish(job, "failed", String((err && err.message) || err));
166
+ return { started: true, jobId };
167
+ }
168
+ job.child = child;
169
+
170
+ const outLines = makeLineSplitter((line) => pushLine(job, line));
171
+ const errLines = makeLineSplitter((line) => {
172
+ job.stderrTail.push(line);
173
+ if (job.stderrTail.length > 20) job.stderrTail.shift();
174
+ });
175
+ child.stdout?.setEncoding?.("utf8");
176
+ child.stderr?.setEncoding?.("utf8");
177
+ child.stdout?.on?.("data", (chunk) => {
178
+ // 回执解析要完整 JSON,而 ring buffer 只留尾巴——所以另攒一份,封顶 256 KiB
179
+ // 防止一个跑飞的任务把宿主内存吃光。
180
+ if (job.stdoutAll.length < 256 * 1024) job.stdoutAll += chunk;
181
+ outLines.push(chunk);
182
+ });
183
+ child.stderr?.on?.("data", (chunk) => errLines.push(chunk));
184
+ child.on?.("error", (err) => {
185
+ outLines.flush();
186
+ errLines.flush();
187
+ finish(job, "failed", String((err && err.message) || err));
188
+ });
189
+ child.on?.("close", (code) => {
190
+ outLines.flush();
191
+ errLines.flush();
192
+ if (code === 0) {
193
+ finish(job, "done", null);
194
+ } else {
195
+ // CLI 的拒绝与异常都走 stderr + exit 1(bin:161-171):只报 exit code 等于
196
+ // 让面板显示「失败了,不知道为啥」,所以把 stderr 尾巴当 error 带出去。
197
+ const tail = job.stderrTail.length > 0 ? job.stderrTail[job.stderrTail.length - 1] : "";
198
+ finish(job, "failed", tail || `exit code ${code}`);
199
+ }
200
+ });
201
+ return { started: true, jobId };
202
+ }
203
+
204
+ /** 状态快照(不含 child 等内部件)。未知 jobId → null。 */
205
+ function status(jobId) {
206
+ const job = jobs.get(String(jobId ?? ""));
207
+ if (!job) return null;
208
+ const view = {
209
+ jobId: job.jobId,
210
+ repo: job.repo,
211
+ state: job.state,
212
+ startedAt: job.startedAt,
213
+ modules: { ...job.modules },
214
+ logTail: job.logTail.slice()
215
+ };
216
+ if (job.finishedAt) view.finishedAt = job.finishedAt;
217
+ if (job.error) view.error = job.error;
218
+ return view;
219
+ }
220
+
221
+ function dispose() {
222
+ for (const job of jobs.values()) {
223
+ if (job.state === "running") {
224
+ try { job.child?.kill?.(); } catch { /* 进程可能已退出:dispose 不该抛 */ }
225
+ }
226
+ }
227
+ jobs.clear();
228
+ runningByRepo.clear();
229
+ }
230
+
231
+ return { start, status, dispose, scriptPath };
232
+ }
package/lib/wiki/t1.js CHANGED
@@ -24,6 +24,9 @@ import { join, dirname, sep, resolve } from "node:path";
24
24
  import { tmpdir } from "node:os";
25
25
  import { realpathSync } from "node:fs";
26
26
  import { extractHumanBlocks, injectHumanBlocks, T1_SECTION_START, T1_SECTION_END } from "./render.js";
27
+ // issue #607:拒绝文案里的 CLI 命令要给绝对路径入口(profile 装的插件没有全局
28
+ // shim,光写 `dsh-mneme-choro` 用户照抄只会 command not found)。
29
+ import { choroCliCommandFrom } from "./cli-hint.js";
27
30
 
28
31
  // 标记的单一来源在 render.js(页面格式的家);这里转出去,调用方与测试继续从
29
32
  // t1.js 取,不必知道它住在哪。
@@ -31,6 +34,9 @@ export { T1_SECTION_START, T1_SECTION_END };
31
34
  export const T1_FAIL_THRESHOLD = 2;
32
35
  export const DEFAULT_T1_MODEL = "xdpool/deepseek-v4.1-flash";
33
36
 
37
+ // #607:拒绝文案里的命令串推导一次(本文件在 src/wiki/ 与 lib/wiki/ 同一层)。
38
+ const T1_CLI = choroCliCommandFrom(import.meta.url, "<repo>");
39
+
34
40
  // 三重上限缺省(实现计划 P15):单模块预算——timeout 墙钟、工具调用次数、
35
41
  // 累计输出字节。真实成本实测见 V1(10s/次任务),这里给的是上限不是预期。
36
42
  export const DEFAULT_LIMITS = Object.freeze({
@@ -239,7 +245,7 @@ export function createT1({ store, runSession = null, limits = DEFAULT_LIMITS, no
239
245
  const rootPath = realpathSync(resolve(repoRoot));
240
246
  const repo = store.db.prepare("SELECT * FROM choro_repos WHERE root_path = ?").get(rootPath);
241
247
  if (!repo) {
242
- throw new T1Refusal("not-registered", `${rootPath} 还没有登记:先跑一次 T0(dsh-mneme-choro <repo> --apply)`);
248
+ throw new T1Refusal("not-registered", `${rootPath} 还没有登记:先跑一次 T0(${T1_CLI ?? "dsh-mneme-choro <repo> --apply"})`);
243
249
  }
244
250
 
245
251
  // 选模块:skeleton 必跑;failed 只在 retryFailed 时;enriched 跳过(重富化留待显式 --module)。
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, 10 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
4
- "version": "0.12.0",
4
+ "version": "0.13.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -57,6 +57,30 @@
57
57
  "patch": "./cordis.patch.yml"
58
58
  }
59
59
  },
60
+ "dshGo": {
61
+ "v": 1,
62
+ "title": {
63
+ "zh": "记忆库",
64
+ "en": "Memory"
65
+ },
66
+ "icon": "book",
67
+ "contributes": {
68
+ "panels": [
69
+ {
70
+ "id": "mneme",
71
+ "title": {
72
+ "zh": "记忆库",
73
+ "en": "Memory"
74
+ },
75
+ "icon": "book",
76
+ "placement": [
77
+ "session.tab",
78
+ "features"
79
+ ]
80
+ }
81
+ ]
82
+ }
83
+ },
60
84
  "peerDependencies": {
61
85
  "@deepseek-ai/cordis": "^4.0.1",
62
86
  "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
@@ -478,7 +478,10 @@ export function createStandaloneApi({ service, store, config = {}, logger, setti
478
478
  const listFilters = { includeArchived, ...(occurredFrom ? { occurredFrom } : {}), ...(occurredTo ? { occurredTo } : {}) };
479
479
  const items = service.toApiList(service.list({ type, limit, offset, order, minImportance, source, ...listFilters }));
480
480
  // Total honors the same filters so pager math stays correct.
481
- sendJson(res, 200, { items, total: service.count(type, { minImportance, source, ...listFilters }) });
481
+ // #597:typeCounts 与宿主内 /list 同口径(同一份筛选、不带 type 的全库分布),
482
+ // 两张面只许剩鉴权与路径不同。
483
+ const countFilters = { minImportance, source, ...listFilters };
484
+ sendJson(res, 200, { items, total: service.count(type, countFilters), typeCounts: service.countByType(countFilters) });
482
485
  return;
483
486
  }
484
487