@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.
Files changed (89) hide show
  1. package/build/package.json +2 -2
  2. package/build/src/_cli/_commands/find-duplicates.command.js +41 -16
  3. package/build/src/_cli/_commands/scan-projects.command.js +113 -113
  4. package/build/src/_cli/_commands/scan.command.js +2 -0
  5. package/build/src/_cli/_commands/serve.command.js +110 -10
  6. package/build/src/_cli/_services/fam-client.service.js +25 -1
  7. package/build/src/_cli/register-commands.js +28 -0
  8. package/build/src/_collections/config-catalog.const.js +152 -33
  9. package/build/src/_collections/error-codes.const.js +2 -0
  10. package/build/src/_collections/fam-console.util.js +38 -260
  11. package/build/src/_collections/fam-db-models.const.js +2 -0
  12. package/build/src/_collections/fam-entry-bootstrap.util.js +20 -6
  13. package/build/src/_collections/fam-error-context.util.js +1 -0
  14. package/build/src/_collections/fam-mcp-bridge.util.js +15 -1
  15. package/build/src/_collections/fam-operation-queue.service.js +85 -0
  16. package/build/src/_collections/fam-project-discovery.util.js +148 -0
  17. package/build/src/_collections/fam-request-origin.util.js +41 -0
  18. package/build/src/_collections/fam-retry.util.js +48 -0
  19. package/build/src/_collections/fam-scan-progress-sink.service.js +26 -0
  20. package/build/src/_collections/title.const.js +26 -26
  21. package/build/src/_enums/fam-rule-scope.type-enum.js +25 -0
  22. package/build/src/_models/data-models/fam-entry.data-model.js +29 -0
  23. package/build/src/_models/data-models/fam-memory.data-model.js +5 -0
  24. package/build/src/_models/data-models/fam-rules.data-model.js +24 -0
  25. package/build/src/_models/data-models/fam-scan-job.data-model.js +73 -0
  26. package/build/src/_models/data-models/fam-scope.data-model.js +9 -0
  27. package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +1 -1
  28. package/build/src/_modules/embedding/_services/fam-dedup-warn.control-service.js +40 -0
  29. package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +74 -16
  30. package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +50 -0
  31. package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +32 -6
  32. package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +142 -9
  33. package/build/src/_modules/embedding/_services/fam-entry.data-service.js +32 -1
  34. package/build/src/_modules/embedding/_services/fam-hydration-coordinator.control-service.js +159 -0
  35. package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +41 -4
  36. package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +67 -12
  37. package/build/src/_modules/embedding/index.js +5 -1
  38. package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +10 -0
  39. package/build/src/_modules/ingest/_collections/fam-frontmatter.util.js +134 -0
  40. package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +30 -0
  41. package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +5 -2
  42. package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +55 -29
  43. package/build/src/_modules/ingest/_collections/fam-split-chunker.util.js +139 -0
  44. package/build/src/_modules/ingest/_models/interfaces/fam-scan-job.interface.js +2 -0
  45. package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +10 -0
  46. package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +68 -1
  47. package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +1 -1
  48. package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +177 -37
  49. package/build/src/_modules/ingest/_services/fam-scan-job.control-service.js +429 -0
  50. package/build/src/_modules/ingest/_services/fam-scan-job.data-service.js +34 -0
  51. package/build/src/_modules/ingest/_services/fam-scan.control-service.js +6 -4
  52. package/build/src/_modules/ingest/index.js +6 -1
  53. package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +1 -1
  54. package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +264 -41
  55. package/build/src/_modules/mcp/_services/fam-read-tool.service.js +71 -1
  56. package/build/src/_modules/mcp/_services/fam-write-tool.service.js +36 -0
  57. package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
  58. package/build/src/_modules/retrieval/_collections/fam-lexical-match.util.js +69 -0
  59. package/build/src/_modules/retrieval/_collections/fam-memory-activation.util.js +66 -0
  60. package/build/src/_modules/retrieval/_collections/fam-rule-injection.util.js +125 -0
  61. package/build/src/_modules/retrieval/_collections/fam-rule-propagation.util.js +95 -0
  62. package/build/src/_modules/retrieval/_collections/fam-rule-scope-context.util.js +74 -0
  63. package/build/src/_modules/retrieval/_services/fam-global-rule.control-service.js +82 -0
  64. package/build/src/_modules/retrieval/_services/fam-memory-cold-search.control-service.js +121 -0
  65. package/build/src/_modules/retrieval/_services/fam-memory-dormancy.control-service.js +99 -0
  66. package/build/src/_modules/retrieval/_services/fam-memory-reactivation.control-service.js +68 -0
  67. package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +15 -2
  68. package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +197 -5
  69. package/build/src/_modules/retrieval/_services/fam-rule-injection.control-service.js +102 -0
  70. package/build/src/_modules/retrieval/index.js +12 -1
  71. package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +134 -0
  72. package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +423 -0
  73. package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
  74. package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +25 -0
  75. package/build/src/_modules/scope-reference/index.js +4 -1
  76. package/build/src/_routes/server/api/api.controller.js +324 -23
  77. package/build/src/_routes/server/api/fam-doctor.control-service.js +156 -0
  78. package/build/src/_routes/server/config/config.control-service.js +2 -2
  79. package/build/src/app.server.js +29 -27
  80. package/client-dist/{chunk-YXHWCJ5O.js → chunk-GF3FJP7E.js} +71 -71
  81. package/client-dist/chunk-NMUCMQY6.js +1 -0
  82. package/client-dist/favicon.ico +0 -0
  83. package/client-dist/index.html +3 -3
  84. package/client-dist/main-YK4YIVAQ.js +2 -0
  85. package/package.json +2 -2
  86. package/build/src/_cli/_collections/fam-project-discovery.util.js +0 -98
  87. package/build/src/_collections/fam-heap-guard.control-service.js +0 -176
  88. package/client-dist/chunk-I77GXVAQ.js +0 -1
  89. 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` (FEAT-001) `fam start`-kor (a PRIMARY példánynál) megnyit egy KÜLÖN, vizuálisan
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
- * **Miért nem a process saját konzolja?** MCP-stdio módban a process stdout-ja a JSON-RPC csatorna —
15
- * azt NEM szabad konzol-ablakká tenni. Ezért a látható ablak egy KÜLÖN monitor-process, ami a szerver
16
- * **activity-logját tail-eli**: a `tee`-vel a `console.*` kimenetet egy log-fájlba is írjuk (a meglévő
17
- * stderr/stdout mellé), és egy platform-natív terminál-ablakot indítunk, ami azt a fájlt követi.
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
- * **Default ON.** Kikapcsolható: `FAM_SHOW_CONSOLE=false|0|off|no` (a docker-compose ezt állítja, mert
20
- * a konténerben nincs GUI). Minden lépés **fail-safe** (try/catch) az ablak hibája SOHA nem töri a bootot.
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
- * **Részletesség (`isVerbose`, `FAM_CONSOLE_VERBOSE`):** default VERBOSE → MINDEN `console.*` log-sor a
23
- * monitorba kerül (user-direktíva). `FAM_CONSOLE_VERBOSE=false` kurált allowlist (`isActivityRelevant`).
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
- * A monitor-lock **frissesség-küszöbe** (ms): ha a lockfile mtime-ja ennél régebbi, a monitor-ablakot
28
- * HALOTTNAK tekintjük (bezárták) új nyitható. A nyitott ablak `MONITOR_HEARTBEAT_MS`-enként frissíti a
29
- * lockot (a PID-alapú check helyett az törékeny: async PID-write-race a `start`-detach miatt + Windows
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 PRIMARY szerver activity-konzol-ablakának megnyitása: log-fájl-tee + platform-natív monitor-ablak.
104
- * Disabled (`FAM_SHOW_CONSOLE` off) vagy bármilyen hiba esetén CSENDES no-op (a boot folytatódik).
105
- */
106
- static openActivityConsole(set) {
107
- if (!FAM_Console_Util.isEnabled()) {
108
- return;
109
- }
110
- try {
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` (mint a FEAT-001 monitor) → NINCS `-Command`-escaping-rémálom; `-NoExit` → az ablak
319
- // a szerver kilépése (vagy crash) UTÁN is nyitva marad (a hiba látható).
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 (`$env:X='...'`),
348
- * majd a szerver futtatása (`& command args`). A `'`-eket megdupláljuk (PS string-escape). A `-NoExit`
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' : ''}). Activity-monitor: ${fam_console_util_1.FAM_Console_Util.isEnabled() ? 'ON' : 'OFF'}`
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), a monitor-ablak nélkül is. Fail-safe, env-gated.
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
- const result = await client.post(`/api/${tool.name}`, args ?? {});
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;