@futdevpro/fdp-agent-memory 1.1.14 → 1.1.30

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 (28) hide show
  1. package/build/package.json +2 -2
  2. package/build/src/_cli/_commands/config.command.js +4 -1
  3. package/build/src/_cli/_commands/scan-projects.command.js +27 -2
  4. package/build/src/_collections/config-catalog.const.js +34 -1
  5. package/build/src/_collections/fam-config-value-coerce.util.js +63 -0
  6. package/build/src/_collections/fam-cooldown.util.js +35 -0
  7. package/build/src/_collections/title.const.js +31 -0
  8. package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +1 -1
  9. package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +1 -1
  10. package/build/src/_modules/embedding/_services/fam-entry.data-service.js +23 -1
  11. package/build/src/_modules/ingest/_collections/fam-famignore.util.js +57 -1
  12. package/build/src/_modules/ingest/_collections/fam-scan-reconcile.util.js +40 -0
  13. package/build/src/_modules/ingest/_collections/fam-secret-content.util.js +49 -0
  14. package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +12 -0
  15. package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +40 -9
  16. package/build/src/_modules/ingest/_services/fam-scan.control-service.js +77 -11
  17. package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +1 -1
  18. package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +113 -33
  19. package/build/src/_modules/mcp/_services/fam-read-tool.service.js +1 -1
  20. package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
  21. package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +58 -0
  22. package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +190 -0
  23. package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
  24. package/build/src/_modules/scope-reference/index.js +4 -1
  25. package/build/src/_routes/server/api/api.controller.js +85 -18
  26. package/build/src/_routes/server/config/config.control-service.js +2 -2
  27. package/build/src/app.server.js +30 -1
  28. package/package.json +2 -2
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ScopeMaintenance_ControlService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
6
+ const error_codes_const_1 = require("../../../_collections/error-codes.const");
7
+ const fam_error_factory_util_1 = require("../../../_collections/fam-error-factory.util");
8
+ const embedding_1 = require("../../embedding");
9
+ const fam_reference_data_service_1 = require("./fam-reference.data-service");
10
+ const fam_scope_data_service_1 = require("./fam-scope.data-service");
11
+ const fam_scope_normalize_util_1 = require("../_collections/fam-scope-normalize.util");
12
+ const fam_scope_maintenance_util_1 = require("../_collections/fam-scope-maintenance.util");
13
+ /**
14
+ * `FAM_ScopeMaintenance_ControlService` (user-FR 2026-06-22) — **scope rename + merge** maintenance-engine.
15
+ * A claude-mem import (és egyéb) rendetlen scope-neveit (pl. `10.3.3` → valójában ccap-revisioned) rendezi:
16
+ * a `canonicalName`-t a denormalizált `scopePath[]`-en (MINDEN RAG-entry) + a reference `canonicalScopeRef`-jén
17
+ * + a scope-doc-on bulk-ban átírja (rename), VAGY egy scope-ot egy másikba olvaszt (merge → a forrás-scope
18
+ * minden entry-je az `into`-ra mutat, majd a forrás-scope soft-delete).
19
+ *
20
+ * **dryRun (default true a destruktív-biztonsághoz):** csak az érintett-számokat adja vissza, NEM ír. A
21
+ * tényleges íráshoz `dryRun:false` explicit. A bulk array-update mongoose `updateMany` + `arrayFilters` (a spec
22
+ * a tiszta `FAM_ScopeMaintenance_Util`-ból). A LVS-pool a Mongo-ból hidratál → a változás a következő (vagy
23
+ * scope-scoped) keresésnél él; nagy átírás után szerver-restart ajánlott a pool friss scope-metaadatához.
24
+ *
25
+ * **Megjegyzés (dedup):** a merge NEM dedup-ol — ha az `into` és a `from` ugyanazt a tartalmat tartalmazta,
26
+ * a `deduplicate_entries` (külön capability) felel a content-hash-összevonásért.
27
+ */
28
+ class FAM_ScopeMaintenance_ControlService {
29
+ static _instance;
30
+ issuer = 'FAM_ScopeMaintenance_ControlService';
31
+ static getInstance() {
32
+ if (!FAM_ScopeMaintenance_ControlService._instance) {
33
+ FAM_ScopeMaintenance_ControlService._instance = new FAM_ScopeMaintenance_ControlService();
34
+ }
35
+ return FAM_ScopeMaintenance_ControlService._instance;
36
+ }
37
+ /**
38
+ * Egy scope ÁTNEVEZÉSE: a `scopeId` `canonicalName`-jét `newName`-re — a scopeId VÁLTOZATLAN, csak a
39
+ * denormalizált név (entry-k `scopePath` + reference + scope-doc). `dryRun` → csak az érintett-szám.
40
+ */
41
+ async renameScope(set) {
42
+ const newName = (set.newName ?? '').trim();
43
+ if (!newName) {
44
+ throw this.invalid('A `newName` (nem-üres string) KÖTELEZŐ a rename_scope-hoz.', 'rename_scope');
45
+ }
46
+ const scope = await this.resolveScope(set);
47
+ const scopeId = scope._id;
48
+ const dryRun = set.dryRun !== false;
49
+ const affected = await this.countAffected(scopeId);
50
+ if (!dryRun) {
51
+ const entryUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.renameEntryUpdate(scopeId, newName);
52
+ await this.applyEntryUpdate(entryUpdate);
53
+ const refUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.renameReferenceUpdate(scopeId, newName);
54
+ await this.referenceCollection().collection.updateMany(refUpdate.filter, refUpdate.update);
55
+ // A scope-doc canonicalName-je ($set, atomikus — a denormalizált forrás).
56
+ await new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer })
57
+ .updateData({ filterBy: { _id: scopeId }, update: { $set: { canonicalName: newName } } });
58
+ }
59
+ return {
60
+ operation: 'rename_scope',
61
+ dryRun: dryRun,
62
+ scope: { scopeId: scopeId, layer: scope.layer ?? '', oldName: scope.canonicalName ?? '', newName: newName },
63
+ affected: affected,
64
+ totalAffected: affected.reduce((sum, item) => sum + item.count, 0),
65
+ };
66
+ }
67
+ /**
68
+ * Két scope ÖSSZEOLVASZTÁSA: a `from` scope minden entry-je az `into` scope `{scopeId,layer,canonicalName}`-jére
69
+ * mutat, majd a `from` scope soft-delete. `dryRun` → csak az érintett-szám. Az `into` MARAD.
70
+ */
71
+ async mergeScopes(set) {
72
+ const from = await this.resolveScope(set.from);
73
+ const into = await this.resolveScope(set.into);
74
+ const fromId = from._id;
75
+ const intoId = into._id;
76
+ if (fromId === intoId) {
77
+ throw this.invalid('A `from` és az `into` ugyanaz a scope — nincs mit összeolvasztani.', 'merge_scopes');
78
+ }
79
+ const dryRun = set.dryRun !== false;
80
+ const intoRef = { scopeId: intoId, layer: into.layer ?? '', canonicalName: into.canonicalName ?? '' };
81
+ const affected = await this.countAffected(fromId);
82
+ if (!dryRun) {
83
+ const entryUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.mergeEntryUpdate(fromId, intoRef);
84
+ await this.applyEntryUpdate(entryUpdate);
85
+ const refUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.mergeReferenceUpdate(fromId, intoRef);
86
+ await this.referenceCollection().collection.updateMany(refUpdate.filter, refUpdate.update);
87
+ // A forrás-scope soft-delete (move-to-archive) — már nincs rá hivatkozó entry.
88
+ await new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer }).deleteData(fromId);
89
+ }
90
+ return {
91
+ operation: 'merge_scopes',
92
+ dryRun: dryRun,
93
+ from: { scopeId: fromId, layer: from.layer ?? '', canonicalName: from.canonicalName ?? '' },
94
+ into: { scopeId: intoId, layer: into.layer ?? '', canonicalName: into.canonicalName ?? '' },
95
+ affected: affected,
96
+ totalAffected: affected.reduce((sum, item) => sum + item.count, 0),
97
+ fromScopeDeleted: !dryRun,
98
+ };
99
+ }
100
+ /** Az `entryUpdate` spec alkalmazása MINDEN fő RAG-tár collection-jén (mongoose `updateMany` + arrayFilters). */
101
+ async applyEntryUpdate(entryUpdate) {
102
+ for (const named of this.entryCollections()) {
103
+ await named.collection.updateMany(entryUpdate.filter, entryUpdate.update, { arrayFilters: entryUpdate.arrayFilters });
104
+ }
105
+ }
106
+ /** A `scopeId`-t hivatkozó aktív entry-k száma per-collection (a 6 fő tár + a reference). */
107
+ async countAffected(scopeId) {
108
+ const affected = [];
109
+ for (const named of this.entryCollections()) {
110
+ affected.push({ collection: named.name, count: await named.collection.countDocuments({ 'scopePath.scopeId': scopeId }) });
111
+ }
112
+ const reference = this.referenceCollection();
113
+ affected.push({ collection: reference.name, count: await reference.collection.countDocuments({ 'canonicalScopeRef.scopeId': scopeId }) });
114
+ return affected;
115
+ }
116
+ /** A 6 fő RAG-tár mongoose-collection-jei (a DS-példányosítás regisztrálja a modellt, onnan a valós collection). */
117
+ entryCollections() {
118
+ const result = [];
119
+ for (const entry of embedding_1.FAM_STORE_REGISTRY) {
120
+ new embedding_1.FAM_Entry_DataService({ dataParams: entry.dataParams, issuer: this.issuer });
121
+ const model = mongoose_1.default.models[entry.dataParams.dataName];
122
+ if (model?.collection) {
123
+ result.push({ name: model.collection.collectionName, collection: model.collection });
124
+ }
125
+ }
126
+ return result;
127
+ }
128
+ /** A reference helper-tár mongoose-collection-je. */
129
+ referenceCollection() {
130
+ new fam_reference_data_service_1.FAM_Reference_DataService({ issuer: this.issuer });
131
+ const model = mongoose_1.default.models['fam_reference'];
132
+ return { name: model?.collection?.collectionName ?? 'fam_references', collection: model.collection };
133
+ }
134
+ /**
135
+ * Egy scope feloldása a szelektorból: `scopeId` (közvetlen) VAGY `layer`+`canonicalName` (egyértelmű) VAGY
136
+ * csak `canonicalName` (minden layer-en, normalizált-egyezés — egyértelmű kell legyen). Nem-talált/kétértelmű → hiba.
137
+ */
138
+ async resolveScope(selector) {
139
+ const dataService = new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
140
+ if (selector.scopeId) {
141
+ const scope = await dataService.findScopeById(selector.scopeId);
142
+ if (scope?._id) {
143
+ return scope;
144
+ }
145
+ throw this.notFound(`scopeId '${selector.scopeId}'`);
146
+ }
147
+ if (selector.canonicalName) {
148
+ const matches = selector.layer
149
+ ? await dataService.findByCanonical(selector.layer, selector.canonicalName)
150
+ : await this.findByNameAnyLayer(dataService, selector.canonicalName);
151
+ if (matches.length === 1) {
152
+ return matches[0];
153
+ }
154
+ if (matches.length > 1) {
155
+ throw fam_error_factory_util_1.FAM_Error_Util.create({
156
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.refResolveAmbiguous,
157
+ message: `A(z) '${selector.canonicalName}' név több scope-ra illeszkedik (${matches.length}) — adj meg `
158
+ + '`layer`-t vagy `scopeId`-t az egyértelműsítéshez.',
159
+ issuer: this.issuer,
160
+ context: { operation: 'scope-maintenance:resolve' },
161
+ });
162
+ }
163
+ throw this.notFound(`'${selector.canonicalName}'${selector.layer ? ` (layer '${selector.layer}')` : ''}`);
164
+ }
165
+ throw this.invalid('Add meg a scope-ot: `scopeId` VAGY `canonicalName` (+ opcionális `layer`).', 'resolve-scope');
166
+ }
167
+ /** Egy név normalizált-egyezése MINDEN aktív scope-on (layer nélkül; a halmaz kicsi). */
168
+ async findByNameAnyLayer(dataService, name) {
169
+ const all = await dataService.findAllActive();
170
+ const target = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(name);
171
+ return all.filter((scope) => fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(scope.canonicalName ?? '') === target);
172
+ }
173
+ notFound(what) {
174
+ return fam_error_factory_util_1.FAM_Error_Util.create({
175
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.refResolveNoMatch,
176
+ message: `Nincs aktív scope: ${what}.`,
177
+ issuer: this.issuer,
178
+ context: { operation: 'scope-maintenance:resolve' },
179
+ });
180
+ }
181
+ invalid(message, operation) {
182
+ return fam_error_factory_util_1.FAM_Error_Util.create({
183
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.mcpDispatchUnknownCapability,
184
+ message: message,
185
+ issuer: this.issuer,
186
+ context: { operation: `scope-maintenance:${operation}` },
187
+ });
188
+ }
189
+ }
190
+ exports.FAM_ScopeMaintenance_ControlService = FAM_ScopeMaintenance_ControlService;
@@ -409,7 +409,7 @@ class FAM_ScopeResolver_ControlService {
409
409
  }
