@futdevpro/fdp-agent-memory 1.1.30 → 1.1.113
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 +1 -1
- 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 +160 -6
- 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/_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/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 +31 -5
- package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +142 -9
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +9 -0
- 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 +173 -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/_services/fam-capability-registry.service.js +151 -8
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +70 -0
- package/build/src/_modules/mcp/_services/fam-write-tool.service.js +36 -0
- 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 +216 -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 +76 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +233 -0
- package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +25 -0
- package/build/src/_routes/server/api/api.controller.js +270 -0
- package/build/src/_routes/server/api/fam-doctor.control-service.js +156 -0
- package/build/src/app.server.js +13 -10
- 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 +1 -1
- package/build/src/_cli/_collections/fam-project-discovery.util.js +0 -98
- package/client-dist/chunk-I77GXVAQ.js +0 -1
- package/client-dist/main-PJPEDVJT.js +0 -2
|
@@ -21,6 +21,14 @@ class FAM_Scope_DataModel extends fsm_dynamo_1.DyFM_Metadata {
|
|
|
21
21
|
parentScopeId;
|
|
22
22
|
/** Denormalizált gyors-alias gyorsítótár (forrás: `fam_reference`; szinkron MP-3). */
|
|
23
23
|
aliases = [];
|
|
24
|
+
/**
|
|
25
|
+
* Kanonikus repo-kulcs (scope-prevenció, user-FR 2026-06-22) — a stabil projekt-identitás a git-remote-ból
|
|
26
|
+
* (`org/repo`, lowercase) vagy fallback a package-névből. A SCAN stamp-eli a project-layer scope-ra a felismert
|
|
27
|
+
* git/package identitásból (`FAM_ProjectIdentity_Util.canonicalRepoKey`). Erre épül a repo-kulcs alapú
|
|
28
|
+
* duplikátum-detekció (`detectIssues.repoKeyDuplicates`), ami a KÜLÖNBÖZŐ nevű, de AZONOS repo eseteket
|
|
29
|
+
* (pl. import-`10.3.3` vs scan-`ccap-revisioned`) is elkapja — amit a puszta név-egyezés kihagy.
|
|
30
|
+
*/
|
|
31
|
+
canonicalRepoKey;
|
|
24
32
|
/** Leírás. */
|
|
25
33
|
description;
|
|
26
34
|
/** Ki hozta létre (szűkített: `manual` | `agent-write` | `import`). */
|
|
@@ -46,6 +54,7 @@ exports.famScope_dataParams = new fsm_dynamo_1.DyFM_DataModel_Params({
|
|
|
46
54
|
canonicalName: { type: 'string', index: true, required: true },
|
|
47
55
|
parentScopeId: { type: 'string', index: true },
|
|
48
56
|
aliases: { type: 'string[]', index: true, default: [] },
|
|
57
|
+
canonicalRepoKey: { type: 'string', index: true },
|
|
49
58
|
description: { type: 'string' },
|
|
50
59
|
createdBy: { type: 'string', index: true },
|
|
51
60
|
},
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_DedupWarn_ControlService = void 0;
|
|
4
|
+
const fam_vector_search_control_service_1 = require("./fam-vector-search.control-service");
|
|
5
|
+
/**
|
|
6
|
+
* `FAM_DedupWarn_ControlService` (user-FR 2026-06-25) — **near-duplikátum-figyelmeztetés** íráskor: egy ÚJ entry
|
|
7
|
+
* vektorával megkeresi a tár legközelebbi szomszédait, és ha valamelyik a küszöb (`threshold`) FÖLÖTT van, jelzi
|
|
8
|
+
* (NEM blokkol — csak warning: „ez majdnem ugyanaz, mint X; érdemes lehet UPDATE-elni a meglévőt új entry helyett").
|
|
9
|
+
* A meglévő `FAM_DuplicateScan` az EGÉSZ tár O(n²) cluster-scanje (utólagos takarítás); EZ a KÖNNYŰ, ÍRÁS-IDEJŰ
|
|
10
|
+
* változat (1 vektor-keresés a friss vektorral) — a duplikáció-KELETKEZÉS megelőzésére.
|
|
11
|
+
*/
|
|
12
|
+
class FAM_DedupWarn_ControlService {
|
|
13
|
+
static _instance;
|
|
14
|
+
static getInstance() {
|
|
15
|
+
if (!FAM_DedupWarn_ControlService._instance) {
|
|
16
|
+
FAM_DedupWarn_ControlService._instance = new FAM_DedupWarn_ControlService();
|
|
17
|
+
}
|
|
18
|
+
return FAM_DedupWarn_ControlService._instance;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Az új tartalom (már kiszámolt `vector`) near-duplikátumai a `table` hidratált poolján: a legközelebbi `topK`
|
|
22
|
+
* szomszéd, ÖNMAGÁT (`excludeId`) kizárva, a `threshold` fölött. Üres vektor / nincs jelölt → üres lista.
|
|
23
|
+
*/
|
|
24
|
+
async check(set) {
|
|
25
|
+
if (!set.vector.length) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
// +1 a topK-hoz: az önmagát (excludeId) is visszaadja score ~1.0-val, azt kiszűrjük.
|
|
29
|
+
const hits = await fam_vector_search_control_service_1.FAM_VectorSearch_ControlService.getInstance().search({
|
|
30
|
+
table: set.table,
|
|
31
|
+
queryVector: set.vector,
|
|
32
|
+
topK: (set.topK ?? 5) + 1,
|
|
33
|
+
});
|
|
34
|
+
return hits
|
|
35
|
+
.filter((hit) => hit.id !== set.excludeId && hit.score >= set.threshold)
|
|
36
|
+
.slice(0, set.topK ?? 5)
|
|
37
|
+
.map((hit) => ({ id: hit.id, score: hit.score }));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.FAM_DedupWarn_ControlService = FAM_DedupWarn_ControlService;
|
|
@@ -43,18 +43,20 @@ class FAM_DuplicateScan_ControlService {
|
|
|
43
43
|
const registryEntry = fam_store_registry_const_1.FAM_StoreRegistry_Util.getEntry(input.table);
|
|
44
44
|
const search = fam_vector_search_control_service_1.FAM_VectorSearch_ControlService.getInstance();
|
|
45
45
|
const totalInPool = registryEntry ? search.getPoolSize(input.table) : 0;
|
|
46
|
+
const filtered = FAM_DuplicateScan_ControlService.hasCandidateFilter(input);
|
|
46
47
|
const empty = {
|
|
47
48
|
table: input.table, threshold: threshold, totalInPool: totalInPool, scanned: 0,
|
|
48
|
-
capped: false, clusterCount: 0, duplicateEntryCount: 0, clusters: [],
|
|
49
|
+
capped: false, filtered: filtered, clusterCount: 0, duplicateEntryCount: 0, clusters: [],
|
|
49
50
|
};
|
|
50
51
|
if (!registryEntry || !totalInPool) {
|
|
51
52
|
return empty;
|
|
52
53
|
}
|
|
53
54
|
// 1. A (capolt) (id, vektor) halmaz lean-streamelése (NEM a teljes content — csak a vektorok).
|
|
55
|
+
// A szűrő-mezők Mongo-prefilterként MÁR ITT szűkítik a jelölt-halmazt (célzott scan a nagy táron).
|
|
54
56
|
const dataService = new fam_entry_data_service_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
|
|
55
57
|
const ids = [];
|
|
56
58
|
const vectors = [];
|
|
57
|
-
const filter =
|
|
59
|
+
const filter = FAM_DuplicateScan_ControlService.buildScanFilter(input);
|
|
58
60
|
await dataService.streamHydratableVectors(filter, (id, contentVector) => {
|
|
59
61
|
if (ids.length < maxEntries) {
|
|
60
62
|
ids.push(id);
|
|
@@ -62,13 +64,17 @@ class FAM_DuplicateScan_ControlService {
|
|
|
62
64
|
}
|
|
63
65
|
});
|
|
64
66
|
const scanned = ids.length;
|
|
65
|
-
const capped = totalInPool > scanned;
|
|
66
|
-
// 2. Élek: minden entry top-szomszédai a küszöb felett (a saját maga kizárva).
|
|
67
|
-
//
|
|
67
|
+
const capped = filtered ? scanned >= maxEntries : totalInPool > scanned;
|
|
68
|
+
// 2. Élek: minden entry top-szomszédai a küszöb felett (a saját maga kizárva). Szűrt scan-nél a
|
|
69
|
+
// találat-halmaz is a jelölt-halmazra korlátozódik (candidateIds — a cluster NE tartalmazzon
|
|
70
|
+
// szűrőn-kívüli entry-t). Per-entry try/catch (egy dimenzió-/embedding-anomália NE döntse az egész scan-t).
|
|
71
|
+
const candidateIds = filtered ? ids : undefined;
|
|
68
72
|
const edges = [];
|
|
69
73
|
for (let i = 0; i < scanned; i++) {
|
|
70
74
|
try {
|
|
71
|
-
const hits = await search.search({
|
|
75
|
+
const hits = await search.search({
|
|
76
|
+
table: input.table, queryVector: vectors[i], topK: neighbors + 1, candidateIds: candidateIds,
|
|
77
|
+
});
|
|
72
78
|
for (const hit of hits) {
|
|
73
79
|
if (hit.id !== ids[i] && hit.score >= threshold) {
|
|
74
80
|
edges.push({ a: ids[i], b: hit.id, score: hit.score });
|
|
@@ -89,19 +95,32 @@ class FAM_DuplicateScan_ControlService {
|
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
const metaById = await this.fetchMeta(dataService, clusteredIds);
|
|
92
|
-
// 5. Cluster-ek építése +
|
|
98
|
+
// 5. Cluster-ek építése (tag-dúsítás + projekt-lefedettség) + a `minProjects` cross-project szűrő +
|
|
99
|
+
// rendezés (legszélesebb projekt-lefedettség, majd méret, majd score elöl) + output-cap.
|
|
100
|
+
const minProjects = input.minProjects ?? 0;
|
|
93
101
|
const clusters = components
|
|
94
|
-
.map((component) =>
|
|
95
|
-
|
|
96
|
-
maxScore: component.maxScore,
|
|
97
|
-
minScore: component.minScore,
|
|
98
|
-
members: component.ids.map((id) => ({
|
|
102
|
+
.map((component) => {
|
|
103
|
+
const members = component.ids.map((id) => ({
|
|
99
104
|
id: id,
|
|
100
105
|
kind: metaById.get(id)?.kind,
|
|
101
106
|
snippet: metaById.get(id)?.snippet,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
sourceFilePath: metaById.get(id)?.sourceFilePath,
|
|
108
|
+
project: metaById.get(id)?.project,
|
|
109
|
+
}));
|
|
110
|
+
const projects = FAM_DuplicateScan_ControlService.distinctProjects(members);
|
|
111
|
+
return {
|
|
112
|
+
size: component.ids.length,
|
|
113
|
+
maxScore: component.maxScore,
|
|
114
|
+
minScore: component.minScore,
|
|
115
|
+
projects: projects,
|
|
116
|
+
projectCount: projects.length,
|
|
117
|
+
members: members,
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
.filter((cluster) => cluster.projectCount >= minProjects)
|
|
121
|
+
.sort((left, right) => right.projectCount - left.projectCount
|
|
122
|
+
|| right.size - left.size
|
|
123
|
+
|| right.maxScore - left.maxScore);
|
|
105
124
|
const duplicateEntryCount = clusters.reduce((sum, cluster) => sum + cluster.size, 0);
|
|
106
125
|
return {
|
|
107
126
|
table: input.table,
|
|
@@ -109,11 +128,48 @@ class FAM_DuplicateScan_ControlService {
|
|
|
109
128
|
totalInPool: totalInPool,
|
|
110
129
|
scanned: scanned,
|
|
111
130
|
capped: capped,
|
|
131
|
+
filtered: filtered,
|
|
112
132
|
clusterCount: clusters.length,
|
|
113
133
|
duplicateEntryCount: duplicateEntryCount,
|
|
114
134
|
clusters: clusters.slice(0, maxClusters),
|
|
115
135
|
};
|
|
116
136
|
}
|
|
137
|
+
/** Van-e AKTÍV jelölt-szűrő az inputban (→ Mongo-prefilter + candidateIds-szűkített keresés)? Tiszta. */
|
|
138
|
+
static hasCandidateFilter(input) {
|
|
139
|
+
return Boolean(input.pathRegex || input.excludePathRegex || input.excludeRootRegex
|
|
140
|
+
|| input.projects?.length || input.excludeProjects?.length);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* A jelölt-halmaz Mongo-prefilterének összeállítása az input szűrő-mezőiből. Tiszta (side-effect-mentes) →
|
|
144
|
+
* unit-tesztelhető. A feltételek `$and`-be kerülnek (a `sourceFilePath` include+exclude kulcs-ütközése miatt).
|
|
145
|
+
*/
|
|
146
|
+
static buildScanFilter(input) {
|
|
147
|
+
const conditions = [{ embeddingStatus: 'completed' }];
|
|
148
|
+
if (input.pathRegex) {
|
|
149
|
+
conditions.push({ sourceFilePath: { $regex: input.pathRegex, $options: 'i' } });
|
|
150
|
+
}
|
|
151
|
+
if (input.excludePathRegex) {
|
|
152
|
+
conditions.push({ sourceFilePath: { $not: { $regex: input.excludePathRegex, $options: 'i' } } });
|
|
153
|
+
}
|
|
154
|
+
if (input.excludeRootRegex) {
|
|
155
|
+
conditions.push({ 'source.root': { $not: { $regex: input.excludeRootRegex, $options: 'i' } } });
|
|
156
|
+
}
|
|
157
|
+
if (input.projects?.length) {
|
|
158
|
+
conditions.push({ scopePath: { $elemMatch: { layer: 'project', canonicalName: { $in: input.projects } } } });
|
|
159
|
+
}
|
|
160
|
+
if (input.excludeProjects?.length) {
|
|
161
|
+
conditions.push({ scopePath: { $not: { $elemMatch: { layer: 'project', canonicalName: { $in: input.excludeProjects } } } } });
|
|
162
|
+
}
|
|
163
|
+
return (conditions.length === 1 ? conditions[0] : { $and: conditions });
|
|
164
|
+
}
|
|
165
|
+
/** Egy entry projekt-neve a scopePath project-layer eleméből (nincs → undefined). Tiszta. */
|
|
166
|
+
static projectOf(scopePath) {
|
|
167
|
+
return (scopePath ?? []).find((ref) => ref.layer === 'project')?.canonicalName;
|
|
168
|
+
}
|
|
169
|
+
/** A cluster-tagok KÜLÖNBÖZŐ (nem-üres) projekt-nevei rendezve. Tiszta. */
|
|
170
|
+
static distinctProjects(members) {
|
|
171
|
+
return [...new Set(members.map((member) => member.project).filter((project) => Boolean(project)))].sort();
|
|
172
|
+
}
|
|
117
173
|
/**
|
|
118
174
|
* **Tiszta union-find**: a hasonlóság-élekből összefüggő komponensek (méret ≥ 2). Minden komponens id-i
|
|
119
175
|
* rendezve + a komponens score-tartománya (min/max él-score). Determinista (azonos input → azonos output).
|
|
@@ -178,7 +234,7 @@ class FAM_DuplicateScan_ControlService {
|
|
|
178
234
|
}
|
|
179
235
|
return result;
|
|
180
236
|
}
|
|
181
|
-
/** A clustered id-k jelleg + snippet metaadata (egy lean lekérdezés, csak a kis clustered halmazra). */
|
|
237
|
+
/** A clustered id-k jelleg + snippet + forrás-út + projekt metaadata (egy lean lekérdezés, csak a kis clustered halmazra). */
|
|
182
238
|
async fetchMeta(dataService, clusteredIds) {
|
|
183
239
|
const metaById = new Map();
|
|
184
240
|
if (!clusteredIds.size) {
|
|
@@ -194,6 +250,8 @@ class FAM_DuplicateScan_ControlService {
|
|
|
194
250
|
metaById.set(id, {
|
|
195
251
|
kind: entry.kind,
|
|
196
252
|
snippet: content.replace(/\s+/g, ' ').trim().slice(0, FAM_DuplicateScan_ControlService.SNIPPET_LENGTH),
|
|
253
|
+
sourceFilePath: entry.sourceFilePath,
|
|
254
|
+
project: FAM_DuplicateScan_ControlService.projectOf(entry.scopePath),
|
|
197
255
|
});
|
|
198
256
|
}
|
|
199
257
|
return metaById;
|
|
@@ -3,7 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FAM_EmbeddingBootstrap_ControlService = void 0;
|
|
4
4
|
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
5
5
|
const config_control_service_1 = require("../../../_routes/server/config/config.control-service");
|
|
6
|
+
const fam_hydration_coordinator_control_service_1 = require("./fam-hydration-coordinator.control-service");
|
|
6
7
|
const fam_vector_search_control_service_1 = require("./fam-vector-search.control-service");
|
|
8
|
+
const fam_scope_maintenance_control_service_1 = require("../../scope-reference/_services/fam-scope-maintenance.control-service");
|
|
9
|
+
const fam_scan_job_control_service_1 = require("../../ingest/_services/fam-scan-job.control-service");
|
|
10
|
+
const fam_memory_dormancy_control_service_1 = require("../../retrieval/_services/fam-memory-dormancy.control-service");
|
|
7
11
|
/**
|
|
8
12
|
* `FAM_EmbeddingBootstrap_ControlService` (SP-2.2/2.4) — az embedding + vektor-réteg **boot-hook**-ja.
|
|
9
13
|
* Az `App.postProcess()` (a DB-connect + setup UTÁNI hook) hívja. Két lépés:
|
|
@@ -41,12 +45,58 @@ class FAM_EmbeddingBootstrap_ControlService {
|
|
|
41
45
|
catch (error) {
|
|
42
46
|
fsm_dynamo_1.DyFM_Log.warn(`[FAM bootstrap] ENV-config seed sikertelen: ${error?.message}`);
|
|
43
47
|
}
|
|
48
|
+
// A readiness-coordinator hidratálás-bevárás time-outja a `read.hydrationWaitMs` config-ból (a hidratálás
|
|
49
|
+
// ELŐTT, hogy a `beginBoot` után érkező read-ek már a helyes időkorláttal várjanak). Best-effort (default 180s).
|
|
50
|
+
try {
|
|
51
|
+
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('read.hydrationWaitMs');
|
|
52
|
+
if (typeof resolved.value === 'number') {
|
|
53
|
+
fam_hydration_coordinator_control_service_1.FAM_HydrationCoordinator_ControlService.getInstance().configureWaitMs(resolved.value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM bootstrap] hydration-wait config-feloldás sikertelen (default marad): ${error?.message}`);
|
|
58
|
+
}
|
|
44
59
|
try {
|
|
45
60
|
await fam_vector_search_control_service_1.FAM_VectorSearch_ControlService.getInstance().hydrateAll();
|
|
46
61
|
}
|
|
47
62
|
catch (error) {
|
|
48
63
|
fsm_dynamo_1.DyFM_Log.warn(`[FAM bootstrap] vektor-pool hidratálás sikertelen: ${error?.message}`);
|
|
49
64
|
}
|
|
65
|
+
// Scope-integritás self-check (user-FR 2026-06-22): a rendszer MAGÁTÓL ismerje fel az árva/duplikált
|
|
66
|
+
// scope-okat (boot után, best-effort — a takarítás `merge_scopes`, nem itt). Csak figyelmeztet, nem ír.
|
|
67
|
+
try {
|
|
68
|
+
const issues = await fam_scope_maintenance_control_service_1.FAM_ScopeMaintenance_ControlService.getInstance().detectIssues();
|
|
69
|
+
if (issues.hasIssues) {
|
|
70
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM bootstrap] SCOPE-INTEGRITÁS: ${issues.orphanProjectScopes.length} árva projekt-scope + `
|
|
71
|
+
+ `${issues.duplicateScopes.length} duplikált (name+layer) — futtasd a 'detect_scope_issues' capability-t `
|
|
72
|
+
+ 'a részletekért, a takarítás a merge_scopes-szal.');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM bootstrap] scope-integritás self-check sikertelen: ${error?.message}`);
|
|
77
|
+
}
|
|
78
|
+
// AUTO-RESUME (FEAT-008 Phase 4, user-FR 2026-06-22): a megszakadt (running/pending) scan-job-okat folytatjuk
|
|
79
|
+
// a megszakítási ponttól (durable Mongo-állapot → a már kész projektek átugorva). Best-effort, a boot UTÁN.
|
|
80
|
+
try {
|
|
81
|
+
const resumed = await fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().resumeInterrupted();
|
|
82
|
+
if (resumed > 0) {
|
|
83
|
+
fsm_dynamo_1.DyFM_Log.info(`[FAM bootstrap] ${resumed} megszakadt scan-job FOLYTATVA a durable állapotból.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM bootstrap] scan-job auto-resume sikertelen: ${error?.message}`);
|
|
88
|
+
}
|
|
89
|
+
// MEMORY DORMANCY sweep (dsgn-013 §5, MAM F3): a régóta-nem-felidézett, ritkán-használt memóriák ALVÓ-vá
|
|
90
|
+
// jelölése (a hatás a KÖVETKEZŐ re-hidratáláskor jön; a hot pool kisebb marad). Best-effort, a boot UTÁN.
|
|
91
|
+
try {
|
|
92
|
+
const sweep = await fam_memory_dormancy_control_service_1.FAM_MemoryDormancy_ControlService.getInstance().sweep(Date.now());
|
|
93
|
+
if (sweep.markedDormant > 0) {
|
|
94
|
+
fsm_dynamo_1.DyFM_Log.info(`[FAM bootstrap] memory-dormancy: ${sweep.markedDormant} bejegyzés ALVÓ-vá jelölve.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM bootstrap] memory-dormancy sweep sikertelen: ${error?.message}`);
|
|
99
|
+
}
|
|
50
100
|
}
|
|
51
101
|
}
|
|
52
102
|
exports.FAM_EmbeddingBootstrap_ControlService = FAM_EmbeddingBootstrap_ControlService;
|
|
@@ -52,7 +52,7 @@ class FAM_EmbeddingPipeline_ControlService {
|
|
|
52
52
|
async embedAndPersist(set) {
|
|
53
53
|
const registryEntry = fam_store_registry_const_1.FAM_StoreRegistry_Util.getEntry(set.table);
|
|
54
54
|
if (!registryEntry || !set.entry._id) {
|
|
55
|
-
return { status: 'error', dims: 0 };
|
|
55
|
+
return { status: 'error', dims: 0, vector: [] };
|
|
56
56
|
}
|
|
57
57
|
const dataService = new fam_entry_data_service_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
|
|
58
58
|
// FEAT-003: a tartalomban felismert azonosító-kódok inverted-index-be írása — az embed-sikertől
|
|
@@ -67,7 +67,7 @@ class FAM_EmbeddingPipeline_ControlService {
|
|
|
67
67
|
if (!composed.trim().length) {
|
|
68
68
|
// Nincs vektorizálható content → marad pending (a write-validáció a content-kötelezőséget
|
|
69
69
|
// külön kezeli; itt csak jelezzük, hogy nincs mit embeddelni).
|
|
70
|
-
return { status: 'error', dims: 0 };
|
|
70
|
+
return { status: 'error', dims: 0, vector: [] };
|
|
71
71
|
}
|
|
72
72
|
try {
|
|
73
73
|
const vectors = await fam_embedding_control_service_1.FAM_Embedding_ControlService.getInstance().embedTexts({
|
|
@@ -89,21 +89,47 @@ class FAM_EmbeddingPipeline_ControlService {
|
|
|
89
89
|
contentHash: contentHash,
|
|
90
90
|
});
|
|
91
91
|
fam_vector_search_control_service_1.FAM_VectorSearch_ControlService.getInstance().upsertVector({ table: set.table, id: set.entry._id, vector: vector });
|
|
92
|
-
return { status: 'completed', dims: vector.length };
|
|
92
|
+
return { status: 'completed', dims: vector.length, vector: vector };
|
|
93
93
|
}
|
|
94
94
|
catch (error) {
|
|
95
95
|
// No-silent-failure: az entry `error`-státuszba kerül + a hiba persistált; a pool-ból kivesszük.
|
|
96
96
|
await dataService.setEmbeddingStatus(set.entry._id, 'error');
|
|
97
97
|
fam_vector_search_control_service_1.FAM_VectorSearch_ControlService.getInstance().removeVector(set.table, set.entry._id);
|
|
98
|
+
// A belső provider-hiba (`FAM-EMB-PROVIDER-002`) DIAGNOSZTIKÁJÁT (a vétkes batch csonkolt MINTÁJA +
|
|
99
|
+
// provider) FELEMELJÜK a persistált rekord TOP-LEVEL contextSnapshot-jába (user-FR 2026-06-24 — „az LM
|
|
100
|
+
// Studio által dobott hibákat el kéne tároljuk … bizonyos sémákon akad fönn újra"). Különben a
|
|
101
|
+
// `failedSample` csak a cause-láncon élne, nem a kereshető rekord-mezőben.
|
|
102
|
+
const diag = FAM_EmbeddingPipeline_ControlService.extractProviderDiagnostics(error);
|
|
98
103
|
await errors_control_service_1.FAM_Error_ControlService.getInstance().emit({
|
|
99
104
|
errorCode: error_codes_const_1.FAM_ERROR_CODES.embProviderUnreachable,
|
|
100
105
|
message: `Az entry embeddelése sikertelen (table='${set.table}', id='${set.entry._id}').`,
|
|
101
106
|
cause: error,
|
|
102
|
-
context: {
|
|
107
|
+
context: {
|
|
108
|
+
operation: 'embed-and-persist', table: set.table,
|
|
109
|
+
provider: diag.provider, failedSample: diag.failedSample,
|
|
110
|
+
},
|
|
103
111
|
});
|
|
104
|
-
return { status: 'error', dims: 0 };
|
|
112
|
+
return { status: 'error', dims: 0, vector: [] };
|
|
105
113
|
}
|
|
106
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* A belső `FAM-EMB-PROVIDER-002` (vagy bármely FAM-error) cause `contextSnapshot`-jából kiolvassa a
|
|
117
|
+
* diagnosztikát (a vétkes batch `failedSample`-jét + a `provider`-t), hogy az embed-and-persist rekord
|
|
118
|
+
* TOP-LEVEL kontextusába felemelhető legyen (kereshető, redaktált). Fail-safe: bármi hiányzik → üres.
|
|
119
|
+
*/
|
|
120
|
+
static extractProviderDiagnostics(error) {
|
|
121
|
+
const additional = error?.additionalContent;
|
|
122
|
+
const snapshot = additional?.contextSnapshot;
|
|
123
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
const sample = snapshot.failedSample;
|
|
127
|
+
const provider = snapshot.provider;
|
|
128
|
+
return {
|
|
129
|
+
failedSample: typeof sample === 'string' ? sample : undefined,
|
|
130
|
+
provider: typeof provider === 'string' ? provider : undefined,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
107
133
|
// =========================================================================
|
|
108
134
|
// RE-EMBED (dsgn-006 §5) — modell-/provider-váltás
|
|
109
135
|
// =========================================================================
|
|
@@ -4,6 +4,7 @@ exports.FAM_Embedding_ControlService = void 0;
|
|
|
4
4
|
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
5
5
|
const error_codes_const_1 = require("../../../_collections/error-codes.const");
|
|
6
6
|
const fam_error_factory_util_1 = require("../../../_collections/fam-error-factory.util");
|
|
7
|
+
const fam_retry_util_1 = require("../../../_collections/fam-retry.util");
|
|
7
8
|
const config_control_service_1 = require("../../../_routes/server/config/config.control-service");
|
|
8
9
|
const fam_embedding_prefix_util_1 = require("../_collections/fam-embedding-prefix.util");
|
|
9
10
|
const fam_embedding_cost_control_service_1 = require("./fam-embedding-cost.control-service");
|
|
@@ -27,6 +28,8 @@ class FAM_Embedding_ControlService {
|
|
|
27
28
|
static _instance;
|
|
28
29
|
/** Default issuer (a hibák / cost-event issuer-éhez). */
|
|
29
30
|
issuer = 'FAM_Embedding_ControlService';
|
|
31
|
+
/** A provider-hívás max próbálkozás-száma tranziens hibára (1 = nincs retry; env `FAM_EMBED_MAX_ATTEMPTS`, default 3). */
|
|
32
|
+
providerMaxAttempts = Math.max(1, Number(process.env.FAM_EMBED_MAX_ATTEMPTS) || 3);
|
|
30
33
|
static getInstance() {
|
|
31
34
|
if (!FAM_Embedding_ControlService._instance) {
|
|
32
35
|
FAM_Embedding_ControlService._instance = new FAM_Embedding_ControlService();
|
|
@@ -106,14 +109,22 @@ class FAM_Embedding_ControlService {
|
|
|
106
109
|
const allVectors = [];
|
|
107
110
|
for (let i = 0; i < texts.length; i += resolved.batchSize) {
|
|
108
111
|
const batch = texts.slice(i, i + resolved.batchSize);
|
|
112
|
+
let vectors;
|
|
109
113
|
try {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
// RETRY a TRANZIENS provider-hibára (stresszteszt-lelet 2026-06-22: konkurrens terhelés alatt a remote
|
|
115
|
+
// LM Studio embedding ritkán `FAM-EMB-PROVIDER-002`-t adott → a teljes read elbukott). Backoff-fal
|
|
116
|
+
// újrapróbáljuk; a query- ÉS write-embed is profitál (egy scan-chunk se vesszen el egy tranziens hibán).
|
|
117
|
+
vectors = await this.embedBatchWithRetry(provider, batch, resolved.modelId, issuer);
|
|
113
118
|
}
|
|
114
119
|
catch (error) {
|
|
115
|
-
|
|
120
|
+
// A VÉTKES batch-et (`batch`) + a scope/table-kontextust átadjuk a hiba-factory-nak: így a tárolt
|
|
121
|
+
// `FAM-EMB-PROVIDER-002` rekord hordozza, MELY tartalom/séma omlasztotta a modellt (user-FR 2026-06-24).
|
|
122
|
+
throw this.providerError(resolved.provider, resolved.modelId, error, issuer, {
|
|
123
|
+
batch: batch, table: set.table, scopePath: set.scopePath,
|
|
124
|
+
});
|
|
116
125
|
}
|
|
126
|
+
this.assertDimensions(vectors, resolved, issuer);
|
|
127
|
+
allVectors.push(...vectors);
|
|
117
128
|
}
|
|
118
129
|
// Cost-aggregálás (SP-2.4, BFR-AM-007): a becsült tokenből (a pontos cost-event a bedrock-szinten).
|
|
119
130
|
fam_embedding_cost_control_service_1.FAM_EmbeddingCost_ControlService.getInstance().recordActual({
|
|
@@ -170,7 +181,7 @@ class FAM_Embedding_ControlService {
|
|
|
170
181
|
context: { operation: 'build-provider', provider: 'lmstudio' },
|
|
171
182
|
});
|
|
172
183
|
}
|
|
173
|
-
return new fam_lmstudio_embedding_provider_1.FAM_LMStudio_EmbeddingProvider(resolved.baseUrl, this.lmStudioApiKey());
|
|
184
|
+
return new fam_lmstudio_embedding_provider_1.FAM_LMStudio_EmbeddingProvider(resolved.baseUrl, this.lmStudioApiKey(), this.lmStudioTimeoutMs());
|
|
174
185
|
case 'mock':
|
|
175
186
|
return new fam_mock_embedding_provider_1.FAM_Mock_EmbeddingProvider(resolved.dims ?? undefined);
|
|
176
187
|
default:
|
|
@@ -203,20 +214,133 @@ class FAM_Embedding_ControlService {
|
|
|
203
214
|
});
|
|
204
215
|
}
|
|
205
216
|
}
|
|
206
|
-
/**
|
|
207
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Egy batch embeddelése RETRY-jal a TRANZIENS provider-hibára (stresszteszt-lelet 2026-06-22). A `providerMaxAttempts`
|
|
219
|
+
* próbálkozás, exponenciális backoff-fal (250ms × 2^n, max ~1s); a `FAM_Error` (kanonikus, pl. dimenzió-mismatch) NEM
|
|
220
|
+
* retry-zhető (az determinisztikus hiba). Az utolsó hibát továbbdobja (a hívó `providerError`-rá konvertálja).
|
|
221
|
+
*/
|
|
222
|
+
async embedBatchWithRetry(provider, batch, modelId, issuer) {
|
|
223
|
+
return fam_retry_util_1.FAM_Retry_Util.withRetry(() => provider.embedTexts({ texts: batch, model: modelId, issuer: issuer }), {
|
|
224
|
+
maxAttempts: this.providerMaxAttempts,
|
|
225
|
+
// Kanonikus FAM-hiba (pl. dimenzió-mismatch) = determinisztikus → NEM retry-zhető; minden más tranziens.
|
|
226
|
+
isRetryable: (error) => !(error instanceof fsm_dynamo_1.DyFM_Error),
|
|
227
|
+
onRetry: (attempt, backoffMs, error) => fsm_dynamo_1.DyFM_Log.warn(`[FAM embedding] provider-hiba (${attempt}/${this.providerMaxAttempts}. próba) — `
|
|
228
|
+
+ `${backoffMs}ms múlva újra: ${error?.message}`),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Provider-hiba kanonikus `FAM-EMB-PROVIDER-002`-vé (elérhetetlen / API-hiba). A `failed` (a vétkes batch +
|
|
233
|
+
* scope/table) opcionális: ha van, a hiba diagnosztizálhatóvá válik (user-FR 2026-06-24 — „bizonyos sémákon akad
|
|
234
|
+
* fönn újra és újra az LM Studio"). A batch SÉMA-SIGNATURE-ja az ÜZENETBE kerül (így a DyFM dedup séma-szerint
|
|
235
|
+
* csoportosít: ugyanaz a tartalom-forma → ugyanaz a rekord, occurrence++; ELTÉRŐ forma → külön rekord), a
|
|
236
|
+
* csonkolt MINTA pedig a `context.failedSample`-be (a tényleges tartalom szemrevételezéséhez).
|
|
237
|
+
*/
|
|
238
|
+
providerError(provider, modelId, cause, issuer, failed) {
|
|
208
239
|
// Ha már kanonikus FAM error (pl. dimenzió-mismatch a batch-en belül), nem csomagoljuk újra.
|
|
209
240
|
if (cause instanceof fsm_dynamo_1.DyFM_Error) {
|
|
210
241
|
return cause;
|
|
211
242
|
}
|
|
243
|
+
const described = failed?.batch?.length ? this.describeFailedBatch(failed.batch) : undefined;
|
|
244
|
+
const signatureSuffix = described ? ` ${described.signature}` : '';
|
|
212
245
|
return fam_error_factory_util_1.FAM_Error_Util.create({
|
|
213
246
|
errorCode: error_codes_const_1.FAM_ERROR_CODES.embProviderUnreachable,
|
|
214
|
-
message: `Az embedding-provider ('${provider}', modell '${modelId}') elérhetetlen vagy hibát adott
|
|
247
|
+
message: `Az embedding-provider ('${provider}', modell '${modelId}') elérhetetlen vagy hibát adott.${signatureSuffix}`,
|
|
215
248
|
issuer: issuer,
|
|
216
249
|
cause: cause,
|
|
217
|
-
context: {
|
|
250
|
+
context: {
|
|
251
|
+
operation: 'embed',
|
|
252
|
+
provider: provider,
|
|
253
|
+
table: failed?.table ?? null,
|
|
254
|
+
scopePath: failed?.scopePath?.map((ref) => ref.canonicalName) ?? null,
|
|
255
|
+
failedSample: described?.sample ?? null,
|
|
256
|
+
},
|
|
218
257
|
});
|
|
219
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* A vétkes embedding-batch DIAGNOSZTIKAI leírása (user-FR 2026-06-24). Két részt ad:
|
|
261
|
+
* - `signature`: KOMPAKT, determinisztikus séma-jelző (batch-darabszám + a leghosszabb szöveg HOSSZ-vödre +
|
|
262
|
+
* tartalom-fajta heurisztika + non-ASCII arány-vödör) — ez az ÜZENETBE megy, hogy a dedup séma-szerint
|
|
263
|
+
* csoportosítson (azonos forma → 1 rekord/occurrence++; eltérő forma → külön rekord).
|
|
264
|
+
* - `sample`: a leghosszabb néhány szöveg HOSSZ-korlátozott, összefűzött mintája — a context `failedSample`-jébe
|
|
265
|
+
* (a tényleges, modell-omlasztó tartalom szemrevételezéséhez). A sensitive-redakciót a context-util végzi.
|
|
266
|
+
*/
|
|
267
|
+
describeFailedBatch(batch) {
|
|
268
|
+
const lengths = batch.map((text) => text.length);
|
|
269
|
+
const maxLen = lengths.reduce((max, len) => (len > max ? len : max), 0);
|
|
270
|
+
// A leghosszabb szövegen futtatjuk a fajta-/non-ASCII-heurisztikát (ez a legvalószínűbb omlasztó).
|
|
271
|
+
const longest = batch.reduce((acc, text) => (text.length > acc.length ? text : acc), '');
|
|
272
|
+
const kind = this.classifyContentKind(longest);
|
|
273
|
+
const nonAsciiBucket = this.nonAsciiBucket(longest);
|
|
274
|
+
const signature = `[batch n=${batch.length}, maxLen=${this.lengthBucket(maxLen)}, `
|
|
275
|
+
+ `kind=${kind}, nonAscii=${nonAsciiBucket}]`;
|
|
276
|
+
// Minta: a 2 leghosszabb szöveg, egyenként ~400 karakterre csonkolva (a teljes minta-cap ~1000 karakter).
|
|
277
|
+
const sorted = [...batch].sort((a, b) => b.length - a.length).slice(0, 2);
|
|
278
|
+
const sample = sorted
|
|
279
|
+
.map((text, index) => `#${index + 1} (len=${text.length}): ${this.truncate(text, 450)}`)
|
|
280
|
+
.join('\n---\n')
|
|
281
|
+
.slice(0, 1000);
|
|
282
|
+
return { signature: signature, sample: sample };
|
|
283
|
+
}
|
|
284
|
+
/** Hossz-vödör (a maxLen kompakt, dedup-barát kvantálása — ne fragmentáljon karakter-pontosan). */
|
|
285
|
+
lengthBucket(length) {
|
|
286
|
+
if (length < 512) {
|
|
287
|
+
return '<512';
|
|
288
|
+
}
|
|
289
|
+
if (length < 2048) {
|
|
290
|
+
return '512-2k';
|
|
291
|
+
}
|
|
292
|
+
if (length < 8192) {
|
|
293
|
+
return '2k-8k';
|
|
294
|
+
}
|
|
295
|
+
if (length < 32768) {
|
|
296
|
+
return '8k-32k';
|
|
297
|
+
}
|
|
298
|
+
return '>32k';
|
|
299
|
+
}
|
|
300
|
+
/** Tartalom-fajta heurisztika (json/code/markdown/prose) — a leghosszabb szöveg mintájából. */
|
|
301
|
+
classifyContentKind(text) {
|
|
302
|
+
const head = text.slice(0, 4096);
|
|
303
|
+
const trimmed = head.trimStart();
|
|
304
|
+
if ((trimmed.startsWith('{') || trimmed.startsWith('[')) && /"\s*:/.test(head)) {
|
|
305
|
+
return 'json-like';
|
|
306
|
+
}
|
|
307
|
+
if (/```|(^|\n)\s*(import |export |function |const |class |def |public |private )/.test(head)) {
|
|
308
|
+
return 'code-like';
|
|
309
|
+
}
|
|
310
|
+
if (/(^|\n)(#{1,6} |[-*] |\d+\. |> )/.test(head)) {
|
|
311
|
+
return 'markdown';
|
|
312
|
+
}
|
|
313
|
+
return 'prose';
|
|
314
|
+
}
|
|
315
|
+
/** Non-ASCII arány-vödör (low/med/high) — a >127 kódpontú karakterek hányada a minta-fejen. */
|
|
316
|
+
nonAsciiBucket(text) {
|
|
317
|
+
const head = text.slice(0, 4096);
|
|
318
|
+
if (!head.length) {
|
|
319
|
+
return 'none';
|
|
320
|
+
}
|
|
321
|
+
let nonAscii = 0;
|
|
322
|
+
for (let i = 0; i < head.length; i++) {
|
|
323
|
+
if (head.charCodeAt(i) > 127) {
|
|
324
|
+
nonAscii++;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const ratio = nonAscii / head.length;
|
|
328
|
+
if (!nonAscii) {
|
|
329
|
+
return 'none';
|
|
330
|
+
}
|
|
331
|
+
if (ratio < 0.05) {
|
|
332
|
+
return 'low';
|
|
333
|
+
}
|
|
334
|
+
if (ratio < 0.2) {
|
|
335
|
+
return 'med';
|
|
336
|
+
}
|
|
337
|
+
return 'high';
|
|
338
|
+
}
|
|
339
|
+
/** Egy-soros, hossz-korlátozott csonkolás (a newline-okat szóközre cseréli a kompakt minta-sorhoz). */
|
|
340
|
+
truncate(text, max) {
|
|
341
|
+
const oneLine = text.replace(/\s+/g, ' ').trim();
|
|
342
|
+
return oneLine.length > max ? `${oneLine.slice(0, max)}…` : oneLine;
|
|
343
|
+
}
|
|
220
344
|
/** Cost-cap blokk → `FAM-EMB-COSTCAP-001` (block-before-overrun; dsgn-006 §7 / dsgn-008). */
|
|
221
345
|
costCapError(reason, issuer) {
|
|
222
346
|
return fam_error_factory_util_1.FAM_Error_Util.create({
|
|
@@ -259,6 +383,15 @@ class FAM_Embedding_ControlService {
|
|
|
259
383
|
lmStudioApiKey() {
|
|
260
384
|
return process.env.LMSTUDIO_API_KEY;
|
|
261
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Az LM Studio request-timeout (ms) az ENV-ből (`LMSTUDIO_REQUEST_TIMEOUT_MS`, default 120000 = 2 perc). Egy
|
|
388
|
+
* NEM-VÁLASZOLÓ LM Studio (crash-elt modell) ne fagyassza be a teljes scant — a timeout retry-zható hibára fordul.
|
|
389
|
+
* `0` → nincs timeout (a régi viselkedés). Érvénytelen/hiányzó env → a 2 perces default.
|
|
390
|
+
*/
|
|
391
|
+
lmStudioTimeoutMs() {
|
|
392
|
+
const raw = Number(process.env.LMSTUDIO_REQUEST_TIMEOUT_MS);
|
|
393
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : 120000;
|
|
394
|
+
}
|
|
262
395
|
/** A `embedOne` set-jéből a `text`-et kihagyó opció-objektum (a `embedTexts`-nek átadva). */
|
|
263
396
|
stripText(set) {
|
|
264
397
|
return {
|
|
@@ -124,6 +124,15 @@ class FAM_Entry_DataService extends nts_dynamo_1.DyNTS_DataService {
|
|
|
124
124
|
const update = { $set: { embeddingStatus: status } };
|
|
125
125
|
await this.updateData({ filterBy: { _id: id }, update: update });
|
|
126
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* A `superseded` (LEVÁLTOTT) flag atomikus beállítása (dsgn-013 §6, MAM F4) — egy additív (`memory`) fájl régi,
|
|
129
|
+
* már átírt tartalmát jelöli leváltottnak. NEM törlés (a tartalom + vektor megmarad, cold-kereshető); a ranking
|
|
130
|
+
* penalizálja. Az embed-pipeline-tól FÜGGETLEN flag-frissítés.
|
|
131
|
+
*/
|
|
132
|
+
async setSuperseded(id, superseded) {
|
|
133
|
+
const update = { $set: { superseded: superseded } };
|
|
134
|
+
await this.updateData({ filterBy: { _id: id }, update: update });
|
|
135
|
+
}
|
|
127
136
|
/**
|
|
128
137
|
* A felismert azonosító-kódok (`referenceCodes`) atomikus frissítése (`$set`) — FEAT-003 inverted-index.
|
|
129
138
|
* Az embed-sikertől FÜGGETLEN write (a write-path az embed ELŐTT hívja), így a kód-citáció a még nem
|