@futdevpro/fdp-agent-memory 1.1.21 → 1.1.112
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/build/package.json +2 -2
- package/build/src/_cli/_commands/find-duplicates.command.js +41 -16
- package/build/src/_cli/_commands/scan-projects.command.js +113 -113
- package/build/src/_cli/_commands/scan.command.js +2 -0
- package/build/src/_cli/_commands/serve.command.js +110 -10
- package/build/src/_cli/_services/fam-client.service.js +25 -1
- package/build/src/_cli/register-commands.js +28 -0
- package/build/src/_collections/config-catalog.const.js +152 -33
- package/build/src/_collections/error-codes.const.js +2 -0
- package/build/src/_collections/fam-console.util.js +38 -260
- package/build/src/_collections/fam-db-models.const.js +2 -0
- package/build/src/_collections/fam-entry-bootstrap.util.js +20 -6
- package/build/src/_collections/fam-error-context.util.js +1 -0
- package/build/src/_collections/fam-mcp-bridge.util.js +15 -1
- package/build/src/_collections/fam-operation-queue.service.js +85 -0
- package/build/src/_collections/fam-project-discovery.util.js +148 -0
- package/build/src/_collections/fam-request-origin.util.js +41 -0
- package/build/src/_collections/fam-retry.util.js +48 -0
- package/build/src/_collections/fam-scan-progress-sink.service.js +26 -0
- package/build/src/_collections/title.const.js +26 -26
- package/build/src/_enums/fam-rule-scope.type-enum.js +25 -0
- package/build/src/_models/data-models/fam-entry.data-model.js +29 -0
- package/build/src/_models/data-models/fam-memory.data-model.js +5 -0
- package/build/src/_models/data-models/fam-rules.data-model.js +24 -0
- package/build/src/_models/data-models/fam-scan-job.data-model.js +73 -0
- package/build/src/_models/data-models/fam-scope.data-model.js +9 -0
- package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +1 -1
- package/build/src/_modules/embedding/_services/fam-dedup-warn.control-service.js +40 -0
- package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +74 -16
- package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +50 -0
- package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +32 -6
- package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +142 -9
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +32 -1
- package/build/src/_modules/embedding/_services/fam-hydration-coordinator.control-service.js +159 -0
- package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +41 -4
- package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +67 -12
- package/build/src/_modules/embedding/index.js +5 -1
- package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +10 -0
- package/build/src/_modules/ingest/_collections/fam-frontmatter.util.js +134 -0
- package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +30 -0
- package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +5 -2
- package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +55 -29
- package/build/src/_modules/ingest/_collections/fam-split-chunker.util.js +139 -0
- package/build/src/_modules/ingest/_models/interfaces/fam-scan-job.interface.js +2 -0
- package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +10 -0
- package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +68 -1
- package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +1 -1
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +177 -37
- package/build/src/_modules/ingest/_services/fam-scan-job.control-service.js +429 -0
- package/build/src/_modules/ingest/_services/fam-scan-job.data-service.js +34 -0
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +6 -4
- package/build/src/_modules/ingest/index.js +6 -1
- package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +1 -1
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +264 -41
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +71 -1
- package/build/src/_modules/mcp/_services/fam-write-tool.service.js +36 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
- package/build/src/_modules/retrieval/_collections/fam-lexical-match.util.js +69 -0
- package/build/src/_modules/retrieval/_collections/fam-memory-activation.util.js +66 -0
- package/build/src/_modules/retrieval/_collections/fam-rule-injection.util.js +125 -0
- package/build/src/_modules/retrieval/_collections/fam-rule-propagation.util.js +95 -0
- package/build/src/_modules/retrieval/_collections/fam-rule-scope-context.util.js +74 -0
- package/build/src/_modules/retrieval/_services/fam-global-rule.control-service.js +82 -0
- package/build/src/_modules/retrieval/_services/fam-memory-cold-search.control-service.js +121 -0
- package/build/src/_modules/retrieval/_services/fam-memory-dormancy.control-service.js +99 -0
- package/build/src/_modules/retrieval/_services/fam-memory-reactivation.control-service.js +68 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +15 -2
- package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +197 -5
- package/build/src/_modules/retrieval/_services/fam-rule-injection.control-service.js +102 -0
- package/build/src/_modules/retrieval/index.js +12 -1
- package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +134 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +423 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
- package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +25 -0
- package/build/src/_modules/scope-reference/index.js +4 -1
- package/build/src/_routes/server/api/api.controller.js +324 -23
- package/build/src/_routes/server/api/fam-doctor.control-service.js +156 -0
- package/build/src/_routes/server/config/config.control-service.js +2 -2
- package/build/src/app.server.js +29 -27
- package/client-dist/{chunk-YXHWCJ5O.js → chunk-GF3FJP7E.js} +71 -71
- package/client-dist/chunk-NMUCMQY6.js +1 -0
- package/client-dist/favicon.ico +0 -0
- package/client-dist/index.html +3 -3
- package/client-dist/main-YK4YIVAQ.js +2 -0
- package/package.json +2 -2
- package/build/src/_cli/_collections/fam-project-discovery.util.js +0 -98
- package/build/src/_collections/fam-heap-guard.control-service.js +0 -176
- package/client-dist/chunk-I77GXVAQ.js +0 -1
- package/client-dist/main-PJPEDVJT.js +0 -2
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Api_Controller = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
|
|
4
6
|
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
5
7
|
const nts_dynamo_1 = require("@futdevpro/nts-dynamo");
|
|
8
|
+
const nts_dynamo_2 = require("@futdevpro/nts-dynamo");
|
|
6
9
|
const fam_version_const_1 = require("../../../_collections/fam-version.const");
|
|
7
|
-
const fam_heap_guard_control_service_1 = require("../../../_collections/fam-heap-guard.control-service");
|
|
8
10
|
const fam_table_type_enum_1 = require("../../../_enums/fam-table.type-enum");
|
|
9
11
|
const fam_config_level_type_enum_1 = require("../../../_enums/fam-config-level.type-enum");
|
|
12
|
+
const fam_operation_queue_service_1 = require("../../../_collections/fam-operation-queue.service");
|
|
13
|
+
const fam_scan_job_control_service_1 = require("../../../_modules/ingest/_services/fam-scan-job.control-service");
|
|
14
|
+
const fam_vector_search_control_service_1 = require("../../../_modules/embedding/_services/fam-vector-search.control-service");
|
|
10
15
|
const config_control_service_1 = require("../config/config.control-service");
|
|
16
|
+
const errors_control_service_1 = require("../errors/errors.control-service");
|
|
17
|
+
const fam_error_data_model_1 = require("../../../_models/data-models/fam-error.data-model");
|
|
11
18
|
const embedding_1 = require("../../../_modules/embedding");
|
|
12
19
|
const ingest_1 = require("../../../_modules/ingest");
|
|
13
20
|
const mcp_1 = require("../../../_modules/mcp");
|
|
21
|
+
const fam_doctor_control_service_1 = require("./fam-doctor.control-service");
|
|
22
|
+
const retrieval_1 = require("../../../_modules/retrieval");
|
|
23
|
+
const fam_request_origin_util_1 = require("../../../_collections/fam-request-origin.util");
|
|
14
24
|
/** A UI/CLI lapozás CCAP-örökség konstansai (dsgn-010/dsgn-011 §4.1). */
|
|
15
25
|
const DEFAULT_PAGE_SIZE = 50;
|
|
16
26
|
const MAX_PAGE_SIZE = 500;
|
|
@@ -51,6 +61,9 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
51
61
|
this.writeEndpoint(),
|
|
52
62
|
this.capabilitiesEndpoint(),
|
|
53
63
|
this.healthEndpoint(),
|
|
64
|
+
this.doctorEndpoint(),
|
|
65
|
+
this.rulesInjectEndpoint(),
|
|
66
|
+
this.rulesPropagateEndpoint(),
|
|
54
67
|
this.itemsEndpoint(),
|
|
55
68
|
this.statsEndpoint(),
|
|
56
69
|
this.statsTableEndpoint(),
|
|
@@ -58,6 +71,12 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
58
71
|
this.duplicatesEndpoint(),
|
|
59
72
|
this.configGetEndpoint(),
|
|
60
73
|
this.configSetEndpoint(),
|
|
74
|
+
this.scanStartEndpoint(),
|
|
75
|
+
this.scanStatusEndpoint(),
|
|
76
|
+
this.scanJobsEndpoint(),
|
|
77
|
+
this.scanCancelEndpoint(),
|
|
78
|
+
this.errorsSummaryEndpoint(),
|
|
79
|
+
this.errorsEndpoint(),
|
|
61
80
|
];
|
|
62
81
|
}
|
|
63
82
|
// =========================================================================
|
|
@@ -108,6 +127,14 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
108
127
|
// Heap-load-shed (OOM-hibakezelés): a NEHÉZ write-eket (scan/import/re-embed) critical
|
|
109
128
|
// memória-nyomásnál elutasítjuk (503), MIELŐTT egy nagy allokáció átlökné a plafont.
|
|
110
129
|
this.assertHeapCapacity(String(input.operation));
|
|
130
|
+
// FEAT-008 (user-FR 2026-06-22): a NEHÉZ write-ek SZERVER-OLDALI QUEUE-n mennek át → a szerver
|
|
131
|
+
// szerializálja őket (egyszerre egy), így több agent egyidejű write-jai sorba rakódnak, és nincs
|
|
132
|
+
// párhuzamos scan-feldolgozás (a war-factory/warbots átfedés gyökér-megoldása). A könnyű ops
|
|
133
|
+
// (create/update/delete) bypass-olják a queue-t. Lásd `FAM_OperationQueue_Service`.
|
|
134
|
+
if (Api_Controller.HEAVY_WRITE_OPERATIONS.includes(String(input.operation))) {
|
|
135
|
+
return fam_operation_queue_service_1.FAM_OperationQueue_Service.getInstance()
|
|
136
|
+
.enqueue(() => mcp_1.FAM_WriteTool_Service.getInstance().handle(input));
|
|
137
|
+
}
|
|
111
138
|
return mcp_1.FAM_WriteTool_Service.getInstance().handle(input);
|
|
112
139
|
});
|
|
113
140
|
},
|
|
@@ -130,9 +157,235 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
130
157
|
],
|
|
131
158
|
});
|
|
132
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* `POST /scan/start` (FEAT-008) — szerver-oldali multi-projekt scan-job INDÍTÁSA. Body: explicit `projects` VAGY
|
|
162
|
+
* `root` (felderítés a SZERVEREN) + opcionális cooldown-override. AZONNAL visszaad `{ jobId, total }` (a hosszú
|
|
163
|
+
* feldolgozás háttérben fut → nincs Node-requestTimeout). A progress a `GET /scan/status/:jobId`-on.
|
|
164
|
+
*/
|
|
165
|
+
scanStartEndpoint() {
|
|
166
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
167
|
+
name: 'scanStart',
|
|
168
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.post,
|
|
169
|
+
endpoint: '/scan/start',
|
|
170
|
+
tasks: [
|
|
171
|
+
async (req, res) => {
|
|
172
|
+
await this.run(res, async () => {
|
|
173
|
+
const input = (req.body ?? {});
|
|
174
|
+
// Aláírás-feloldás (user-FR 2026-06-25): a `X-FAM-*` header VAGY a body `origin` → KI indította
|
|
175
|
+
// a scant (több egyidejű workspace-session beazonosítható). A job-on + a lista-nézetben látszik.
|
|
176
|
+
input.origin = fam_request_origin_util_1.FAM_RequestOrigin_Util.fromRequest(req, input.origin);
|
|
177
|
+
return fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().start(input);
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/** `GET /scan/status/:jobId` (FEAT-008) — egy scan-job állapota (progress + per-projekt eredmények). 404, ha ismeretlen. */
|
|
184
|
+
scanStatusEndpoint() {
|
|
185
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
186
|
+
name: 'scanStatus',
|
|
187
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
188
|
+
endpoint: '/scan/status/:jobId',
|
|
189
|
+
tasks: [
|
|
190
|
+
async (req, res) => {
|
|
191
|
+
await this.run(res, async () => {
|
|
192
|
+
const job = fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().getStatus(String(req.params.jobId));
|
|
193
|
+
if (!job) {
|
|
194
|
+
throw new fsm_dynamo_1.DyFM_Error({
|
|
195
|
+
errorCode: 'FAM-SCAN-JOB-404',
|
|
196
|
+
message: `Ismeretlen scan-job: '${String(req.params.jobId)}' (lehet, hogy a szerver `
|
|
197
|
+
+ 'újraindult — a job-ok in-memory tárolódnak).',
|
|
198
|
+
issuer: this.issuer,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return job;
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* `GET /scan/jobs` (FEAT-008 Phase 5 — „jobb job kezelés") — AZ ÖSSZES (futó + befejezett) scan-job KOMPAKT listája,
|
|
209
|
+
* legfrissebb elöl. Egy elárvult/futó job a `jobId` ISMERETE NÉLKÜL is felfedezhető (+ a stall-jelzés is látszik).
|
|
210
|
+
*/
|
|
211
|
+
scanJobsEndpoint() {
|
|
212
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
213
|
+
name: 'scanJobs',
|
|
214
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
215
|
+
endpoint: '/scan/jobs',
|
|
216
|
+
tasks: [
|
|
217
|
+
async (req, res) => {
|
|
218
|
+
await this.run(res, async () => ({ jobs: fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().listJobs() }));
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* `POST /scan/cancel/:jobId` (FEAT-008 Phase 5) — egy FUTÓ scan-job megszakítás-kérése (a processor a következő projekt
|
|
225
|
+
* ELŐTT ellenőrzi). `{ cancelled: true }`, ha a job létezett + futott; `false`, ha ismeretlen/már terminál.
|
|
226
|
+
*/
|
|
227
|
+
scanCancelEndpoint() {
|
|
228
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
229
|
+
name: 'scanCancel',
|
|
230
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.post,
|
|
231
|
+
endpoint: '/scan/cancel/:jobId',
|
|
232
|
+
tasks: [
|
|
233
|
+
async (req, res) => {
|
|
234
|
+
await this.run(res, async () => ({
|
|
235
|
+
cancelled: fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().cancel(String(req.params.jobId)),
|
|
236
|
+
}));
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* `GET /errors` (→ `/api/errors`, user-FR 2026-06-24: „kéne legyenek végpontok, amivel le tudjuk érni az errorokat")
|
|
243
|
+
* — a `fam_errors` rekordok KOMPAKT, FAM-natív nézete a `/api` felületen (a built-in `/api/server/errors` nyers/bő
|
|
244
|
+
* Dynamo-objektumai helyett). Query: `code` (errorCode-részlet, case-insensitive), `unhandledOnly` (`true` → csak nem
|
|
245
|
+
* kezeltek), `limit` (default 50, max 500). Sorrend: occurrence DESC, majd recency. A `contextSnapshot` (benne a
|
|
246
|
+
* `failedSample` + provider/table/operation) is kijön — így az LM-Studio-omlasztó tartalom-séma diagnosztizálható.
|
|
247
|
+
*/
|
|
248
|
+
errorsEndpoint() {
|
|
249
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
250
|
+
name: 'errorsList',
|
|
251
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
252
|
+
endpoint: '/errors',
|
|
253
|
+
tasks: [
|
|
254
|
+
async (req, res) => {
|
|
255
|
+
await this.run(res, async () => {
|
|
256
|
+
const unhandledOnly = req.query.unhandledOnly === 'true';
|
|
257
|
+
const codeFilter = req.query.code ? String(req.query.code).toLowerCase() : '';
|
|
258
|
+
const limit = Math.min(500, Math.max(1, Number(req.query.limit) || 50));
|
|
259
|
+
const all = await errors_control_service_1.FAM_Error_ControlService.getInstance()
|
|
260
|
+
.listErrors(unhandledOnly ? { handled: false } : undefined);
|
|
261
|
+
const filtered = all
|
|
262
|
+
.filter((r) => !codeFilter || (r.errorCode ?? '').toLowerCase().includes(codeFilter))
|
|
263
|
+
.sort((a, b) => fam_error_data_model_1.FAM_Error_DataModel.occurrenceCount(b) - fam_error_data_model_1.FAM_Error_DataModel.occurrenceCount(a)
|
|
264
|
+
|| String(b._id ?? '').localeCompare(String(a._id ?? '')));
|
|
265
|
+
return {
|
|
266
|
+
total: filtered.length,
|
|
267
|
+
returned: Math.min(limit, filtered.length),
|
|
268
|
+
errors: filtered.slice(0, limit).map((r) => this.mapErrorCompact(r)),
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* `GET /errors/summary` (→ `/api/errors/summary`) — errorCode szerint CSOPORTOSÍTOTT triage-nézet: kódonként az
|
|
277
|
+
* occurrence-összeg + distinct rekordszám + utolsó üzenet + egy reprezentatív `failedSample`. Gyors „mi omlik a
|
|
278
|
+
* legtöbbet" áttekintés (pl. melyik `FAM-EMB-PROVIDER-002` séma viszi el az LM Studio-t).
|
|
279
|
+
*/
|
|
280
|
+
errorsSummaryEndpoint() {
|
|
281
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
282
|
+
name: 'errorsSummary',
|
|
283
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
284
|
+
endpoint: '/errors/summary',
|
|
285
|
+
tasks: [
|
|
286
|
+
async (req, res) => {
|
|
287
|
+
await this.run(res, async () => {
|
|
288
|
+
const all = await errors_control_service_1.FAM_Error_ControlService.getInstance().listErrors();
|
|
289
|
+
const groups = {};
|
|
290
|
+
for (const r of all) {
|
|
291
|
+
const code = r.errorCode ?? '(no-code)';
|
|
292
|
+
const group = groups[code] ?? { errorCode: code, occurrences: 0, records: 0 };
|
|
293
|
+
group.occurrences += fam_error_data_model_1.FAM_Error_DataModel.occurrenceCount(r);
|
|
294
|
+
group.records += 1;
|
|
295
|
+
group.lastMessage = r.message ?? group.lastMessage;
|
|
296
|
+
const sample = r.contextSnapshot?.failedSample;
|
|
297
|
+
if (sample && !group.sampleFailedSample) {
|
|
298
|
+
group.sampleFailedSample = String(sample).slice(0, 400);
|
|
299
|
+
}
|
|
300
|
+
groups[code] = group;
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
groups: Object.values(groups).sort((a, b) => b.occurrences - a.occurrences),
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/** Egy `fam_errors` rekord → KOMPAKT, FAM-natív hiba-nézet (a nyers Dynamo-mezők/óriás-stack helyett a diagnózishoz kell). */
|
|
311
|
+
mapErrorCompact(record) {
|
|
312
|
+
return {
|
|
313
|
+
id: String(record._id ?? ''),
|
|
314
|
+
errorCode: record.errorCode,
|
|
315
|
+
level: typeof Reflect.get(record, 'level') === 'string' ? String(Reflect.get(record, 'level')) : undefined,
|
|
316
|
+
message: record.message,
|
|
317
|
+
occurrenceCount: fam_error_data_model_1.FAM_Error_DataModel.occurrenceCount(record),
|
|
318
|
+
handled: record.handled ?? false,
|
|
319
|
+
issuer: record.issuer,
|
|
320
|
+
context: record.contextSnapshot,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
133
323
|
// =========================================================================
|
|
134
324
|
// /health — CLI remote reachability-check (dsgn-010 §9)
|
|
135
325
|
// =========================================================================
|
|
326
|
+
/**
|
|
327
|
+
* `GET /doctor` (#2 diagnosztikai végpont, user-FR 2026-06-24) — SZERVER-OLDALI aggregált health-report
|
|
328
|
+
* (env/mongodb/config/embedding/vector/collections + scope-integritás), a `fam doctor` REST-tükre (ADR-015
|
|
329
|
+
* server-centric: a CLI/kliens/UI/külső eszköz EZT hívja, nem 6 külön capability-t). Opcionális `?skip=embedding,vector`.
|
|
330
|
+
*/
|
|
331
|
+
doctorEndpoint() {
|
|
332
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
333
|
+
name: 'doctor',
|
|
334
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
335
|
+
endpoint: '/doctor',
|
|
336
|
+
tasks: [
|
|
337
|
+
async (req, res) => {
|
|
338
|
+
await this.run(res, async () => {
|
|
339
|
+
const skipRaw = typeof req.query.skip === 'string' ? req.query.skip : '';
|
|
340
|
+
const skip = skipRaw.split(',').map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
341
|
+
return fam_doctor_control_service_1.FAM_Doctor_ControlService.getInstance().runDoctor({ skip: skip });
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* `POST /rules/inject` (Rules-Management MP-2) — a SessionStart-hook (+ a `read`-reflex) szerver-oldali rule-assembly-je:
|
|
349
|
+
* a `cwd`-ből (vagy explicit felülírásból) detektált tier-kontextusra `importance`-rangsorolt, char-budgetbe illesztett
|
|
350
|
+
* always-apply rule-blokk a „(…and N more)" záró sorral. A hook DUMB marad (csak `additionalContext`-be teszi a `block`-ot).
|
|
351
|
+
* Body: `{ cwd?, isFdp?, inProject?, projectType?, topics?, budgetChars? }`.
|
|
352
|
+
*/
|
|
353
|
+
rulesInjectEndpoint() {
|
|
354
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
355
|
+
name: 'rulesInject',
|
|
356
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.post,
|
|
357
|
+
endpoint: '/rules/inject',
|
|
358
|
+
tasks: [
|
|
359
|
+
async (req, res) => {
|
|
360
|
+
await this.run(res, async () => {
|
|
361
|
+
const input = (req.body ?? {});
|
|
362
|
+
return retrieval_1.FAM_RuleInjection_ControlService.getInstance().assembleInjection(input);
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* `POST /rules/propagate` (Rules-Management MP-5) — a kontextusra érvényes szabályok GENERÁLT, marker-keretezett
|
|
370
|
+
* `CLAUDE.md`-blokkja (propagáció a FAM-NÉLKÜLI projekt-agenteknek). Budget nélkül (minden alkalmazható szabály),
|
|
371
|
+
* dedup `ruleId`/`derivedFrom` szerint. A hívó (sync-tool/CLI) a `block`-ot a projekt-`CLAUDE.md` markerei közé illeszti.
|
|
372
|
+
* Body: `{ cwd?, isFdp?, inProject?, projectType?, topics? }`.
|
|
373
|
+
*/
|
|
374
|
+
rulesPropagateEndpoint() {
|
|
375
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
376
|
+
name: 'rulesPropagate',
|
|
377
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.post,
|
|
378
|
+
endpoint: '/rules/propagate',
|
|
379
|
+
tasks: [
|
|
380
|
+
async (req, res) => {
|
|
381
|
+
await this.run(res, async () => {
|
|
382
|
+
const input = (req.body ?? {});
|
|
383
|
+
return retrieval_1.FAM_RuleInjection_ControlService.getInstance().assemblePropagation(input);
|
|
384
|
+
});
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
}
|
|
136
389
|
/** `GET /health` — a CLI `--server-url` elérhetőség-próbája (dsgn-010 §9). */
|
|
137
390
|
healthEndpoint() {
|
|
138
391
|
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
@@ -141,12 +394,19 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
141
394
|
endpoint: '/health',
|
|
142
395
|
tasks: [
|
|
143
396
|
async (req, res) => {
|
|
144
|
-
// A heap-nyomás IS kimegy a health-en (observability — a
|
|
145
|
-
const memory =
|
|
397
|
+
// A heap-nyomás IS kimegy a health-en (observability) — a BEDROCK DyNTS_MemoryGuard pressure-éből.
|
|
398
|
+
const memory = nts_dynamo_2.DyNTS_MemoryGuard.getInstance().pressure();
|
|
399
|
+
// Hidratálás-állapot (user-FR 2026-06-22): a boot-hidratálás időbe telik → a hívó lássa, kész-e.
|
|
400
|
+
const hydration = fam_vector_search_control_service_1.FAM_VectorSearch_ControlService.getInstance().hydrationSummary();
|
|
401
|
+
// Readiness-coordinator per-tár állapot (pending/hydrating/ready/failed) — a read-queue diagnosztikája.
|
|
402
|
+
const readiness = embedding_1.FAM_HydrationCoordinator_ControlService.getInstance().snapshot();
|
|
146
403
|
res.send({
|
|
147
404
|
ok: true, service: 'fdp-agent-memory', version: fam_version_const_1.FAM_VERSION, uptime: process.uptime(),
|
|
148
|
-
|
|
149
|
-
|
|
405
|
+
ready: hydration.complete,
|
|
406
|
+
memory: { level: memory.level, heapUsedMb: memory.heapUsedMb, heapLimitMb: memory.heapLimitMb,
|
|
407
|
+
heapPct: memory.heapPct, rssMb: memory.rssMb, gcFraction: memory.gcFraction },
|
|
408
|
+
hydration: hydration,
|
|
409
|
+
readiness: readiness,
|
|
150
410
|
});
|
|
151
411
|
},
|
|
152
412
|
],
|
|
@@ -193,13 +453,27 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
193
453
|
if (req.query.ingestRunId) {
|
|
194
454
|
filter.ingestRunId = String(req.query.ingestRunId);
|
|
195
455
|
}
|
|
196
|
-
const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
|
|
197
|
-
const all = await dataService.findHydratableList(filter);
|
|
198
456
|
const pageSize = Math.min(this.parseNumber(req.query.pageSize, DEFAULT_PAGE_SIZE), MAX_PAGE_SIZE);
|
|
199
457
|
const page = Math.max(this.parseNumber(req.query.page, 1), 1);
|
|
200
458
|
const start = (page - 1) * pageSize;
|
|
201
|
-
|
|
202
|
-
|
|
459
|
+
// FIX (OOM): DB-OLDALI lapozás + `contentVector` projekció-kizárás. KORÁBBAN `findHydratableList({})` a TELJES
|
|
460
|
+
// tárat (126k+ teljes doc + 2560-dim vektor) memóriába töltötte ÉS memóriában szeletelt → a UI-polling többször
|
|
461
|
+
// GB-okat allokált → OOM + Mongo-flapping. Most: `countDocuments` + `skip/limit` + a nagy vektor-mező kihagyva.
|
|
462
|
+
const collection = this.entryCollection(registryEntry);
|
|
463
|
+
const total = await collection.countDocuments(filter);
|
|
464
|
+
const items = await collection
|
|
465
|
+
.find(filter, { projection: { contentVector: 0 } })
|
|
466
|
+
.sort({ __lastModified: -1 })
|
|
467
|
+
.skip(start)
|
|
468
|
+
.limit(pageSize)
|
|
469
|
+
.toArray();
|
|
470
|
+
return { table: table, total: total, page: page, pageSize: pageSize, items: items };
|
|
471
|
+
}
|
|
472
|
+
/** Egy fő RAG-tár mongoose-collection-je (a DS-példányosítás regisztrálja a modellt; onnan a valós collection). */
|
|
473
|
+
entryCollection(registryEntry) {
|
|
474
|
+
new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
|
|
475
|
+
const model = mongoose_1.default.models[registryEntry.dataParams.dataName];
|
|
476
|
+
return model.collection;
|
|
203
477
|
}
|
|
204
478
|
// =========================================================================
|
|
205
479
|
// /stats · /stats/:table — high-level stats aggregátum (dsgn-011 §3/§4.4)
|
|
@@ -234,7 +508,11 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
234
508
|
],
|
|
235
509
|
});
|
|
236
510
|
}
|
|
237
|
-
/**
|
|
511
|
+
/**
|
|
512
|
+
* A tár-stats aggregátum (aktív elem-szám + embedding-státusz + utolsó ingest) — dsgn-011 §3/§4.4.
|
|
513
|
+
* FIX (OOM): `countDocuments` + `$group`-aggregáció + 1-doc last-modified projekció — KORÁBBAN `findHydratableList({})`
|
|
514
|
+
* a TELJES tárat memóriába töltötte (a UI stats-pollingja → ismételt GB-allokáció → OOM). Most semmit nem hidratál.
|
|
515
|
+
*/
|
|
238
516
|
async collectTableStats(tables) {
|
|
239
517
|
const result = [];
|
|
240
518
|
for (const table of tables) {
|
|
@@ -242,24 +520,35 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
242
520
|
if (!registryEntry) {
|
|
243
521
|
continue;
|
|
244
522
|
}
|
|
245
|
-
const
|
|
246
|
-
const
|
|
523
|
+
const collection = this.entryCollection(registryEntry);
|
|
524
|
+
const count = await collection.countDocuments({});
|
|
525
|
+
// Embedding-státusz bontás Mongo-aggregációval ($group), NEM a doc-ok memóriába töltésével.
|
|
247
526
|
const byStatus = { pending: 0, completed: 0, error: 0 };
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
if (
|
|
254
|
-
|
|
527
|
+
const grouped = await collection
|
|
528
|
+
.aggregate([{ $group: { _id: '$embeddingStatus', n: { $sum: 1 } } }])
|
|
529
|
+
.toArray();
|
|
530
|
+
for (const group of grouped) {
|
|
531
|
+
const status = group._id ?? 'pending';
|
|
532
|
+
if (status in byStatus) {
|
|
533
|
+
byStatus[status] += group.n;
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
byStatus.pending += group.n;
|
|
255
537
|
}
|
|
256
538
|
}
|
|
539
|
+
// Utolsó módosítás: EGYETLEN doc (a legfrissebb), csak az időbélyeg-mezőre projektálva.
|
|
540
|
+
const newest = await collection
|
|
541
|
+
.find({}, { projection: { __lastModified: 1 } })
|
|
542
|
+
.sort({ __lastModified: -1 })
|
|
543
|
+
.limit(1)
|
|
544
|
+
.toArray();
|
|
545
|
+
const lastModifiedMs = newest[0]?.__lastModified ? new Date(newest[0].__lastModified).getTime() : 0;
|
|
257
546
|
result.push({
|
|
258
547
|
table: table,
|
|
259
|
-
count:
|
|
548
|
+
count: count,
|
|
260
549
|
embeddingStatus: byStatus,
|
|
261
550
|
lastModified: lastModifiedMs ? new Date(lastModifiedMs).toISOString() : null,
|
|
262
|
-
status:
|
|
551
|
+
status: count ? 'ok' : 'empty',
|
|
263
552
|
});
|
|
264
553
|
}
|
|
265
554
|
return result;
|
|
@@ -313,12 +602,24 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
313
602
|
async (req, res) => {
|
|
314
603
|
await this.run(res, async () => {
|
|
315
604
|
const table = this.parseTable(req.params.table);
|
|
605
|
+
// A vessző-szeparált lista-paraméterek (projects/excludeProjects) tömbbé bontása.
|
|
606
|
+
const parseList = (value) => (typeof value === 'string' && value.length
|
|
607
|
+
? value.split(',').map((item) => item.trim()).filter((item) => item.length > 0)
|
|
608
|
+
: undefined);
|
|
316
609
|
return embedding_1.FAM_DuplicateScan_ControlService.getInstance().scan({
|
|
317
610
|
table: table,
|
|
318
611
|
threshold: req.query.threshold !== undefined ? Number(req.query.threshold) : undefined,
|
|
319
612
|
neighbors: req.query.neighbors !== undefined ? this.parseNumber(req.query.neighbors, 10) : undefined,
|
|
320
613
|
maxEntries: req.query.maxEntries !== undefined ? this.parseNumber(req.query.maxEntries, 3000) : undefined,
|
|
321
614
|
maxClusters: req.query.maxClusters !== undefined ? this.parseNumber(req.query.maxClusters, 100) : undefined,
|
|
615
|
+
pathRegex: typeof req.query.pathRegex === 'string' && req.query.pathRegex.length ? req.query.pathRegex : undefined,
|
|
616
|
+
excludePathRegex: typeof req.query.excludePathRegex === 'string' && req.query.excludePathRegex.length
|
|
617
|
+
? req.query.excludePathRegex : undefined,
|
|
618
|
+
excludeRootRegex: typeof req.query.excludeRootRegex === 'string' && req.query.excludeRootRegex.length
|
|
619
|
+
? req.query.excludeRootRegex : undefined,
|
|
620
|
+
projects: parseList(req.query.projects),
|
|
621
|
+
excludeProjects: parseList(req.query.excludeProjects),
|
|
622
|
+
minProjects: req.query.minProjects !== undefined ? this.parseNumber(req.query.minProjects, 0) : undefined,
|
|
322
623
|
});
|
|
323
624
|
});
|
|
324
625
|
},
|
|
@@ -434,14 +735,14 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
434
735
|
if (!Api_Controller.HEAVY_WRITE_OPERATIONS.includes(operation)) {
|
|
435
736
|
return;
|
|
436
737
|
}
|
|
437
|
-
const guard =
|
|
738
|
+
const guard = nts_dynamo_2.DyNTS_MemoryGuard.getInstance();
|
|
438
739
|
if (!guard.shouldShedLoad()) {
|
|
439
740
|
return;
|
|
440
741
|
}
|
|
441
742
|
const pressure = guard.pressure();
|
|
442
743
|
throw new fsm_dynamo_1.DyFM_Error({
|
|
443
744
|
errorCode: 'FAM-MEM-SHED-001',
|
|
444
|
-
message: `Memória-nyomás (${pressure.
|
|
745
|
+
message: `Memória-nyomás (${pressure.heapUsedMb}/${pressure.heapLimitMb} MB, ${pressure.heapPct}%, `
|
|
445
746
|
+ `GC ${Math.round(pressure.gcFraction * 100)}%) — a nehéz '${operation}' művelet ideiglenesen elutasítva `
|
|
446
747
|
+ '(load-shed). Próbáld újra, amikor a nyomás enyhül (lásd /api/logs), vagy szakaszold a scant (cooldown).',
|
|
447
748
|
issuer: this.issuer,
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_Doctor_ControlService = void 0;
|
|
4
|
+
const mcp_1 = require("../../../_modules/mcp");
|
|
5
|
+
const scope_reference_1 = require("../../../_modules/scope-reference");
|
|
6
|
+
/** A 6 doctor al-check ↔ capability-eszköz mátrix (dsgn-010 §5.2; a `fam doctor` CLI-mátrixával egyező). */
|
|
7
|
+
const CHECK_MATRIX = [
|
|
8
|
+
{ name: 'env', capability: 'validate_environment', fixHint: 'Hiányzó env (pl. MONGODB_URI) → `fam init` vagy állítsd be a `.env`-ben.' },
|
|
9
|
+
{ name: 'mongodb', capability: 'test_mongodb_connection', fixHint: 'Mongo nem elérhető → indítsd a Mongo-t, ellenőrizd a portot / MONGODB_URI-t.' },
|
|
10
|
+
{ name: 'config', capability: 'get_config_status', fixHint: 'Nincs fam_config global rekord → `fam init` vagy `fam config set …`.' },
|
|
11
|
+
{ name: 'embedding', capability: 'test_embedding_provider', fixHint: 'OpenAI 401 → ellenőrizd OPENAI_API_KEY-t; LM Studio: indítsd a szervert + tölts be modellt.' },
|
|
12
|
+
{ name: 'vector', capability: 'test_vector_search', fixHint: 'Vektor-pool üres/nem-hidratált → `fam write … re-embed` vagy ellenőrizd a Mongo-persist réteget.' },
|
|
13
|
+
{ name: 'collections', capability: 'get_table_stats', fixHint: 'Hiányzó collection → indítsd a szervert egyszer (auto-create), majd `fam doctor`.' },
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* `FAM_Doctor_ControlService` (#2 diagnosztikai végpont, user-FR 2026-06-24) — a `fam doctor` aggregátorának
|
|
17
|
+
* **SZERVER-OLDALI** változata (server-centric / thin-CLI, ADR-015). A 6 capability-diagnosztikát IN-PROCESS futtatja
|
|
18
|
+
* (`FAM_CapabilitiesTool_Service.handle`, nem HTTP-round-trip), és aggregált health-report-ot ad (+ best-effort
|
|
19
|
+
* scope-integritás). A `/api/doctor` ezt szolgálja ki; a CLI/kliens/UI/külső eszköz ezt hívhatja (nem 6 külön
|
|
20
|
+
* capability-hívást). Az `interpret` PURE (a capability szemantikus hiba-jelzéseit — `available/reachable/ok:false`,
|
|
21
|
+
* `missing[]` — fail-ként ismeri fel, nem csak a throw-t; a CLI csak a throw-t fogta).
|
|
22
|
+
*/
|
|
23
|
+
class FAM_Doctor_ControlService {
|
|
24
|
+
static _instance;
|
|
25
|
+
static getInstance() {
|
|
26
|
+
if (!FAM_Doctor_ControlService._instance) {
|
|
27
|
+
FAM_Doctor_ControlService._instance = new FAM_Doctor_ControlService();
|
|
28
|
+
}
|
|
29
|
+
return FAM_Doctor_ControlService._instance;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A capability-eredmény SZEMANTIKUS hiba-jelzése (PURE, tesztelhető): a technikailag sikeres hívás is jelezhet
|
|
33
|
+
* hibát (`available:false` = embedding down; `reachable:false` = Mongo down; `ok:false`; `missing[]` = hiányzó env).
|
|
34
|
+
* A wrapper-réteget (`data`/`result`) is megnézi. `true` = egészségtelen (→ FAIL).
|
|
35
|
+
*/
|
|
36
|
+
static interpretUnhealthy(result) {
|
|
37
|
+
const root = (result && typeof result === 'object') ? result : {};
|
|
38
|
+
const inner = root.data ?? root.result ?? root;
|
|
39
|
+
const flat = (inner && typeof inner === 'object') ? inner : root;
|
|
40
|
+
return flat.available === false
|
|
41
|
+
|| flat.reachable === false
|
|
42
|
+
|| flat.ok === false
|
|
43
|
+
|| (Array.isArray(flat.missing) && flat.missing.length > 0);
|
|
44
|
+
}
|
|
45
|
+
/** A teljes (vagy `skip`-elt) check-szett lefuttatása + aggregált health-report. */
|
|
46
|
+
async runDoctor(set) {
|
|
47
|
+
const skip = set?.skip ?? [];
|
|
48
|
+
const checks = [];
|
|
49
|
+
for (const descriptor of CHECK_MATRIX) {
|
|
50
|
+
if (skip.includes(descriptor.name)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
checks.push(await this.runCheck(descriptor));
|
|
54
|
+
}
|
|
55
|
+
const summary = { ok: 0, warn: 0, fail: 0 };
|
|
56
|
+
for (const check of checks) {
|
|
57
|
+
summary[check.status] += 1;
|
|
58
|
+
}
|
|
59
|
+
const health = summary.fail ? 'fail' : summary.warn ? 'warn' : 'ok';
|
|
60
|
+
return {
|
|
61
|
+
health: health,
|
|
62
|
+
checks: checks,
|
|
63
|
+
summary: summary,
|
|
64
|
+
scopeIntegrity: await this.scopeIntegrity(),
|
|
65
|
+
database: await this.databaseInfo(),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Best-effort DB-diagnosztika (user-FR 2026-06-24): a 6 fő tár elem-számai + embedding-státusza (get_table_stats)
|
|
70
|
+
* + a helper-collection-ök mérete + az összes aktív doc (get_storage_usage). A hiba NEM buktatja a report-ot.
|
|
71
|
+
*/
|
|
72
|
+
async databaseInfo() {
|
|
73
|
+
try {
|
|
74
|
+
const statsRaw = await mcp_1.FAM_CapabilitiesTool_Service.getInstance().handle({ action: 'invoke', name: 'get_table_stats' });
|
|
75
|
+
const storageRaw = await mcp_1.FAM_CapabilitiesTool_Service.getInstance().handle({ action: 'invoke', name: 'get_storage_usage' });
|
|
76
|
+
const statsList = FAM_Doctor_ControlService.unwrapArray(statsRaw);
|
|
77
|
+
const storage = FAM_Doctor_ControlService.unwrapRecord(storageRaw);
|
|
78
|
+
const tables = (Array.isArray(statsList) ? statsList : []).map((raw) => {
|
|
79
|
+
const item = (raw && typeof raw === 'object') ? raw : {};
|
|
80
|
+
const es = (item.embeddingStatus && typeof item.embeddingStatus === 'object')
|
|
81
|
+
? item.embeddingStatus : {};
|
|
82
|
+
return {
|
|
83
|
+
table: String(item.table ?? ''),
|
|
84
|
+
count: Number(item.count) || 0,
|
|
85
|
+
completed: Number(es.completed) || 0,
|
|
86
|
+
pending: Number(es.pending) || 0,
|
|
87
|
+
error: Number(es.error) || 0,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
const mainCollections = /^fam_(rules|documents|codebases|knowledges|coding_patterns|memories)$/;
|
|
91
|
+
const collectionsRaw = storage.collections;
|
|
92
|
+
const helperCollections = (Array.isArray(collectionsRaw) ? collectionsRaw : [])
|
|
93
|
+
.map((raw) => {
|
|
94
|
+
const item = (raw && typeof raw === 'object') ? raw : {};
|
|
95
|
+
return { collection: String(item.collection ?? ''), activeDocs: Number(item.activeDocs) || 0 };
|
|
96
|
+
})
|
|
97
|
+
.filter((entry) => entry.collection && !mainCollections.test(entry.collection));
|
|
98
|
+
return {
|
|
99
|
+
tables: tables,
|
|
100
|
+
totalActiveDocs: typeof storage.totalActiveDocs === 'number' ? storage.totalActiveDocs : undefined,
|
|
101
|
+
helperCollections: helperCollections,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** A capability-eredmény tömb-payloadja (a `this.ok` wrapper-réteget is megnézve). */
|
|
109
|
+
static unwrapArray(result) {
|
|
110
|
+
if (Array.isArray(result)) {
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
const root = (result && typeof result === 'object') ? result : {};
|
|
114
|
+
const inner = root.data ?? root.result ?? root;
|
|
115
|
+
return Array.isArray(inner) ? inner : [];
|
|
116
|
+
}
|
|
117
|
+
/** A capability-eredmény objektum-payloadja (a `this.ok` wrapper-réteget is megnézve). */
|
|
118
|
+
static unwrapRecord(result) {
|
|
119
|
+
const root = (result && typeof result === 'object') ? result : {};
|
|
120
|
+
const inner = root.data ?? root.result ?? root;
|
|
121
|
+
return (inner && typeof inner === 'object') ? inner : root;
|
|
122
|
+
}
|
|
123
|
+
/** Egy capability-check IN-PROCESS futtatása → `FAM_DoctorCheck` (throw → FAIL/WARN; szemantikus hiba → FAIL). */
|
|
124
|
+
async runCheck(descriptor) {
|
|
125
|
+
try {
|
|
126
|
+
const result = await mcp_1.FAM_CapabilitiesTool_Service.getInstance()
|
|
127
|
+
.handle({ action: 'invoke', name: descriptor.capability });
|
|
128
|
+
if (FAM_Doctor_ControlService.interpretUnhealthy(result)) {
|
|
129
|
+
return { name: descriptor.name, status: 'fail', detail: JSON.stringify(result).slice(0, 240), fixHint: descriptor.fixHint };
|
|
130
|
+
}
|
|
131
|
+
return { name: descriptor.name, status: 'ok' };
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const message = error?.message ?? 'ismeretlen hiba';
|
|
135
|
+
// A late-bind eszközök clear "pending MP-X" hibája → WARN (nem FAIL, nem néma).
|
|
136
|
+
const isPending = message.toLowerCase().includes('pending');
|
|
137
|
+
return { name: descriptor.name, status: isPending ? 'warn' : 'fail', detail: message, fixHint: descriptor.fixHint };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Best-effort scope-integritás-összegzés (a detect_scope_issues hibája NEM buktatja a doctor-report-ot). */
|
|
141
|
+
async scopeIntegrity() {
|
|
142
|
+
try {
|
|
143
|
+
const issues = await scope_reference_1.FAM_ScopeMaintenance_ControlService.getInstance().detectIssues();
|
|
144
|
+
return {
|
|
145
|
+
hasIssues: issues.hasIssues,
|
|
146
|
+
orphanScopes: issues.orphanProjectScopes.length,
|
|
147
|
+
duplicateScopes: issues.duplicateScopes.length,
|
|
148
|
+
repoKeyDuplicates: issues.repoKeyDuplicates.length,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.FAM_Doctor_ControlService = FAM_Doctor_ControlService;
|
|
@@ -163,7 +163,7 @@ class FAM_Config_ControlService {
|
|
|
163
163
|
const dataService = new config_data_service_1.FAM_Config_DataService({ issuer: options.issuer ?? this.issuer });
|
|
164
164
|
// Régi aktív rekord (ha van) → soft-delete (archív; history-megőrzés, dsgn-007 §7.2).
|
|
165
165
|
const existing = await dataService.findActive(filter);
|
|
166
|
-
if (existing
|
|
166
|
+
if (existing?._id) {
|
|
167
167
|
await dataService.deleteData(existing._id);
|
|
168
168
|
}
|
|
169
169
|
// Új aktív rekord — a `value` Mixed; a saveData $set-tel ír (Mongoose Mixed silent-drop ellen).
|
|
@@ -435,7 +435,7 @@ class FAM_Config_ControlService {
|
|
|
435
435
|
async seedGlobalIfMissing(key, value) {
|
|
436
436
|
const dataService = new config_data_service_1.FAM_Config_DataService({ issuer: this.issuer });
|
|
437
437
|
const existing = await dataService.findActive({ level: fam_config_level_type_enum_1.FAM_ConfigLevel.global, key: key });
|
|
438
|
-
if (existing
|
|
438
|
+
if (existing?._id) {
|
|
439
439
|
return;
|
|
440
440
|
}
|
|
441
441
|
await this.set(key, value, { level: fam_config_level_type_enum_1.FAM_ConfigLevel.global, setBy: 'env-seed', issuer: this.issuer });
|