@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.
- package/build/package.json +2 -2
- package/build/src/_cli/_commands/config.command.js +4 -1
- package/build/src/_cli/_commands/scan-projects.command.js +27 -2
- package/build/src/_collections/config-catalog.const.js +34 -1
- package/build/src/_collections/fam-config-value-coerce.util.js +63 -0
- package/build/src/_collections/fam-cooldown.util.js +35 -0
- package/build/src/_collections/title.const.js +31 -0
- package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +1 -1
- package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +1 -1
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +23 -1
- package/build/src/_modules/ingest/_collections/fam-famignore.util.js +57 -1
- package/build/src/_modules/ingest/_collections/fam-scan-reconcile.util.js +40 -0
- package/build/src/_modules/ingest/_collections/fam-secret-content.util.js +49 -0
- package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +12 -0
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +40 -9
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +77 -11
- package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +1 -1
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +113 -33
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +1 -1
- package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
- package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +58 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +190 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
- package/build/src/_modules/scope-reference/index.js +4 -1
- package/build/src/_routes/server/api/api.controller.js +85 -18
- package/build/src/_routes/server/config/config.control-service.js +2 -2
- package/build/src/app.server.js +30 -1
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
-
/**
|
|
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
|
|
236
|
-
const
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
|
|
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:
|
|
290
|
+
count: count,
|
|
250
291
|
embeddingStatus: byStatus,
|
|
251
292
|
lastModified: lastModifiedMs ? new Date(lastModifiedMs).toISOString() : null,
|
|
252
|
-
status:
|
|
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
|
|
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 });
|
package/build/src/app.server.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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",
|