410
410
  const scope_DS = new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
411
411
  const scope = await scope_DS.findScopeById(scopeId);
412
- return Boolean(scope && scope._id);
412
+ return Boolean(scope?._id);
413
413
  }
414
414
  // =========================================================================
415
415
  // TRACE (dsgn-002 §4, dsgn-003 §4.2) — a debug-capability (MP-6) fogyasztja
@@ -460,7 +460,7 @@ class FAM_ScopeResolver_ControlService {
460
460
  async traceEntityFallback(rawLayer, scopeDataService) {
461
461
  const matches = await scopeDataService.findByCanonical(rawLayer.layer, rawLayer.rawName);
462
462
  const hit = matches.find((scope) => !!scope._id);
463
- if (hit && hit._id) {
463
+ if (hit?._id) {
464
464
  return { matched: true, scopeId: hit._id, canonicalName: hit.canonicalName };
465
465
  }
466
466
  return { matched: false };
@@ -9,7 +9,7 @@
9
9
  * a fogyasztó-MP-ké. A reference/scope collection-ök MP-1-ben már a `dbModels`-ben vannak.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.FAM_FuzzyMatch_Util = exports.FAM_ScopeNormalize_Util = exports.FAM_ScopeResolver_ControlService = exports.FAM_Reference_DataService = exports.FAM_Scope_DataService = void 0;
12
+ exports.FAM_FuzzyMatch_Util = exports.FAM_ScopeNormalize_Util = exports.FAM_ScopeMaintenance_ControlService = exports.FAM_ScopeResolver_ControlService = exports.FAM_Reference_DataService = exports.FAM_Scope_DataService = void 0;
13
13
  // SP-3.1 — Scope CRUD + scope-fa traverzál
14
14
  var fam_scope_data_service_1 = require("./_services/fam-scope.data-service");
15
15
  Object.defineProperty(exports, "FAM_Scope_DataService", { enumerable: true, get: function () { return fam_scope_data_service_1.FAM_Scope_DataService; } });
@@ -19,6 +19,9 @@ Object.defineProperty(exports, "FAM_Reference_DataService", { enumerable: true,
19
19
  // SP-3.3/SP-3.4 — a feloldó control-service (write-resolve / read-expand / cache / scopeExists / uncertainty)
20
20
  var fam_scope_resolver_control_service_1 = require("./_services/fam-scope-resolver.control-service");
21
21
  Object.defineProperty(exports, "FAM_ScopeResolver_ControlService", { enumerable: true, get: function () { return fam_scope_resolver_control_service_1.FAM_ScopeResolver_ControlService; } });
22
+ // user-FR 2026-06-22 — scope rename/merge maintenance (rendetlen claude-mem import scope-nevek rendezése)
23
+ var fam_scope_maintenance_control_service_1 = require("./_services/fam-scope-maintenance.control-service");
24
+ Object.defineProperty(exports, "FAM_ScopeMaintenance_ControlService", { enumerable: true, get: function () { return fam_scope_maintenance_control_service_1.FAM_ScopeMaintenance_ControlService; } });
22
25
  // utils (SP-3.1/SP-3.2)
23
26
  var fam_scope_normalize_util_1 = require("./_collections/fam-scope-normalize.util");
24
27
  Object.defineProperty(exports, "FAM_ScopeNormalize_Util", { enumerable: true, get: function () { return fam_scope_normalize_util_1.FAM_ScopeNormalize_Util; } });
@@ -1,8 +1,11 @@
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
10
  const fam_table_type_enum_1 = require("../../../_enums/fam-table.type-enum");
8
11
  const fam_config_level_type_enum_1 = require("../../../_enums/fam-config-level.type-enum");
@@ -104,6 +107,9 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
104
107
  async (req, res) => {
105
108
  await this.run(res, async () => {
106
109
  const input = (req.body ?? {});
110
+ // Heap-load-shed (OOM-hibakezelés): a NEHÉZ write-eket (scan/import/re-embed) critical
111
+ // memória-nyomásnál elutasítjuk (503), MIELŐTT egy nagy allokáció átlökné a plafont.
112
+ this.assertHeapCapacity(String(input.operation));
107
113
  return mcp_1.FAM_WriteTool_Service.getInstance().handle(input);
108
114
  });
109
115
  },
@@ -137,7 +143,13 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
137
143
  endpoint: '/health',
138
144
  tasks: [
139
145
  async (req, res) => {
140
- res.send({ ok: true, service: 'fdp-agent-memory', version: fam_version_const_1.FAM_VERSION, uptime: process.uptime() });
146
+ // A heap-nyomás IS kimegy a health-en (observability) a BEDROCK DyNTS_MemoryGuard pressure-éből.
147
+ const memory = nts_dynamo_2.DyNTS_MemoryGuard.getInstance().pressure();
148
+ res.send({
149
+ ok: true, service: 'fdp-agent-memory', version: fam_version_const_1.FAM_VERSION, uptime: process.uptime(),
150
+ memory: { level: memory.level, heapUsedMb: memory.heapUsedMb, heapLimitMb: memory.heapLimitMb,
151
+ heapPct: memory.heapPct, rssMb: memory.rssMb, gcFraction: memory.gcFraction },
152
+ });
141
153
  },
142
154
  ],
143
155
  });
@@ -183,13 +195,27 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
183
195
  if (req.query.ingestRunId) {
184
196
  filter.ingestRunId = String(req.query.ingestRunId);
185
197
  }
186
- const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
187
- const all = await dataService.findHydratableList(filter);
188
198
  const pageSize = Math.min(this.parseNumber(req.query.pageSize, DEFAULT_PAGE_SIZE), MAX_PAGE_SIZE);
189
199
  const page = Math.max(this.parseNumber(req.query.page, 1), 1);
190
200
  const start = (page - 1) * pageSize;
191
- const items = all.slice(start, start + pageSize);
192
- return { table: table, total: all.length, page: page, pageSize: pageSize, items: items };
201
+ // FIX (OOM): DB-OLDALI lapozás + `contentVector` projekció-kizárás. KORÁBBAN `findHydratableList({})` a TELJES
202
+ // 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
203
+ // GB-okat allokált → OOM + Mongo-flapping. Most: `countDocuments` + `skip/limit` + a nagy vektor-mező kihagyva.
204
+ const collection = this.entryCollection(registryEntry);
205
+ const total = await collection.countDocuments(filter);
206
+ const items = await collection
207
+ .find(filter, { projection: { contentVector: 0 } })
208
+ .sort({ __lastModified: -1 })
209
+ .skip(start)
210
+ .limit(pageSize)
211
+ .toArray();
212
+ return { table: table, total: total, page: page, pageSize: pageSize, items: items };
213
+ }
214
+ /** Egy fő RAG-tár mongoose-collection-je (a DS-példányosítás regisztrálja a modellt; onnan a valós collection). */
215
+ entryCollection(registryEntry) {
216
+ new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
217
+ const model = mongoose_1.default.models[registryEntry.dataParams.dataName];
218
+ return model.collection;
193
219
  }
194
220
  // =========================================================================
195
221
  // /stats · /stats/:table — high-level stats aggregátum (dsgn-011 §3/§4.4)
@@ -224,7 +250,11 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
224
250
  ],
225
251
  });
