@futdevpro/fdp-agent-memory 1.1.21 → 1.1.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +2 -2
- package/build/src/_cli/_commands/find-duplicates.command.js +41 -16
- package/build/src/_cli/_commands/scan-projects.command.js +113 -113
- package/build/src/_cli/_commands/scan.command.js +2 -0
- package/build/src/_cli/_commands/serve.command.js +110 -10
- package/build/src/_cli/_services/fam-client.service.js +25 -1
- package/build/src/_cli/register-commands.js +28 -0
- package/build/src/_collections/config-catalog.const.js +152 -33
- package/build/src/_collections/error-codes.const.js +2 -0
- package/build/src/_collections/fam-console.util.js +38 -260
- package/build/src/_collections/fam-db-models.const.js +2 -0
- package/build/src/_collections/fam-entry-bootstrap.util.js +20 -6
- package/build/src/_collections/fam-error-context.util.js +1 -0
- package/build/src/_collections/fam-mcp-bridge.util.js +15 -1
- package/build/src/_collections/fam-operation-queue.service.js +85 -0
- package/build/src/_collections/fam-project-discovery.util.js +148 -0
- package/build/src/_collections/fam-request-origin.util.js +41 -0
- package/build/src/_collections/fam-retry.util.js +48 -0
- package/build/src/_collections/fam-scan-progress-sink.service.js +26 -0
- package/build/src/_collections/title.const.js +26 -26
- package/build/src/_enums/fam-rule-scope.type-enum.js +25 -0
- package/build/src/_models/data-models/fam-entry.data-model.js +29 -0
- package/build/src/_models/data-models/fam-memory.data-model.js +5 -0
- package/build/src/_models/data-models/fam-rules.data-model.js +24 -0
- package/build/src/_models/data-models/fam-scan-job.data-model.js +73 -0
- package/build/src/_models/data-models/fam-scope.data-model.js +9 -0
- package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +1 -1
- package/build/src/_modules/embedding/_services/fam-dedup-warn.control-service.js +40 -0
- package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +74 -16
- package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +50 -0
- package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +32 -6
- package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +142 -9
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +32 -1
- package/build/src/_modules/embedding/_services/fam-hydration-coordinator.control-service.js +159 -0
- package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +41 -4
- package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +67 -12
- package/build/src/_modules/embedding/index.js +5 -1
- package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +10 -0
- package/build/src/_modules/ingest/_collections/fam-frontmatter.util.js +134 -0
- package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +30 -0
- package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +5 -2
- package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +55 -29
- package/build/src/_modules/ingest/_collections/fam-split-chunker.util.js +139 -0
- package/build/src/_modules/ingest/_models/interfaces/fam-scan-job.interface.js +2 -0
- package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +10 -0
- package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +68 -1
- package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +1 -1
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +177 -37
- package/build/src/_modules/ingest/_services/fam-scan-job.control-service.js +429 -0
- package/build/src/_modules/ingest/_services/fam-scan-job.data-service.js +34 -0
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +6 -4
- package/build/src/_modules/ingest/index.js +6 -1
- package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +1 -1
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +264 -41
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +71 -1
- package/build/src/_modules/mcp/_services/fam-write-tool.service.js +36 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
- package/build/src/_modules/retrieval/_collections/fam-lexical-match.util.js +69 -0
- package/build/src/_modules/retrieval/_collections/fam-memory-activation.util.js +66 -0
- package/build/src/_modules/retrieval/_collections/fam-rule-injection.util.js +125 -0
- package/build/src/_modules/retrieval/_collections/fam-rule-propagation.util.js +95 -0
- package/build/src/_modules/retrieval/_collections/fam-rule-scope-context.util.js +74 -0
- package/build/src/_modules/retrieval/_services/fam-global-rule.control-service.js +82 -0
- package/build/src/_modules/retrieval/_services/fam-memory-cold-search.control-service.js +121 -0
- package/build/src/_modules/retrieval/_services/fam-memory-dormancy.control-service.js +99 -0
- package/build/src/_modules/retrieval/_services/fam-memory-reactivation.control-service.js +68 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +15 -2
- package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +197 -5
- package/build/src/_modules/retrieval/_services/fam-rule-injection.control-service.js +102 -0
- package/build/src/_modules/retrieval/index.js +12 -1
- package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +134 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +423 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
- package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +25 -0
- package/build/src/_modules/scope-reference/index.js +4 -1
- package/build/src/_routes/server/api/api.controller.js +324 -23
- package/build/src/_routes/server/api/fam-doctor.control-service.js +156 -0
- package/build/src/_routes/server/config/config.control-service.js +2 -2
- package/build/src/app.server.js +29 -27
- package/client-dist/{chunk-YXHWCJ5O.js → chunk-GF3FJP7E.js} +71 -71
- package/client-dist/chunk-NMUCMQY6.js +1 -0
- package/client-dist/favicon.ico +0 -0
- package/client-dist/index.html +3 -3
- package/client-dist/main-YK4YIVAQ.js +2 -0
- package/package.json +2 -2
- package/build/src/_cli/_collections/fam-project-discovery.util.js +0 -98
- package/build/src/_collections/fam-heap-guard.control-service.js +0 -176
- package/client-dist/chunk-I77GXVAQ.js +0 -1
- package/client-dist/main-PJPEDVJT.js +0 -2
|
@@ -8,55 +8,25 @@ const os = tslib_1.__importStar(require("os"));
|
|
|
8
8
|
const path = tslib_1.__importStar(require("path"));
|
|
9
9
|
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
10
10
|
/**
|
|
11
|
-
* `FAM_Console_Util`
|
|
12
|
-
* látható terminál-ablakot, ami a szerver folyó tevékenységét mutatja.
|
|
11
|
+
* `FAM_Console_Util` — a leválasztott (detached) FAM REST-szerver indítása a SAJÁT, LÁTHATÓ konzol-ablakában.
|
|
13
12
|
*
|
|
14
|
-
* **
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* **A szerver MAGA fut egy látható terminál-ablakban** (user-direktíva 2026-06-22: „a FAM-szervernek MINDIG
|
|
14
|
+
* saját látható ablakban kell megnyílnia"): a `spawnServerWindow` egy platform-natív terminál-ablakot nyit, ami
|
|
15
|
+
* KÖZVETLENÜL a `node`-ot futtatja → az ablak a szerver élő stdout/stderr-jét mutatja. Az ablak `detached` +
|
|
16
|
+
* `unref()` → TELJESEN FÜGGETLEN a hívótól (CLI / agent / subagent): amikor a hívó végez, az ablak NEM záródik be.
|
|
18
17
|
*
|
|
19
|
-
* **
|
|
20
|
-
*
|
|
18
|
+
* **Nincs „activity-monitor" ablak.** A régi FEAT-001 tail-monitor (log-fájlt tail-elő külön ablak) **VÉGLEG
|
|
19
|
+
* ELTÁVOLÍTVA** (user-direktíva 2026-06-22: „a monitor-ablakot soha többé sehol; minden kapcsolódó kódot törölj").
|
|
20
|
+
* Egy szerver = EGY ablak (a szerver konzolja), semmi más.
|
|
21
21
|
*
|
|
22
|
-
* **
|
|
23
|
-
*
|
|
22
|
+
* **File-log (`installFileLog`):** opcionálisan a `console.*` kimenet egy fájlba is rácsatolható (`FAM_LOG_FILE`),
|
|
23
|
+
* hogy a logok a látható ablak MELLETT fájlból is tail-elhetők legyenek (a `fam serve` ezt állítja be a detached szerverre).
|
|
24
24
|
*/
|
|
25
25
|
class FAM_Console_Util {
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* PID-reuse → FELESLEGES dupla ablakok szerver-restartkor; a frissesség mindkettőt megoldja).
|
|
31
|
-
*/
|
|
32
|
-
static MONITOR_STALE_MS = 10_000;
|
|
33
|
-
/** A monitor-ablak heartbeat-periódusa (ms): ennyente frissíti a lockfile mtime-ját, amíg él. */
|
|
34
|
-
static MONITOR_HEARTBEAT_MS = 3_000;
|
|
35
|
-
/**
|
|
36
|
-
* Az activity-monitor-ablak engedélyezett-e. **Default OFF** (opt-in) — user-direktíva 2026-06-21: a külön
|
|
37
|
-
* FEAT-001 tail-monitort kivettük, mert a `fam serve` saját LÁTHATÓ konzol-ablakot ad (az a szerver-aktivitás),
|
|
38
|
-
* így a külön monitor redundáns. Csak EXPLICIT `FAM_SHOW_CONSOLE=true|1|on|yes` esetén nyílik (ritka:
|
|
39
|
-
* pl. ha valaki a stdio-MCP `fam start`-ot primary-ként futtatja és mégis tail-ablakot szeretne).
|
|
40
|
-
*/
|
|
41
|
-
static isEnabled() {
|
|
42
|
-
const raw = (process.env.FAM_SHOW_CONSOLE ?? '').trim().toLowerCase();
|
|
43
|
-
return raw === 'true' || raw === '1' || raw === 'on' || raw === 'yes';
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Az activity-LOG **verbose**-e (`FAM_CONSOLE_VERBOSE`). **Default ON** (user-direktíva: a monitor MINDEN
|
|
47
|
-
* log-sort írjon ki) → a `console.*` minden nem-üres sora a monitor-ablakba kerül. `false|0|off|no` → a
|
|
48
|
-
* **kurált** allowlist (csak `[fam…]` + mongo-connect + hiba/figyelmeztetés; a boot-zaj kimarad). A kettő
|
|
49
|
-
* trade-off: VERBOSE = teljes átláthatóság (de zajosabb), kurált = csak a lényeg.
|
|
50
|
-
*/
|
|
51
|
-
static isVerbose() {
|
|
52
|
-
const raw = (process.env.FAM_CONSOLE_VERBOSE ?? '').trim().toLowerCase();
|
|
53
|
-
return raw !== 'false' && raw !== '0' && raw !== 'off' && raw !== 'no';
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* **File-log (`FAM_LOG_FILE`)** — ha az env be van állítva, a `console.*` MINDEN sorát (SZŰRETLEN) rácsatolja
|
|
57
|
-
* arra a fájlra, FÜGGETLENÜL a monitor-ablaktól (`FAM_SHOW_CONSOLE`). A `fam serve` ezt állítja be a detached
|
|
58
|
-
* szerverre → a szerver logjai (köztük az import-progress) **FÁJLBÓL OLVASHATÓK** (tail-elhetők), nem csak a
|
|
59
|
-
* GUI-ablakból. Append-mód (boot-szeparátorral), fail-safe (a log-write SOHA nem dönti meg a szervert).
|
|
27
|
+
* **File-log (`FAM_LOG_FILE`)** — ha az env be van állítva, a `console.*` MINDEN sorát rácsatolja arra a fájlra
|
|
28
|
+
* (a látható ablak MELLETT). A `fam serve` ezt állítja a detached szerverre → a logok (köztük az import-progress)
|
|
29
|
+
* fájlból is OLVASHATÓK/tail-elhetők. Append-mód (boot-szeparátorral), fail-safe (a log-write SOHA nem dönti meg a szervert).
|
|
60
30
|
*/
|
|
61
31
|
static installFileLog() {
|
|
62
32
|
const logPath = (process.env.FAM_LOG_FILE ?? '').trim();
|
|
@@ -100,223 +70,19 @@ class FAM_Console_Util {
|
|
|
100
70
|
console.error = wrap(console.error);
|
|
101
71
|
}
|
|
102
72
|
/**
|
|
103
|
-
* A
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const logPath = path.join(os.tmpdir(), `fam-activity-${set.port}.log`);
|
|
112
|
-
const lockPath = path.join(os.tmpdir(), `fam-activity-${set.port}.lock`);
|
|
113
|
-
// A log-fájl PERSISTÁL a (reconnect miatti) szerver-újraindulások között: header CSAK ha új,
|
|
114
|
-
// különben separator. A folyó monitor-ablak UGYANAZT a fájlt tail-eli, így a tevékenység
|
|
115
|
-
// akkumulálódik (nem csonkol minden bootkor — ez okozta a „nincs érdemi info"-t).
|
|
116
|
-
const mode = FAM_Console_Util.isVerbose() ? 'VERBOSE (minden log)' : 'kurált (allowlist)';
|
|
117
|
-
if (!fs.existsSync(logPath)) {
|
|
118
|
-
fs.writeFileSync(logPath, `# FAM activity log — port ${set.port} — ${new Date().toISOString()} — mód: ${mode}\n`);
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
fs.appendFileSync(logPath, `# --- szerver-process (újra)indult — ${new Date().toISOString()} — mód: ${mode} ---\n`);
|
|
122
|
-
}
|
|
123
|
-
FAM_Console_Util.teeConsoleToFile(logPath);
|
|
124
|
-
// EGYETLEN monitor-ablak: ha már fut egy (a lockfile-beli PID él), NEM nyitunk újat — csak tee-zünk
|
|
125
|
-
// a közös logba (ezt mutatja a meglévő ablak). Így a reconnect-churn NEM spamel új terminálokat.
|
|
126
|
-
if (FAM_Console_Util.isMonitorAlive(lockPath)) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
FAM_Console_Util.spawnMonitorWindow(logPath, lockPath, set.port);
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
fsm_dynamo_1.DyFM_Log.warn(`[FAM console] activity-ablak megnyitása sikertelen (a szerver fut tovább): ${error?.message}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Fut-e már egy ÉLŐ monitor-ablak ehhez a porthoz? **Frissesség-alapú** (a régi PID-check helyett): a nyitott
|
|
137
|
-
* ablak `MONITOR_HEARTBEAT_MS`-enként frissíti a lockfile-t (mtime), így ha az mtime `MONITOR_STALE_MS`-nél
|
|
138
|
-
* frissebb → él (reuse, NEM nyitunk újat); ha régebbi → az ablakot bezárták → új nyitható. Ez immun a Windows
|
|
139
|
-
* `start`-detach okozta async PID-write-race-re ÉS a PID-reuse-ra (a két ok, ami szerver-restartkor dupla
|
|
140
|
-
* ablakot nyitott). Hiányzó/olvashatatlan lock → nincs élő ablak.
|
|
141
|
-
*/
|
|
142
|
-
static isMonitorAlive(lockPath) {
|
|
143
|
-
try {
|
|
144
|
-
if (!fs.existsSync(lockPath)) {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
const ageMs = Date.now() - fs.statSync(lockPath).mtimeMs;
|
|
148
|
-
return ageMs < FAM_Console_Util.MONITOR_STALE_MS;
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
/** A `console.*` kimenet rácsatolása a log-fájlra (a meglévő stderr/stdout MELLÉ — nem helyette). */
|
|
155
|
-
static teeConsoleToFile(logPath) {
|
|
156
|
-
const append = (line) => {
|
|
157
|
-
try {
|
|
158
|
-
fs.appendFileSync(logPath, `${line}\n`);
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
/* a fájl-append hibája soha nem dönti meg a szervert */
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
const stringify = (value) => {
|
|
165
|
-
if (typeof value === 'string') {
|
|
166
|
-
return value;
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
return JSON.stringify(value);
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
return String(value);
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
const wrap = (original) => {
|
|
176
|
-
return (...args) => {
|
|
177
|
-
original(...args);
|
|
178
|
-
// A stderr/stdout a TELJES kimenetet kapja (változatlan); az activity-LOGba viszont CSAK a
|
|
179
|
-
// tevékenység-releváns sorok kerülnek — a boot-banner (ASCII-art, endpoint-mount, mongoose-
|
|
180
|
-
// warning) így nem szemeteli az ablakot, és a [fam*] activity nem süllyed el (a user-panasz).
|
|
181
|
-
const line = args.map(stringify).join(' ');
|
|
182
|
-
if (FAM_Console_Util.isActivityRelevant(line)) {
|
|
183
|
-
append(line);
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
};
|
|
187
|
-
console.log = wrap(console.log.bind(console));
|
|
188
|
-
console.info = wrap(console.info.bind(console));
|
|
189
|
-
console.warn = wrap(console.warn.bind(console));
|
|
190
|
-
console.error = wrap(console.error.bind(console));
|
|
191
|
-
console.debug = wrap(console.debug.bind(console));
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Activity-releváns-e egy log-sor → bekerül-e a monitor-ablakba. **VERBOSE módban** (default, `isVerbose`)
|
|
195
|
-
* MINDEN nem-üres sor átmegy (a user „minden logot" direktívája). **Kurált módban** (`FAM_CONSOLE_VERBOSE=
|
|
196
|
-
* false`) allowlist: minden `[fam…]`/`[FAM …]` sor (famRead/famWrite/famIngest + `[FAM hydrate]`/`[FAM MCP]`
|
|
197
|
-
* boot-státusz), a `Connected to MongoDB`, a `…successfully`, és MINDEN hiba/figyelmeztetés (hogy a problémák
|
|
198
|
-
* SOHA ne tűnjenek el); minden más (a teljes Express/Dynamo boot-banner) kimarad (stderr-en marad).
|
|
199
|
-
*/
|
|
200
|
-
static isActivityRelevant(line) {
|
|
201
|
-
const trimmed = line.trim();
|
|
202
|
-
if (!trimmed) {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
// VERBOSE (default): minden nem-üres sor a monitorba — teljes átláthatóság (user-direktíva).
|
|
206
|
-
if (FAM_Console_Util.isVerbose()) {
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
if (/^\[fam/i.test(trimmed)) {
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
if (/connected to mongodb|successfully/i.test(trimmed)) {
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
return /\b(error|warn|warning|fail|failed|exception|critical)\b/i.test(trimmed);
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Platform-natív terminál-ablak indítása, ami a log-fájlt tail-eli (detached, fail-safe). Az ablak
|
|
219
|
-
* `MONITOR_HEARTBEAT_MS`-enként **frissíti a lockot** (heartbeat), így a következő (reconnect/restart miatti)
|
|
220
|
-
* boot az mtime-frissességből látja, hogy él → NEM nyit újat (`isMonitorAlive`). A spawn ELŐTT egy
|
|
221
|
-
* provisional friss lockot is írunk (a közel-egyidejű boot-race ellen).
|
|
222
|
-
*/
|
|
223
|
-
static spawnMonitorWindow(logPath, lockPath, port) {
|
|
224
|
-
try {
|
|
225
|
-
FAM_Console_Util.touchLock(lockPath); // provisional friss mtime — a spawn alatti race-dupla ellen
|
|
226
|
-
if (process.platform === 'win32') {
|
|
227
|
-
// A monitor-logikát egy temp `.ps1`-be írjuk + `-File`-lal futtatjuk → NINCS `-Command`-escaping-
|
|
228
|
-
// rémálom (a sok `()[]{}|::`). A script HEARTBEAT-eli a lockot (a régi async `$PID`-write helyett —
|
|
229
|
-
// az okozta a PID-race/reuse dupla-ablakokat), és StreamReader-rel STREAMING-tail-el (UTF-8, a
|
|
230
|
-
// `Get-Content -Wait` helyett, hogy a heartbeat a [fam*]-zaj alatt is frissen tartsa a lockot).
|
|
231
|
-
const scriptPath = path.join(os.tmpdir(), `fam-activity-monitor-${port}.ps1`);
|
|
232
|
-
fs.writeFileSync(scriptPath, FAM_Console_Util.windowsMonitorScript(logPath, lockPath, port));
|
|
233
|
-
const child = (0, child_process_1.spawn)(process.env.ComSpec ?? 'cmd.exe', ['/c', 'start', '', 'powershell', '-NoExit', '-ExecutionPolicy', 'Bypass', '-File', scriptPath], { detached: true, stdio: 'ignore', windowsHide: false });
|
|
234
|
-
child.unref();
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
// mac/linux: a tail MELLETT egy heartbeat-loop (a lockot frissíti, amíg a tail — így az ablak — él);
|
|
238
|
-
// a `kill -0 $tailPid` figyeli a tail-t, így az ablak bezárásakor a loop is kilép (nincs árva heartbeat).
|
|
239
|
-
const heartbeatSec = Math.max(1, Math.round(FAM_Console_Util.MONITOR_HEARTBEAT_MS / 1000));
|
|
240
|
-
const shellCmd = `tail -n 200 -f '${logPath}' & tp=$!; `
|
|
241
|
-
+ `while kill -0 $tp 2>/dev/null; do : > '${lockPath}'; sleep ${heartbeatSec}; done`;
|
|
242
|
-
if (process.platform === 'darwin') {
|
|
243
|
-
const script = `tell application "Terminal" to do script "${shellCmd.replace(/"/g, '\\"')}"`;
|
|
244
|
-
const child = (0, child_process_1.spawn)('osascript', ['-e', script], { detached: true, stdio: 'ignore' });
|
|
245
|
-
child.unref();
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
const terminal = FAM_Console_Util.findLinuxTerminal();
|
|
249
|
-
if (!terminal) {
|
|
250
|
-
fsm_dynamo_1.DyFM_Log.warn('[FAM console] nincs ismert Linux terminál-emulátor — az activity-ablak kihagyva '
|
|
251
|
-
+ `(a log a fájlban: ${logPath}; tail-eld kézzel, vagy FAM_SHOW_CONSOLE=false).`);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
const child = (0, child_process_1.spawn)(terminal, ['-e', 'sh', '-c', shellCmd], { detached: true, stdio: 'ignore' });
|
|
255
|
-
child.unref();
|
|
256
|
-
}
|
|
257
|
-
catch (error) {
|
|
258
|
-
fsm_dynamo_1.DyFM_Log.warn(`[FAM console] a monitor-ablak indítása sikertelen: ${error?.message}`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* A Windows monitor-`.ps1` tartalma: UTF-8 streaming-tail (StreamReader; a `Get-Content -Wait` helyett, hogy
|
|
263
|
-
* a heartbeat akkor is frissüljön, ha sok a log-sor) + `MONITOR_HEARTBEAT_MS`-enkénti lock-frissítés (a
|
|
264
|
-
* heartbeat). Az ablak bezárásakor a PowerShell-process megszűnik → a lock mtime elöregszik → `isMonitorAlive`
|
|
265
|
-
* false → a következő boot új ablakot nyit (helyesen). A path-okban a `'`-eket megdupláljuk (PS escape, defenzív).
|
|
266
|
-
*/
|
|
267
|
-
static windowsMonitorScript(logPath, lockPath, port) {
|
|
268
|
-
const log = logPath.replace(/'/g, "''");
|
|
269
|
-
const lock = lockPath.replace(/'/g, "''");
|
|
270
|
-
return [
|
|
271
|
-
'try { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 } catch {}',
|
|
272
|
-
`try { $host.UI.RawUI.WindowTitle='FAM activity (${port})' } catch {}`,
|
|
273
|
-
`$log='${log}'`,
|
|
274
|
-
`$lock='${lock}'`,
|
|
275
|
-
'if (Test-Path -LiteralPath $log) { Get-Content -LiteralPath $log -Tail 200 -Encoding UTF8 | Write-Host }',
|
|
276
|
-
"$fs=[System.IO.File]::Open($log,'Open','Read','ReadWrite')",
|
|
277
|
-
'$sr=New-Object System.IO.StreamReader($fs,[System.Text.Encoding]::UTF8)',
|
|
278
|
-
'$sr.BaseStream.Seek(0,[System.IO.SeekOrigin]::End) | Out-Null',
|
|
279
|
-
'$beat=0',
|
|
280
|
-
'while ($true) {',
|
|
281
|
-
' $line=$sr.ReadLine()',
|
|
282
|
-
' if ($null -ne $line) { Write-Host $line } else { Start-Sleep -Milliseconds 400 }',
|
|
283
|
-
' $now=[DateTimeOffset]::Now.ToUnixTimeMilliseconds()',
|
|
284
|
-
` if (($now - $beat) -ge ${FAM_Console_Util.MONITOR_HEARTBEAT_MS}) { [System.IO.File]::WriteAllText($lock,[string]$now); $beat=$now }`,
|
|
285
|
-
'}',
|
|
286
|
-
].join('\r\n');
|
|
287
|
-
}
|
|
288
|
-
/** A lockfile mtime-jának frissítése (a monitor „élő" jelzése, heartbeat). Fail-safe. */
|
|
289
|
-
static touchLock(lockPath) {
|
|
290
|
-
try {
|
|
291
|
-
fs.writeFileSync(lockPath, String(Date.now()));
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
/* a lock-write hibája soha nem dönti meg a szervert */
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
/** Az első elérhető Linux terminál-emulátor (PATH-on), vagy null. */
|
|
298
|
-
static findLinuxTerminal() {
|
|
299
|
-
const candidates = ['x-terminal-emulator', 'gnome-terminal', 'konsole', 'xfce4-terminal', 'xterm'];
|
|
300
|
-
for (const candidate of candidates) {
|
|
301
|
-
for (const dir of (process.env.PATH ?? '').split(path.delimiter)) {
|
|
302
|
-
if (dir && fs.existsSync(path.join(dir, candidate))) {
|
|
303
|
-
return candidate;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Egy VISIBLE, OLVASHATÓ terminál-ablak nyitása, amiben a megadott parancs (a FAM REST-szerver) FUT. A
|
|
311
|
-
* `fam serve` (FEAT-003) ezt használja: a `start`/terminál TELJESEN LEVÁLASZTJA a szervert a hívóról (saját
|
|
312
|
-
* konzol-ablak), így túléli a CLI-t, és az ablak a szerver **élő stdout/stderr-jét** mutatja közvetlenül.
|
|
313
|
-
* Ezért a szervert `FAM_SHOW_CONSOLE=false`-szal indítjuk (a serve env-override-ja) — a FEAT-001 külön
|
|
314
|
-
* tail-monitor-ablak ehhez **redundáns** (nem nyílik 2. ablak). Fail-safe (a hívó `--foreground`-ra eshet vissza).
|
|
73
|
+
* A FAM REST-szerver indítása EGY LÁTHATÓ, DETACHED terminál-ablakban, ami KÖZVETLENÜL a `node`-ot futtatja
|
|
74
|
+
* (az ablak a szerver konzolja). A `start`/terminál TELJESEN LEVÁLASZTJA a szervert a hívóról → **túléli a
|
|
75
|
+
* hívó (CLI / agent / subagent) kilépését** (amikor a hívó végez, az ablak NEM záródik be). Az ablak a kilépés
|
|
76
|
+
* UTÁN is nyitva marad (`-NoExit` / `exec $SHELL`), hogy a boot-hiba látható maradjon. Fail-safe (`--foreground`
|
|
77
|
+
* fallback, ha nincs terminál).
|
|
78
|
+
*
|
|
79
|
+
* `command` = `process.execPath` (node), `args` = `[--max-old-space-size=…, build/src/index.js]`. Az env-override-ok
|
|
80
|
+
* a futtató shellbe kerülnek (PS `$env:` / POSIX `export`).
|
|
315
81
|
*/
|
|
316
82
|
static spawnServerWindow(set) {
|
|
317
83
|
if (process.platform === 'win32') {
|
|
318
|
-
// Launcher-`.ps1`
|
|
319
|
-
//
|
|
84
|
+
// Launcher-`.ps1` → NINCS `-Command`-escaping-rémálom; `-NoExit` → az ablak a szerver kilépése/crash UTÁN is
|
|
85
|
+
// nyitva marad. A `cmd /c start` ÚJ, látható ablakot nyit; a `detached`+`unref` leválasztja a hívóról (túléli).
|
|
320
86
|
const scriptPath = path.join(os.tmpdir(), `fam-server-${set.port}.ps1`);
|
|
321
87
|
fs.writeFileSync(scriptPath, FAM_Console_Util.windowsServerScript(set));
|
|
322
88
|
const child = (0, child_process_1.spawn)(process.env.ComSpec ?? 'cmd.exe', ['/c', 'start', '', 'powershell', '-NoExit', '-ExecutionPolicy', 'Bypass', '-File', scriptPath], { detached: true, stdio: 'ignore', windowsHide: false });
|
|
@@ -344,9 +110,9 @@ class FAM_Console_Util {
|
|
|
344
110
|
child.unref();
|
|
345
111
|
}
|
|
346
112
|
/**
|
|
347
|
-
* A Windows szerver-launcher `.ps1` tartalma: UTF-8 konzol + ablakcím, az env-override-ok
|
|
348
|
-
* majd a szerver futtatása (`& command args`). A `'`-eket megdupláljuk (PS string-escape). A
|
|
349
|
-
* (a spawn-oldalon) tartja nyitva az ablakot kilépés után.
|
|
113
|
+
* A Windows szerver-launcher `.ps1` tartalma (PURE — unit-tesztelhető): UTF-8 konzol + ablakcím, az env-override-ok
|
|
114
|
+
* (`$env:X='...'`), majd a szerver futtatása (`& command args`). A `'`-eket megdupláljuk (PS string-escape). A
|
|
115
|
+
* `-NoExit` (a spawn-oldalon) tartja nyitva az ablakot kilépés után.
|
|
350
116
|
*/
|
|
351
117
|
static windowsServerScript(set) {
|
|
352
118
|
const esc = (value) => value.replace(/'/g, "''");
|
|
@@ -363,5 +129,17 @@ class FAM_Console_Util {
|
|
|
363
129
|
lines.push("Write-Host \"[FAM szerver kilépett — exit $LASTEXITCODE — 'fam serve --restart' újraindítja]\"");
|
|
364
130
|
return lines.join('\r\n');
|
|
365
131
|
}
|
|
132
|
+
/** Az első elérhető Linux terminál-emulátor (PATH-on), vagy null. */
|
|
133
|
+
static findLinuxTerminal() {
|
|
134
|
+
const candidates = ['x-terminal-emulator', 'gnome-terminal', 'konsole', 'xfce4-terminal', 'xterm'];
|
|
135
|
+
for (const candidate of candidates) {
|
|
136
|
+
for (const dir of (process.env.PATH ?? '').split(path.delimiter)) {
|
|
137
|
+
if (dir && fs.existsSync(path.join(dir, candidate))) {
|
|
138
|
+
return candidate;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
366
144
|
}
|
|
367
145
|
exports.FAM_Console_Util = FAM_Console_Util;
|
|
@@ -11,6 +11,7 @@ const fam_knowledge_data_model_1 = require("../_models/data-models/fam-knowledge
|
|
|
11
11
|
const fam_memory_data_model_1 = require("../_models/data-models/fam-memory.data-model");
|
|
12
12
|
const fam_reference_data_model_1 = require("../_models/data-models/fam-reference.data-model");
|
|
13
13
|
const fam_rules_data_model_1 = require("../_models/data-models/fam-rules.data-model");
|
|
14
|
+
const fam_scan_job_data_model_1 = require("../_models/data-models/fam-scan-job.data-model");
|
|
14
15
|
const fam_scope_data_model_1 = require("../_models/data-models/fam-scope.data-model");
|
|
15
16
|
/**
|
|
16
17
|
* A FAM perzisztens collection-jeinek `dataParams`-listája (MP-1, dsgn-001) — **SSOT**.
|
|
@@ -32,6 +33,7 @@ exports.FAM_DB_MODELS = [
|
|
|
32
33
|
fam_reference_data_model_1.famReference_dataParams, // fam_reference
|
|
33
34
|
fam_scope_data_model_1.famScope_dataParams, // fam_scopes
|
|
34
35
|
fam_ingest_run_data_model_1.famIngestRun_dataParams, // fam_ingest_runs
|
|
36
|
+
fam_scan_job_data_model_1.famScanJob_dataParams, // fam_scan_jobs (FEAT-008 Phase 4 — durable scan-job + auto-resume)
|
|
35
37
|
// Config-réteg (MP-7, dsgn-007): DB-backed scoped config (global<table<scope).
|
|
36
38
|
fam_config_data_model_1.famConfig_dataParams, // fam_config
|
|
37
39
|
];
|
|
@@ -9,6 +9,7 @@ const app_server_1 = require("../app.server");
|
|
|
9
9
|
const package_json_1 = require("../../package.json");
|
|
10
10
|
const mcp_1 = require("../_modules/mcp");
|
|
11
11
|
const fam_client_service_1 = require("../_cli/_services/fam-client.service");
|
|
12
|
+
const serve_command_1 = require("../_cli/_commands/serve.command");
|
|
12
13
|
const register_commands_1 = require("../_cli/register-commands");
|
|
13
14
|
const fam_console_util_1 = require("./fam-console.util");
|
|
14
15
|
const fam_mcp_bridge_util_1 = require("./fam-mcp-bridge.util");
|
|
@@ -92,6 +93,23 @@ class FAM_EntryBootstrap_Util {
|
|
|
92
93
|
fsm_dynamo_1.DyFM_Log.warn(`[FAM] már fut egy egészséges FAM-primary (${client.getBaseUrl()}${versionTag}) — a második REST-boot kihagyva.${versionNote}`);
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
96
|
+
if (isStart && !serverAlreadyRunning) {
|
|
97
|
+
// AUTO-ENSURE (user-FR 2026-06-22: „minden fam funkciónak gondoskodnia kell a fam elindításáról"). Az MCP-
|
|
98
|
+
// stdio process NE a heavy REST-primary-t bootolja IN-PROCESS (az OOM-nál MEGHAL → a Claude-reconnect crash-
|
|
99
|
+
// loop-ol), hanem húzzon fel egy LEVÁLASZTOTT primary-t (túléli a reconnectet) és BRIDGE-eljen hozzá. A
|
|
100
|
+
// `waitMs:1` csak a spawn-t INDÍTJA (nem várja a teljes pool-bootot) → az MCP-handshake (ListTools, ami nem
|
|
101
|
+
// igényli a primary-t) AZONNAL kész; a tool-CALL-ok a bridge-önhealen várnak/retry-znek a boot-ra.
|
|
102
|
+
const ensured = await serve_command_1.FAM_Serve_Util.ensureRunning({ waitMs: 1 });
|
|
103
|
+
const spawnUnavailable = ensured.reason === 'no-entry' || ensured.reason === 'spawn-failed' || ensured.reason === 'remote-unreachable';
|
|
104
|
+
if (!spawnUnavailable) {
|
|
105
|
+
fsm_dynamo_1.DyFM_Log.info(`[FAM] auto-ensure: leválasztott primary (${ensured.reason}, ${ensured.url}) → MCP-stdio BRIDGE `
|
|
106
|
+
+ '(a primary a háttérben bootol; a tool-hívások önhealnek).');
|
|
107
|
+
await fam_mcp_bridge_util_1.FAM_McpBridge_Util.start({ name: MCP_SERVER_NAME, version: package_json_1.version });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// A leválasztott spawn nem indítható (nincs belépő / spawn-hiba / remote-URL) → VISSZA a bevált in-process PRIMARY-ra.
|
|
111
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM] auto-ensure: a leválasztott primary nem indítható (${ensured.reason}) → in-process PRIMARY boot (fallback).`);
|
|
112
|
+
}
|
|
95
113
|
// PRIMARY-jelölt (nincs egészséges futó primary). Port pre-flight: ha a portot MÁS tartja (egy nem-FAM
|
|
96
114
|
// process, VAGY egy fél-halott FAM-szerver, ami már nem válaszol a `/api/health`-en), a kötés EADDRINUSE-
|
|
97
115
|
// szal bukna egy nyers stack-kel. Ehelyett TISZTA, akcióképes hibát adunk és kilépünk (nem crashelünk).
|
|
@@ -105,14 +123,10 @@ class FAM_EntryBootstrap_Util {
|
|
|
105
123
|
}
|
|
106
124
|
// PRIMARY (nincs futó szerver, a port szabad) — a teljes szerver felhúzása.
|
|
107
125
|
fsm_dynamo_1.DyFM_Log.info(`[FAM] PRIMARY mód: nincs futó FAM-primary → teljes szerver felhúzása (REST :${SERVER_PORT}`
|
|
108
|
-
+ `${isStart ? ' + MCP stdio' : ''})
|
|
109
|
-
+ `${fam_console_util_1.FAM_Console_Util.isEnabled() ? ` (${fam_console_util_1.FAM_Console_Util.isVerbose() ? 'VERBOSE — minden log' : 'kurált'})` : ''}.`);
|
|
126
|
+
+ `${isStart ? ' + MCP stdio' : ''}).`);
|
|
110
127
|
// File-log (FAM_LOG_FILE): a `fam serve` ezt állítja be → a detached szerver logjai (köztük az
|
|
111
|
-
// import-progress) FÁJLBÓL OLVASHATÓK (tail)
|
|
128
|
+
// import-progress) FÁJLBÓL OLVASHATÓK (tail) a látható szerver-ablak MELLETT. Fail-safe, env-gated.
|
|
112
129
|
fam_console_util_1.FAM_Console_Util.installFileLog();
|
|
113
|
-
// (FEAT-001) A PRIMARY egy KÜLÖN, látható activity-konzol-ablakot nyit (env-gated, fail-safe,
|
|
114
|
-
// docker-ben OFF). Az MCP-stdio JSON-RPC csatornát NEM érinti (külön monitor-process tail-el).
|
|
115
|
-
fam_console_util_1.FAM_Console_Util.openActivityConsole({ port: SERVER_PORT });
|
|
116
130
|
// (1) REST szerver felhúzása (a DyNTS_App ctor indítja a connect + setup + postProcess láncot;
|
|
117
131
|
// a DyNTS_App a side-effect-konstruktor mintát követi — az `index.ts` is így indít).
|
|
118
132
|
FAM_EntryBootstrap_Util.startRestApp();
|
|
@@ -37,6 +37,7 @@ class FAM_ErrorContext_Util {
|
|
|
37
37
|
FAM_ErrorContext_Util.setNumber(snapshot, 'chunkIndex', input.chunkIndex);
|
|
38
38
|
FAM_ErrorContext_Util.setString(snapshot, 'query', input.query);
|
|
39
39
|
FAM_ErrorContext_Util.setString(snapshot, 'provider', input.provider);
|
|
40
|
+
FAM_ErrorContext_Util.setString(snapshot, 'failedSample', input.failedSample);
|
|
40
41
|
// Ha minden mező hiányzott, NINCS snapshot (a rekordon nem jelenik meg üres objektum).
|
|
41
42
|
return Object.keys(snapshot).length ? snapshot : undefined;
|
|
42
43
|
}
|
|
@@ -4,6 +4,7 @@ exports.FAM_McpBridge_Util = void 0;
|
|
|
4
4
|
const fam_core_tools_const_1 = require("../_modules/mcp/_collections/fam-core-tools.const");
|
|
5
5
|
const fam_mcp_adapter_service_1 = require("../_modules/mcp/_services/fam-mcp-adapter.service");
|
|
6
6
|
const fam_client_service_1 = require("../_cli/_services/fam-client.service");
|
|
7
|
+
const serve_command_1 = require("../_cli/_commands/serve.command");
|
|
7
8
|
/**
|
|
8
9
|
* `FAM_McpBridge_Util` (FEAT-002, topology „egy szerver, sok bridge") — ha `fam start`-kor MÁR fut egy
|
|
9
10
|
* FAM-szerver (primary, REST a 39265-ön), az új process NEM bootol második szervert (port-ütközés +
|
|
@@ -31,7 +32,20 @@ class FAM_McpBridge_Util {
|
|
|
31
32
|
description: tool.description,
|
|
32
33
|
inputSchema: tool.inputSchema,
|
|
33
34
|
handler: async (args) => {
|
|
34
|
-
|
|
35
|
+
let result = await client.post(`/api/${tool.name}`, args ?? {});
|
|
36
|
+
// ÖNHEAL (user-FR 2026-06-22): ha a primary időközben leesett (`unreachable`), NE bukjunk némán —
|
|
37
|
+
// automatikusan felhúzzuk a leválasztott primary-t (`ensureRunning`) és EGYSZER újrapróbáljuk. Ha a
|
|
38
|
+
// primary épp bootol (pool-hidratálás), tiszta „indul, próbáld újra" hibát adunk (a következő hívás megy).
|
|
39
|
+
if (!result.ok && result.unreachable) {
|
|
40
|
+
const ensured = await serve_command_1.FAM_Serve_Util.ensureRunning();
|
|
41
|
+
if (ensured.running) {
|
|
42
|
+
result = await client.post(`/api/${tool.name}`, args ?? {});
|
|
43
|
+
}
|
|
44
|
+
else if (ensured.reason === 'boot-timeout') {
|
|
45
|
+
throw new Error('FAM-MCP-BRIDGE-002: A FAM primary most indul (pool-hidratálás folyamatban) — '
|
|
46
|
+
+ 'próbáld újra pár másodperc múlva. (Az auto-indítás már elindult, leválasztva.)');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
35
49
|
if (!result.ok) {
|
|
36
50
|
// A primary hibáját MCP-hibaként felszínre hozzuk (NEM némítjuk; dsgn-008).
|
|
37
51
|
const code = result.error?.errorCode ?? 'FAM-MCP-BRIDGE-001';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_OperationQueue_Service = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `FAM_OperationQueue_Service` (FEAT-008, user-FR 2026-06-22) — **szerver-oldali soros/limitált-konkurrencia QUEUE**
|
|
6
|
+
* a NEHÉZ műveletekhez (scan/import/re-embed). A user-direktíva: a SZERVER vezérelje, hogy egyszerre NE fusson túl
|
|
7
|
+
* sok nehéz művelet — ha több agenttől egyidejű write-parancsok érkeznek, a szerver SORBA rakja (FIFO) és úgy
|
|
8
|
+
* dolgozza fel, NEM párhuzamosan. (Ez okozta a `war-factory`/`warbots` átfedést: a kliens-oldali orchestráció
|
|
9
|
+
* desync-elt — a kliens timeout-olt egy hosszú scant és elindította a következőt, miközben a szerver az elsőt még
|
|
10
|
+
* darálta. Ezzel a queue-val a 2. write MEGVÁRJA az elsőt → soros, átfedés-mentes feldolgozás a kliens-káosztól
|
|
11
|
+
* függetlenül.)
|
|
12
|
+
*
|
|
13
|
+
* Egyszerű async-szemafór: `maxConcurrency` default **1** (teljes szerializáció a nehéz ops-ra). A könnyű ops
|
|
14
|
+
* (create/update/delete/read) NEM mennek ezen át (gyorsak, nincs korrupciós kockázat). Statikus singleton —
|
|
15
|
+
* process-szintű (egy szerver = egy queue). Bukott task is felszabadít (finally) → nem akad meg a queue.
|
|
16
|
+
*/
|
|
17
|
+
class FAM_OperationQueue_Service {
|
|
18
|
+
/** A process-szintű singleton. */
|
|
19
|
+
static instance;
|
|
20
|
+
/** A singleton elérése (lazy). */
|
|
21
|
+
static getInstance() {
|
|
22
|
+
if (!FAM_OperationQueue_Service.instance) {
|
|
23
|
+
FAM_OperationQueue_Service.instance = new FAM_OperationQueue_Service();
|
|
24
|
+
}
|
|
25
|
+
return FAM_OperationQueue_Service.instance;
|
|
26
|
+
}
|
|
27
|
+
/** A konkurrencia-plafon (≥1). Default 1 = teljes szerializáció (egyszerre egy nehéz művelet). */
|
|
28
|
+
maxConcurrency = 1;
|
|
29
|
+
/** Hány task fut épp. */
|
|
30
|
+
running = 0;
|
|
31
|
+
/** A szabad slot-ra várók resolver-ei (FIFO sor). */
|
|
32
|
+
waiters = [];
|
|
33
|
+
/** A konkurrencia-plafon állítása (≥1). Növelésnél azonnal besorolja a várakozókat. */
|
|
34
|
+
setMaxConcurrency(value) {
|
|
35
|
+
this.maxConcurrency = Math.max(1, Math.floor(value));
|
|
36
|
+
this.drain();
|
|
37
|
+
}
|
|
38
|
+
/** Observability (a `/health`-be / progress-be köthető): hány fut + hány vár + a plafon. */
|
|
39
|
+
stats() {
|
|
40
|
+
return { running: this.running, queued: this.waiters.length, maxConcurrency: this.maxConcurrency };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A `task` BESOROLÁSA: megvárja a szabad slot-ot (FIFO), lefuttatja, majd felszabadít. A hiba is felszabadít
|
|
44
|
+
* (`finally`) — egy bukott task NEM akasztja meg a queue-t. A visszatérés a task eredménye (átlátszó wrapper).
|
|
45
|
+
*/
|
|
46
|
+
async enqueue(task) {
|
|
47
|
+
await this.acquire();
|
|
48
|
+
try {
|
|
49
|
+
return await task();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
this.release();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Slot-foglalás: ha van szabad → azonnal; különben FIFO-sorba (a resolver a `drain`-ben fut le). */
|
|
56
|
+
acquire() {
|
|
57
|
+
if (this.running < this.maxConcurrency) {
|
|
58
|
+
this.running++;
|
|
59
|
+
return Promise.resolve();
|
|
60
|
+
}
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
this.waiters.push(resolve);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Slot-felszabadítás + a következő várakozó besorolása. */
|
|
66
|
+
release() {
|
|
67
|
+
this.running = Math.max(0, this.running - 1);
|
|
68
|
+
this.drain();
|
|
69
|
+
}
|
|
70
|
+
/** A szabad slot-okra besorol a várakozókból (FIFO), amíg van hely ÉS van várakozó. */
|
|
71
|
+
drain() {
|
|
72
|
+
while (this.running < this.maxConcurrency && this.waiters.length > 0) {
|
|
73
|
+
const next = this.waiters.shift();
|
|
74
|
+
if (next) {
|
|
75
|
+
this.running++;
|
|
76
|
+
next();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** TESZT-only: a singleton eldobása (friss állapot a spec-ekhez). */
|
|
81
|
+
static resetForTest() {
|
|
82
|
+
FAM_OperationQueue_Service.instance = undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.FAM_OperationQueue_Service = FAM_OperationQueue_Service;
|