226
252
  }
227
- /** A tár-stats aggregátum (aktív elem-szám + embedding-státusz + utolsó ingest) — dsgn-011 §3/§4.4. */
253
+ /**
254
+ * A tár-stats aggregátum (aktív elem-szám + embedding-státusz + utolsó ingest) — dsgn-011 §3/§4.4.
255
+ * FIX (OOM): `countDocuments` + `$group`-aggregáció + 1-doc last-modified projekció — KORÁBBAN `findHydratableList({})`
256
+ * 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.
257
+ */
228
258
  async collectTableStats(tables) {
229
259
  const result = [];
230
260
  for (const table of tables) {
@@ -232,24 +262,35 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
232
262
  if (!registryEntry) {
233
263
  continue;
234
264
  }
235
- const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
236
- const entries = await dataService.findHydratableList({});
265
+ const collection = this.entryCollection(registryEntry);
266
+ const count = await collection.countDocuments({});
267
+ // Embedding-státusz bontás Mongo-aggregációval ($group), NEM a doc-ok memóriába töltésével.
237
268
  const byStatus = { pending: 0, completed: 0, error: 0 };
238
- let lastModifiedMs = 0;
239
- for (const entry of entries) {
240
- const status = entry.embeddingStatus ?? 'pending';
241
- byStatus[status] = (byStatus[status] ?? 0) + 1;
242
- const modified = entry.__lastModified ? new Date(entry.__lastModified).getTime() : 0;
243
- if (modified > lastModifiedMs) {
244
- lastModifiedMs = modified;
269
+ const grouped = await collection
270
+ .aggregate([{ $group: { _id: '$embeddingStatus', n: { $sum: 1 } } }])
271
+ .toArray();
272
+ for (const group of grouped) {
273
+ const status = group._id ?? 'pending';
274
+ if (status in byStatus) {
275
+ byStatus[status] += group.n;
276
+ }
277
+ else {
278
+ byStatus.pending += group.n;
245
279
  }
246
280
  }
281
+ // Utolsó módosítás: EGYETLEN doc (a legfrissebb), csak az időbélyeg-mezőre projektálva.
282
+ const newest = await collection
283
+ .find({}, { projection: { __lastModified: 1 } })
284
+ .sort({ __lastModified: -1 })
285
+ .limit(1)
286
+ .toArray();
287
+ const lastModifiedMs = newest[0]?.__lastModified ? new Date(newest[0].__lastModified).getTime() : 0;
247
288
  result.push({
248
289
  table: table,
249
- count: entries.length,
290
+ count: count,
250
291
  embeddingStatus: byStatus,
251
292
  lastModified: lastModifiedMs ? new Date(lastModifiedMs).toISOString() : null,
252
- status: entries.length ? 'ok' : 'empty',
293
+ status: count ? 'ok' : 'empty',
253
294
  });
254
295
  }
255
296
  return result;
@@ -406,11 +447,37 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
406
447
  res.status(status).send({ ok: false, error: { errorCode: errorCode, message: message } });
407
448
  }
408
449
  }
409
- /** A FAM hibakód → HTTP-státusz (a user/validation-hibák 400, a többi 500; dsgn-008). */
450
+ /** A FAM hibakód → HTTP-státusz (a memória-shed 503, a user/validation-hibák 400, a többi 500; dsgn-008). */
410
451
  httpStatusForCode(errorCode) {
452
+ if (errorCode.startsWith('FAM-MEM-SHED-')) {
453
+ return 503;
454
+ }
411
455
  const userPrefixes = ['FAM-VAL-', 'FAM-REF-RESOLVE-', 'FAM-SCOPE-WRITE-', 'FAM-MCP-DISPATCH-'];
412
456
  return userPrefixes.some((prefix) => errorCode.startsWith(prefix)) ? 400 : 500;
413
457
  }
458
+ /** A NEHÉZ write-ek (scan/import/re-embed) listája — a load-shed CSAK ezeket utasítja el (a könnyűek futnak). */
459
+ static HEAVY_WRITE_OPERATIONS = ['scan-file', 'scan-folder', 'scan-project', 'import', 're-embed'];
460
+ /**
461
+ * Heap-load-shed (OOM-hibakezelés): ha a `operation` NEHÉZ ÉS a heap-guard critical nyomást jelez → 503 `DyFM_Error`
462
+ * (a könnyű create/update/delete fut tovább). Így egy újabb nagy scan/import nem löki OOM-ba a már-feszített heapet.
463
+ */
464
+ assertHeapCapacity(operation) {
465
+ if (!Api_Controller.HEAVY_WRITE_OPERATIONS.includes(operation)) {
466
+ return;
467
+ }
468
+ const guard = nts_dynamo_2.DyNTS_MemoryGuard.getInstance();
469
+ if (!guard.shouldShedLoad()) {
470
+ return;
471
+ }
472
+ const pressure = guard.pressure();
473
+ throw new fsm_dynamo_1.DyFM_Error({
474
+ errorCode: 'FAM-MEM-SHED-001',
475
+ message: `Memória-nyomás (${pressure.heapUsedMb}/${pressure.heapLimitMb} MB, ${pressure.heapPct}%, `
476
+ + `GC ${Math.round(pressure.gcFraction * 100)}%) — a nehéz '${operation}' művelet ideiglenesen elutasítva `
477
+ + '(load-shed). Próbáld újra, amikor a nyomás enyhül (lásd /api/logs), vagy szakaszold a scant (cooldown).',
478
+ issuer: this.issuer,
479
+ });
480
+ }
414
481
  /** A `:table` path-param → `FAM_Table` (ismeretlen → hiba). */
415
482
  parseTable(raw) {
416
483
  const match = Object.values(fam_table_type_enum_1.FAM_Table).find((table) => table === raw);
@@ -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 && existing._id) {
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 && existing._id) {
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 });
@@ -13,6 +13,7 @@ const errors_control_service_1 = require("./_routes/server/errors/errors.control
13
13
  const server_status_controller_1 = require("./_routes/server/server-status/server-status.controller");
14
14
  const embedding_1 = require("./_modules/embedding");
15
15
  const environment_1 = require("./environments/environment");
16
+ const title_const_1 = require("./_collections/title.const");
16
17
  /**
17
18
  * FDP Agent Memory Server App — Pure Dynamo, **AUTH NÉLKÜL** (ADR-004).
18
19
  *
@@ -32,6 +33,7 @@ class App extends nts_dynamo_1.DyNTS_App {
32
33
  const mongoBase = process.env.MONGODB_URI || process.env.MONGO_URL || 'mongodb://0.0.0.0:29017';
33
34
  return new nts_dynamo_1.DyNTS_App_Params({
34
35
  name: 'FDP Agent Memory Server',
36
+ title: title_const_1.FAM_title,
35
37
  version: package_json_1.version,
36
38
  dbName: dbName,
37
39
  dbUri: `${mongoBase}/${dbName}`,
@@ -40,7 +42,7 @@ class App extends nts_dynamo_1.DyNTS_App {
40
42
  }
41
43
  overrideDynamoNTSGlobalSettings() {
42
44
  nts_dynamo_1.DyNTS_global_settings.log_settings.api_errors = false;
43
- nts_dynamo_1.DyNTS_global_settings.log_settings.setup = true;
45
+ //DyNTS_global_settings.log_settings.setup = true;
44
46
  // NINCS JWT/auth check (ADR-004): a szerver lokális, single-user. Az auth-t EXPLICITEN
45
47
  // opcionálisnak jelöljük → elnémítja a minden booton megjelenő „Unique Authentication Service
46
48
  // missing!" figyelmeztetést (a no-auth a SZÁNDÉK, nem hiányosság).
@@ -50,6 +52,30 @@ class App extends nts_dynamo_1.DyNTS_App {
50
52
  // lokális) lekérdezhetővé teszi → az agent IS olvashatja REST-en (nem csak a tmp-fájlból). Install + enable.
51
53
  nts_dynamo_1.DyNTS_global_settings.log_settings.logs_endpoint = { enabled: true };
52
54
  logs_1.DyNTS_Logs_Service.getInstance().install();
55
+ // OOM-hibakezelés a BEDROCK `DyNTS_MemoryGuard`-ból (a base App auto-installálja a startup-ban). FAM-specifikus
56
+ // hangolás: a GC-thrash a fő OOM-jel (a FAM korpusza nagy, a heap-% csak ~85%-on ütött, de GC ~0.87-en thrash-elt),
57
+ // ezért a GC-fraction-küszöbök a kritikusak; a `%`-küszöböt magasra tesszük (csak genuine-danger). A critical
58
+ // nyomás load-shed-eli a nehéz write-eket (scan/import) az api-controllerben, + crash-handler (uncaughtException →
59
+ // teljes-részletű error-entry) ON. **`exitOnSustainedCritical` ON** (nts-dynamo 1.15.74 boot-grace-szel): a FAM
60
+ // boot-hidratálása (126k vektor) ELEVE GC-thrash-el (mért gc≈0.87), ezért `bootGraceMs: 120000` — a ~80s hidratálás
61
+ // + margó alatt a sustained-critical NEM lép ki (csak logol + load-shed); a grace UTÁN a tartós critical TISZTÁN
62
+ // kilép (exit 137) → a `fam serve` ps1-wrapper prediktíven újraindít (a kriptikus OOM exit-134 helyett). Az eredeti
63
+ // OOM uptime 771s-nél volt — jóval a grace után, tehát kezelve.
64
+ nts_dynamo_1.DyNTS_global_settings.memoryGuard = {
65
+ enabled: true,
66
+ pollIntervalMs: 3000,
67
+ heapWarningThreshold: 80,
68
+ heapCriticalThreshold: 90,
69
+ recoveryMargin: 10,
70
+ maxHistoryCount: 100,
71
+ gcWarningFraction: 0.40,
72
+ gcCriticalFraction: 0.60,
73
+ installCrashHandlers: true,
74
+ exitOnSustainedCritical: true,
75
+ sustainedCriticalPolls: 3,
76
+ exitCode: 137,
77
+ bootGraceMs: 120000,
78
+ };
53
79
  }
54
80
  getGlobalServiceCollection() {
55
81
  return {
@@ -115,7 +141,10 @@ class App extends nts_dynamo_1.DyNTS_App {
115
141
  * át, amit a write/scan path (MP-4/MP-5) hív mentés után. Lásd a `_modules/embedding` modul-doksit.
116
142
  */
117
143
  async postProcess() {
144
+ // Az OOM-hibakezelést (DyNTS_MemoryGuard) a base App auto-installálja a startup-ban, a FAM-hangolással
145
+ // (overrideDynamoNTSGlobalSettings → DyNTS_global_settings.memoryGuard) — itt nincs külön teendő.
118
146
  await embedding_1.FAM_EmbeddingBootstrap_ControlService.getInstance().boot();
147
+ fsm_dynamo_1.DyFM_Log.S_info(`UI is available at: localhost:${environment_1.env.httpPort}/api/app`);
119
148
  }
120
149
  getGlobalErrorHandler() {
121
150
  return async (err, req, res, issuer) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fdp-agent-memory",
3
- "version": "1.1.14",
3
+ "version": "1.1.30",
4
4
  "description": "Local-first, vector-backed multi-table agent memory exposed as an MCP server (read/write/capabilities). Public, FDP-Templates-free, no auth.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -55,7 +55,7 @@
55
55
  "license": "FDP",
56
56
  "dependencies": {
57
57
  "@futdevpro/fsm-dynamo": "^1.16.7",
58
- "@futdevpro/nts-dynamo": "^1.15.72",
58
+ "@futdevpro/nts-dynamo": "^1.15.74",
59
59
  "@modelcontextprotocol/sdk": "^1.12.0",
60
60
  "axios": "~1.8.1",
61
61
  "better-sqlite3": "^12.4.1",