@futdevpro/fdp-agent-memory 0.1.0 → 1.1.12

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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -7
  3. package/build/package.json +6 -5
  4. package/build/src/_cli/_collections/fam-arg.util.js +48 -0
  5. package/build/src/_cli/_collections/fam-cli.const.js +40 -0
  6. package/build/src/_cli/_collections/fam-output.util.js +86 -0
  7. package/build/src/_cli/_collections/fam-project-discovery.util.js +98 -0
  8. package/build/src/_cli/_commands/capture.command.js +73 -0
  9. package/build/src/_cli/_commands/config.command.js +93 -0
  10. package/build/src/_cli/_commands/doctor.command.js +124 -0
  11. package/build/src/_cli/_commands/errors.command.js +66 -0
  12. package/build/src/_cli/_commands/export.command.js +65 -0
  13. package/build/src/_cli/_commands/find-duplicates.command.js +97 -0
  14. package/build/src/_cli/_commands/import.command.js +136 -0
  15. package/build/src/_cli/_commands/init.command.js +147 -0
  16. package/build/src/_cli/_commands/read.command.js +109 -0
  17. package/build/src/_cli/_commands/scan-projects.command.js +138 -0
  18. package/build/src/_cli/_commands/scan.command.js +98 -0
  19. package/build/src/_cli/_commands/seed.command.js +40 -0
  20. package/build/src/_cli/_commands/serve.command.js +350 -0
  21. package/build/src/_cli/_commands/start.command.js +134 -0
  22. package/build/src/_cli/_commands/stats.command.js +54 -0
  23. package/build/src/_cli/_commands/write.command.js +103 -0
  24. package/build/src/_cli/_models/interfaces/fam-cli-global-options.interface.js +2 -0
  25. package/build/src/_cli/_models/interfaces/fam-cli-output.interface.js +9 -0
  26. package/build/src/_cli/_models/interfaces/fam-client-result.interface.js +2 -0
  27. package/build/src/_cli/_services/fam-client.service.js +140 -0
  28. package/build/src/_cli/register-commands.js +86 -0
  29. package/build/src/_collections/config-catalog.const.js +67 -1
  30. package/build/src/_collections/fam-console.util.js +367 -0
  31. package/build/src/_collections/fam-entry-bootstrap.util.js +158 -4
  32. package/build/src/_collections/fam-error-factory.util.js +0 -9
  33. package/build/src/_collections/fam-mcp-bridge.util.js +49 -0
  34. package/build/src/_collections/fam-reference-code.util.js +105 -0
  35. package/build/src/_collections/fam-version.const.js +10 -0
  36. package/build/src/_models/data-models/fam-entry-base-properties.const.js +1 -0
  37. package/build/src/_models/data-models/fam-entry.data-model.js +6 -0
  38. package/build/src/_models/data-models/fam-ingest-run.data-model.js +3 -1
  39. package/build/src/_models/data-models/fam-reference.data-model.js +7 -0
  40. package/build/src/_modules/capture/_collections/fam-capture.const.js +11 -0
  41. package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +87 -0
  42. package/build/src/_modules/capture/index.js +8 -0
  43. package/build/src/_modules/embedding/_collections/fam-embedding-prefix.util.js +77 -0
  44. package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +202 -0
  45. package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +33 -9
  46. package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +21 -2
  47. package/build/src/_modules/embedding/_services/fam-entry.data-service.js +135 -0
  48. package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +42 -32
  49. package/build/src/_modules/embedding/index.js +4 -1
  50. package/build/src/_modules/export/_collections/fam-export.const.js +22 -0
  51. package/build/src/_modules/export/_services/fam-export.control-service.js +64 -0
  52. package/build/src/_modules/export/index.js +8 -0
  53. package/build/src/_modules/ingest/_collections/fam-famignore.util.js +83 -0
  54. package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +59 -48
  55. package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +134 -0
  56. package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +57 -0
  57. package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +60 -0
  58. package/build/src/_modules/ingest/_collections/fam-scan-weight.util.js +53 -0
  59. package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +37 -14
  60. package/build/src/_modules/ingest/_collections/fam-sliding-chunker.util.js +34 -0
  61. package/build/src/_modules/ingest/_collections/fam-ts-chunker.util.js +200 -14
  62. package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +4 -1
  63. package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +7 -4
  64. package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +346 -17
  65. package/build/src/_modules/ingest/_services/fam-scan.control-service.js +25 -2
  66. package/build/src/_modules/ingest/index.js +3 -1
  67. package/build/src/_modules/mcp/_collections/fam-active-rules.util.js +56 -0
  68. package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +47 -6
  69. package/build/src/_modules/mcp/_services/fam-capabilities-tool.service.js +4 -4
  70. package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +224 -18
  71. package/build/src/_modules/mcp/_services/fam-mcp-adapter.service.js +4 -4
  72. package/build/src/_modules/mcp/_services/fam-mcp-server.service.js +4 -4
  73. package/build/src/_modules/mcp/_services/fam-read-tool.service.js +53 -1
  74. package/build/src/_modules/mcp/_services/fam-write-tool.service.js +104 -8
  75. package/build/src/_modules/mcp/index.js +4 -4
  76. package/build/src/_modules/migration/_collections/fam-claude-mem-normalize.util.js +66 -3
  77. package/build/src/_modules/migration/_collections/fam-prompt-aggregate.util.js +143 -0
  78. package/build/src/_modules/migration/_collections/fam-target-mapping.util.js +19 -0
  79. package/build/src/_modules/migration/_enums/fam-claude-mem-source.type-enum.js +6 -0
  80. package/build/src/_modules/migration/_models/interfaces/fam-claude-mem.interface.js +5 -0
  81. package/build/src/_modules/migration/_services/fam-agent-memory-reader.service.js +125 -0
  82. package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +101 -18
  83. package/build/src/_modules/migration/_services/fam-import-dedup.data-service.js +53 -0
  84. package/build/src/_modules/migration/index.js +3 -1
  85. package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +78 -4
  86. package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +293 -50
  87. package/build/src/_modules/scope-reference/_collections/fam-scope-normalize.util.js +6 -3
  88. package/build/src/_modules/scope-reference/_services/fam-reference.data-service.js +18 -0
  89. package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +79 -20
  90. package/build/src/_routes/server/api/api.controller.js +34 -2
  91. package/build/src/_routes/server/client-app/client-app.control-service.js +1 -1
  92. package/build/src/_routes/server/server-status/server-status.controller.js +2 -1
  93. package/build/src/app.server.js +13 -1
  94. package/build/src/environments/environment.js +1 -1
  95. package/build/src/index.js +1 -1
  96. package/client-dist/{chunk-GHKRM4SM.js → chunk-I77GXVAQ.js} +1 -1
  97. package/client-dist/{chunk-LMTL7GA3.js → chunk-YXHWCJ5O.js} +1 -1
  98. package/client-dist/index.html +1 -1
  99. package/client-dist/{main-2KWB3QYK.js → main-PJPEDVJT.js} +1 -1
  100. package/package.json +6 -5
@@ -0,0 +1,367 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_Console_Util = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const child_process_1 = require("child_process");
6
+ const fs = tslib_1.__importStar(require("fs"));
7
+ const os = tslib_1.__importStar(require("os"));
8
+ const path = tslib_1.__importStar(require("path"));
9
+ const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
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.
13
+ *
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.
18
+ *
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.
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`).
24
+ */
25
+ class FAM_Console_Util {
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).
60
+ */
61
+ static installFileLog() {
62
+ const logPath = (process.env.FAM_LOG_FILE ?? '').trim();
63
+ if (!logPath) {
64
+ return;
65
+ }
66
+ try {
67
+ fs.appendFileSync(logPath, `# --- FAM server log — (re)start ${new Date().toISOString()} ---\n`);
68
+ }
69
+ catch {
70
+ return; // nem írható a fájl → csendben kihagyjuk (nem blokkoljuk a bootot)
71
+ }
72
+ const append = (line) => {
73
+ try {
74
+ fs.appendFileSync(logPath, `${line}\n`);
75
+ }
76
+ catch {
77
+ /* a fájl-append hibája soha nem dönti meg a szervert */
78
+ }
79
+ };
80
+ const stringify = (value) => {
81
+ if (typeof value === 'string') {
82
+ return value;
83
+ }
84
+ try {
85
+ return JSON.stringify(value);
86
+ }
87
+ catch {
88
+ return String(value);
89
+ }
90
+ };
91
+ const wrap = (original) => {
92
+ return (...args) => {
93
+ original(...args);
94
+ append(args.map(stringify).join(' '));
95
+ };
96
+ };
97
+ console.log = wrap(console.log);
98
+ console.info = wrap(console.info);
99
+ console.warn = wrap(console.warn);
100
+ console.error = wrap(console.error);
101
+ }
102
+ /**
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).
315
+ */
316
+ static spawnServerWindow(set) {
317
+ 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ó).
320
+ const scriptPath = path.join(os.tmpdir(), `fam-server-${set.port}.ps1`);
321
+ fs.writeFileSync(scriptPath, FAM_Console_Util.windowsServerScript(set));
322
+ 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 });
323
+ child.unref();
324
+ return;
325
+ }
326
+ // mac/linux: a terminál a szervert futtatja; a kilépés után az ablak nyitva marad (`exec $SHELL`).
327
+ const exports = Object.entries(set.envOverrides)
328
+ .map(([key, value]) => `export ${key}='${value.replace(/'/g, `'\\''`)}'`).join('; ');
329
+ const invocation = [set.command, ...set.args].map((arg) => `'${arg.replace(/'/g, `'\\''`)}'`).join(' ');
330
+ const shellCmd = `${exports}; ${invocation}; echo; echo '[FAM szerver kilépett — fam serve --restart újraindítja]'; exec $SHELL`;
331
+ if (process.platform === 'darwin') {
332
+ const script = `tell application "Terminal" to do script "${shellCmd.replace(/"/g, '\\"')}"`;
333
+ const child = (0, child_process_1.spawn)('osascript', ['-e', script], { detached: true, stdio: 'ignore' });
334
+ child.unref();
335
+ return;
336
+ }
337
+ const terminal = FAM_Console_Util.findLinuxTerminal();
338
+ if (!terminal) {
339
+ fsm_dynamo_1.DyFM_Log.warn('[FAM serve] nincs ismert Linux terminál-emulátor — futtasd `fam serve --foreground`-dal '
340
+ + '(vagy nyiss kézzel egy ablakot a szerverhez).');
341
+ return;
342
+ }
343
+ const child = (0, child_process_1.spawn)(terminal, ['-e', 'sh', '-c', shellCmd], { detached: true, stdio: 'ignore' });
344
+ child.unref();
345
+ }
346
+ /**
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.
350
+ */
351
+ static windowsServerScript(set) {
352
+ const esc = (value) => value.replace(/'/g, "''");
353
+ const lines = [
354
+ 'try { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 } catch {}',
355
+ `try { $host.UI.RawUI.WindowTitle='FAM server :${set.port}' } catch {}`,
356
+ ];
357
+ for (const [key, value] of Object.entries(set.envOverrides)) {
358
+ lines.push(`$env:${key}='${esc(value)}'`);
359
+ }
360
+ const invocation = [set.command, ...set.args].map((arg) => `'${esc(arg)}'`).join(' ');
361
+ lines.push(`& ${invocation}`);
362
+ lines.push("Write-Host ''");
363
+ lines.push("Write-Host \"[FAM szerver kilépett — exit $LASTEXITCODE — 'fam serve --restart' újraindítja]\"");
364
+ return lines.join('\r\n');
365
+ }
366
+ }
367
+ exports.FAM_Console_Util = FAM_Console_Util;
@@ -1,15 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FAM_EntryBootstrap_Util = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const net = tslib_1.__importStar(require("net"));
6
+ const commander_1 = require("commander");
7
+ const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
4
8
  const app_server_1 = require("../app.server");
5
9
  const package_json_1 = require("../../package.json");
6
10
  const mcp_1 = require("../_modules/mcp");
11
+ const fam_client_service_1 = require("../_cli/_services/fam-client.service");
12
+ const register_commands_1 = require("../_cli/register-commands");
13
+ const fam_console_util_1 = require("./fam-console.util");
14
+ const fam_mcp_bridge_util_1 = require("./fam-mcp-bridge.util");
15
+ /** A REST-szerver portja (env-override; default 39265). */
16
+ const SERVER_PORT = Number(process.env.FAM_SERVER_PORT) || 39265;
7
17
  /** Az MCP-szerver SDK-neve (REQ-001, dsgn-003 §5). */
8
18
  const MCP_SERVER_NAME = 'fdp-agent-memory';
9
19
  /**
10
20
  * `FAM_EntryBootstrap_Util` (MP-6, dsgn-010 §6 / dsgn-012 §4) — a `node build/src/index.js [start]`
11
21
  * (és a publikált `fam start` bin) belépési orchestrátora. A `fam start` MINDKÉT csatornát felhúzza:
12
- * - **REST szerver** (port 39185, `App extends DyNTS_App`) — a CLI (MP-9) + UI (MP-10) belépője,
22
+ * - **REST szerver** (port 39265, `App extends DyNTS_App`) — a CLI (MP-9) + UI (MP-10) belépője,
13
23
  * - **MCP stdio szerver** — a `read`/`write`/`capabilities` 3 tool a JSON-RPC stdio transporton.
14
24
  *
15
25
  * A kettő KÜLÖN csatorna (stdio JSON-RPC vs. HTTP), így egyszerre futhat: a REST a HTTP-porton, az MCP
@@ -17,9 +27,14 @@ const MCP_SERVER_NAME = 'fdp-agent-memory';
17
27
  * `App` connect + `postProcess` boot).
18
28
  *
19
29
  * **stdio-konvenció (dsgn-003 §1, dsgn-010 §6):** stdio módban a stdout a JSON-RPC csatorna — minden
20
- * log/diag a stderr-re kerül. Ezért a boot ELŐTT a `console.log`/`info`/`warn` stdout-írásait a
21
- * stderr-re irányítjuk (a `DyFM_Log` ezeken megy); az MCP `StdioServerTransport` közvetlenül a
22
- * `process.stdout`-ra ír (nem console-on át), így a JSON-RPC framing tiszta marad.
30
+ * log/diag a stderr-re kerül. Ezért a boot ELŐTT KÉT védelmet húzunk fel:
31
+ * (1) `redirectConsoleToStderr` a `console.log`/`info`/`warn`/`debug` (amin a `DyFM_Log` megy) a
32
+ * stderr-re irányítása;
33
+ * (2) `guardStdoutForJsonRpc` — a `process.stdout.write` guard-olása: a `DyNTS_App` boot-bannerje és a
34
+ * terminál-ablakcím OSC-escape (`\x1b]0;…`) KÖZVETLENÜL a `process.stdout`-ra ír (NEM `console`-on
35
+ * át), így a (1) nem fogja meg — a guard nélkül ezek a JSON-RPC framing elé ragadnának és az
36
+ * MCP-kliens connect-je elbukna. A guard a JSON-RPC frame-eket (a `StdioServerTransport`
37
+ * `{…}\n` írásait) átengedi a valódi stdout-ra, minden mást a stderr-re terel.
23
38
  */
24
39
  class FAM_EntryBootstrap_Util {
25
40
  /**
@@ -30,10 +45,74 @@ class FAM_EntryBootstrap_Util {
30
45
  static async main(argv) {
31
46
  const command = argv[2];
32
47
  const isStart = command === 'start' || command === 'start-server';
48
+ const isRestOnly = command === undefined;
49
+ // (A) Management-CLI parancsok (init/doctor/read/write/scan/import/config/stats/errors/seed-example)
50
+ // — a beépített `fam` CLI (a szerver REST-jét hívó VÉKONY kliens; a publikus boundary szerint NEM
51
+ // importálja a szerver-logikát, REST-en megy). Ezekhez NEM húzzuk fel a szervert (dsgn-010 §2).
52
+ if (!isStart && !isRestOnly) {
53
+ FAM_EntryBootstrap_Util.runManagementCli(argv);
54
+ return;
55
+ }
33
56
  if (isStart) {
34
57
  // stdio-konvenció: a log-stdout-ot a stderr-re irányítjuk a JSON-RPC csatorna tisztaságáért.
58
+ // (1) console.* (DyFM_Log) → stderr, (2) process.stdout.write guard — a DyNTS_App boot-bannerje
59
+ // + a terminál-ablakcím OSC-escape közvetlenül a stdout-ra ír (console-t megkerülve), ezért a
60
+ // (1) önmagában nem elég; guard nélkül a JSON-RPC framing sérül és az MCP-connect bukik.
35
61
  FAM_EntryBootstrap_Util.redirectConsoleToStderr();
62
+ FAM_EntryBootstrap_Util.guardStdoutForJsonRpc();
63
+ }
64
+ // (FEAT-002, „egy szerver, sok bridge") — fut-e már egy EGÉSZSÉGES FAM-primary (REST `/api/health`
65
+ // a porton)? A `isReachable` timeout-os (fél-halott primary nem akaszt). A feloldott URL-t is
66
+ // logoljuk, hogy a user MINDIG lássa, melyik példányhoz/portra dől a döntés.
67
+ // KRITIKUS: a check a TÉNYLEGES bind-portot (`SERVER_PORT`) nézze, NEM a CLI-default 39265-öt — különben
68
+ // egy másik porton (pl. `FAM_SERVER_PORT=39191`, `fam serve --port`) induló szerver tévesen „már fut"-ot
69
+ // látna a globális 39265-re, és kihagyná a saját bootját. (Az explicit `FAM_SERVER_URL` továbbra is győz.)
70
+ const reachabilityUrl = process.env.FAM_SERVER_URL || `http://127.0.0.1:${SERVER_PORT}`;
71
+ const client = new fam_client_service_1.FAM_CliClient_Service(reachabilityUrl);
72
+ const serverAlreadyRunning = await client.isReachable();
73
+ // VERZIÓ-FIGYELŐ (user 2026-06-21: „ez figyeli a verziót?"): a reachability eddig CSAK a health-et nézte,
74
+ // a verziót NEM → egy RÉGEBBI futó primary-t is reuse-olt volna (az új kód nem futott volna). Most lekérjük
75
+ // a futó primary verzióját, és ha eltér a most-bootolótól, EXPLICITEN JELEZZÜK (a reuse a régi kóddal megy
76
+ // tovább — a `fam serve --restart` cseréli le; itt nem ölünk, csak figyelmeztetünk).
77
+ const runningVersion = serverAlreadyRunning ? await FAM_EntryBootstrap_Util.fetchPrimaryVersion(client) : null;
78
+ const versionNote = runningVersion && runningVersion !== package_json_1.version
79
+ ? ` ⚠ A futó primary v${runningVersion} ≠ a most-bootoló v${package_json_1.version} — az ÚJ kódhoz: 'fam serve --restart'.`
80
+ : '';
81
+ const versionTag = runningVersion ? `, v${runningVersion}` : '';
82
+ if (isStart && serverAlreadyRunning) {
83
+ // SECONDARY → MCP-stdio BRIDGE a futó primary-hoz (NINCS 2. REST/ablak/LVS-pool; a tool-ok a
84
+ // primary REST-tükrét hívják, így közös Mongo + hidratált pool — port-ütközés elkerülve).
85
+ fsm_dynamo_1.DyFM_Log.info(`[FAM] SECONDARY mód: már fut egy egészséges FAM-primary (${client.getBaseUrl()}${versionTag}) → `
86
+ + `MCP-stdio BRIDGE csatlakozás (közös Mongo + hidratált pool, NINCS 2. REST/monitor/pool).${versionNote}`);
87
+ await fam_mcp_bridge_util_1.FAM_McpBridge_Util.start({ name: MCP_SERVER_NAME, version: package_json_1.version });
88
+ return;
89
+ }
90
+ if (isRestOnly && serverAlreadyRunning) {
91
+ // REST-only boot, de már fut egy szerver → NEM bootolunk második REST-et (egy szerver elég).
92
+ 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
+ return;
36
94
  }
95
+ // PRIMARY-jelölt (nincs egészséges futó primary). Port pre-flight: ha a portot MÁS tartja (egy nem-FAM
96
+ // process, VAGY egy fél-halott FAM-szerver, ami már nem válaszol a `/api/health`-en), a kötés EADDRINUSE-
97
+ // szal bukna egy nyers stack-kel. Ehelyett TISZTA, akcióképes hibát adunk és kilépünk (nem crashelünk).
98
+ const portFree = await FAM_EntryBootstrap_Util.isServerPortFree(SERVER_PORT);
99
+ if (!portFree) {
100
+ fsm_dynamo_1.DyFM_Log.error(`[FAM] a(z) ${SERVER_PORT} port FOGLALT, de nem egy egészséges FAM-primary (a /api/health `
101
+ + 'nem válaszolt időben). Valószínű ok: egy korábbi, fél-halott FAM-szerver-process, vagy MÁS '
102
+ + 'alkalmazás tartja a portot. Teendő: (1) állítsd le a portot tartó processt, VAGY (2) indíts másik '
103
+ + 'porton (`FAM_SERVER_PORT=<port>`). A PRIMARY-boot megszakítva (nincs crash-stack).');
104
+ return;
105
+ }
106
+ // PRIMARY (nincs futó szerver, a port szabad) — a teljes szerver felhúzása.
107
+ 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'})` : ''}.`);
110
+ // 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.
112
+ 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 });
37
116
  // (1) REST szerver felhúzása (a DyNTS_App ctor indítja a connect + setup + postProcess láncot;
38
117
  // a DyNTS_App a side-effect-konstruktor mintát követi — az `index.ts` is így indít).
39
118
  FAM_EntryBootstrap_Util.startRestApp();
@@ -42,6 +121,51 @@ class FAM_EntryBootstrap_Util {
42
121
  await mcp_1.FAM_McpServer_Service.getInstance().start({ name: MCP_SERVER_NAME, version: package_json_1.version });
43
122
  }
44
123
  }
124
+ /**
125
+ * A beépített management-CLI (`fam <command>`, dsgn-010) felhúzása + parse-olása. A `commander`
126
+ * `program` singleton-re fűzi a 10 parancs-csoportot + 7 alias-t (`registerFAMCommands`), majd
127
+ * a `start`-ot itt NEM kezeljük (azt a `main` előbb elkapja → közvetlen szerver-boot). A parancs-
128
+ * handler-ek a saját `process.exitCode`-jukkal lépnek ki (a `commander` használati-hibái `2`-vel).
129
+ */
130
+ static runManagementCli(argv) {
131
+ commander_1.program
132
+ .name('fam')
133
+ .description('FDP Agent Memory — MCP-szerver + management CLI')
134
+ .version(package_json_1.version, '-v, --version', 'output the current version');
135
+ commander_1.program.exitOverride((error) => {
136
+ const cleanExit = ['commander.help', 'commander.helpDisplayed', 'commander.version'];
137
+ process.exit(cleanExit.includes(error.code) ? 0 : 2);
138
+ });
139
+ (0, register_commands_1.registerFAMCommands)(commander_1.program, package_json_1.version);
140
+ commander_1.program.parse(argv);
141
+ }
142
+ /**
143
+ * A futó FAM-primary verziója a `GET /api/health` `version` mezőjéből (a verzió-figyelőhöz). Fail-safe:
144
+ * hiba / hiányzó mező → `null` (a hívó a reuse-t verzió-jelölés nélkül folytatja).
145
+ */
146
+ static async fetchPrimaryVersion(client) {
147
+ try {
148
+ const result = await client.get('/api/health');
149
+ return result.ok && result.data && typeof result.data.version === 'string' ? result.data.version : null;
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ }
155
+ /**
156
+ * Szabad-e a szerver-port (egy rövid próba-`listen` a loopbackon, majd azonnali close)? `true` → köthető;
157
+ * `false` → már foglalt (EADDRINUSE / EACCES). A pre-flight a PRIMARY-boot ELŐTT fut: így a port-ütközést
158
+ * TISZTA hibaüzenettel kezeljük, nem egy nyers `DyNTS_App`-boot-közbeni EADDRINUSE-stack-kel. Fail-safe.
159
+ * (Apró TOCTOU-ablak marad a check és a valós kötés között — a ritka versenyt a kliens-reconnect oldja.)
160
+ */
161
+ static isServerPortFree(port) {
162
+ return new Promise((resolveCheck) => {
163
+ const tester = net.createServer();
164
+ tester.once('error', () => resolveCheck(false));
165
+ tester.once('listening', () => tester.close(() => resolveCheck(true)));
166
+ tester.listen(port, '127.0.0.1');
167
+ });
168
+ }
45
169
  /**
46
170
  * A REST `App` felhúzása (a `DyNTS_App` ctor a connect + setup + postProcess láncot indítja —
47
171
  * side-effect-konstruktor minta, az App-ot nem kell elkapni, a globális service-be regisztrálódik).
@@ -64,6 +188,36 @@ class FAM_EntryBootstrap_Util {
64
188
  console.warn = toStderr;
65
189
  console.debug = toStderr;
66
190
  }
191
+ /**
192
+ * A `process.stdout.write` guard-olása stdio módban (dsgn-003 §1). A `DyNTS_App` boot-bannerje és a
193
+ * terminál-ablakcím OSC-escape (`\x1b]0;…\x07`) KÖZVETLENÜL a `process.stdout`-ra ír (a `console.*`-ot
194
+ * megkerülve), ezért a `redirectConsoleToStderr` nem fogja meg — guard nélkül ezek a JSON-RPC frame elé
195
+ * ragadnának ugyanazon a soron, és az MCP-kliens parse-olása (connect) elbukna. A guard a JSON-RPC
196
+ * frame-eket (a `StdioServerTransport` `{…}\n` írásait) a VALÓDI stdout-ra engedi, minden mást a
197
+ * stderr-re terel. A `process.stdout` stream-objektum maga érintetlen (a transport `once('drain')`-je
198
+ * tovább működik); csak a `write` metódust burkoljuk.
199
+ */
200
+ static guardStdoutForJsonRpc() {
201
+ const realStdoutWrite = process.stdout.write.bind(process.stdout);
202
+ const guardedWrite = (chunk, encodingOrCb, callback) => {
203
+ const sink = FAM_EntryBootstrap_Util.isJsonRpcFrame(chunk)
204
+ ? realStdoutWrite
205
+ : process.stderr.write.bind(process.stderr);
206
+ return sink(chunk, encodingOrCb, callback);
207
+ };
208
+ // A felülírt overload-os szignatúrát egy lokális cast hidalja át (Node stream-típus plumbing).
209
+ process.stdout.write = guardedWrite;
210
+ }
211
+ /**
212
+ * Egy stdout-write chunk JSON-RPC frame-e? A `StdioServerTransport.send` a `serializeMessage`-dzsel
213
+ * `JSON.stringify(message) + '\n'`-t ír — mindig `{`-fel kezdődik és tartalmazza a `"jsonrpc"` mezőt.
214
+ * A boot-banner (ASCII-art) és az OSC-escape egyike sem ilyen, így biztosan a stderr-re kerülnek.
215
+ */
216
+ static isJsonRpcFrame(chunk) {
217
+ const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
218
+ const trimmed = text.trimStart();
219
+ return trimmed.startsWith('{') && trimmed.includes('"jsonrpc"');
220
+ }
67
221
  /** Egy log-argumentum string-reprezentációja (objektum → JSON, primitív → String). */
68
222
  static stringifyArg(arg) {
69
223
  if (typeof arg === 'string') {
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FAM_Error_Util = void 0;
4
- exports.createFamError = createFamError;
5
4
  const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
6
5
  const error_codes_const_1 = require("./error-codes.const");
7
6
  const fam_error_context_util_1 = require("./fam-error-context.util");
@@ -54,11 +53,3 @@ class FAM_Error_Util {
54
53
  }
55
54
  }
56
55
  exports.FAM_Error_Util = FAM_Error_Util;
57
- /**
58
- * Kényelmi factory-függvény (a `FAM_Error_Util.create` rövid alakja) — a réteg-MP-k ezt hívják a
59
- * hiba-emisszió 1. lépéséhez. Pl.: `throw createFamError({ errorCode: FAM_ERROR_CODES.dbConnect,
60
- * message: '...', context: { operation: 'connect' } })`.
61
- */
62
- function createFamError(params) {
63
- return FAM_Error_Util.create(params);
64
- }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_McpBridge_Util = void 0;
4
+ const fam_core_tools_const_1 = require("../_modules/mcp/_collections/fam-core-tools.const");
5
+ const fam_mcp_adapter_service_1 = require("../_modules/mcp/_services/fam-mcp-adapter.service");
6
+ const fam_client_service_1 = require("../_cli/_services/fam-client.service");
7
+ /**
8
+ * `FAM_McpBridge_Util` (FEAT-002, topology „egy szerver, sok bridge") — ha `fam start`-kor MÁR fut egy
9
+ * FAM-szerver (primary, REST a 39265-ön), az új process NEM bootol második szervert (port-ütközés +
10
+ * dupla LVS-pool elkerülése), hanem **MCP-stdio bridge**-ként a futó primary-hoz csatlakozik.
11
+ *
12
+ * A 3 advertised core-tool (`read`/`write`/`capabilities`) DEFINÍCIÓJA (név/leírás/schema) VÁLTOZATLAN
13
+ * (a `FAM_CORE_TOOLS` single-source), csak a handler proxy-zik: a primary REST-tükrét (`/api/read` |
14
+ * `/api/write` | `/api/capabilities`) hívja a `FAM_CliClient_Service`-en át — így a bridge ugyanazt a
15
+ * Mongo-kapcsolatot + hidratált vektor-pool-t használja, mint a primary (paritás, dsgn-003 §6.5).
16
+ *
17
+ * A REST-tükör success-body-ja a NYERS tool-eredmény (`res.send(data)`), így a `{ data: r.data }`
18
+ * pontosan az in-process handler `{ data }` alakját adja vissza (nincs dupla-wrap).
19
+ */
20
+ class FAM_McpBridge_Util {
21
+ /**
22
+ * A stdio MCP-bridge indítása: a 3 core-tool REST-proxy handlerrel regisztrálva + a stdio transport.
23
+ * A `client` a futó primary-t hívja (`FAM_SERVER_URL`/default `http://127.0.0.1:39265`).
24
+ */
25
+ static async start(set) {
26
+ const adapter = new fam_mcp_adapter_service_1.FAM_Mcp_Adapter_Service({ name: set.name, version: set.version });
27
+ const client = new fam_client_service_1.FAM_CliClient_Service();
28
+ for (const tool of fam_core_tools_const_1.FAM_CORE_TOOLS) {
29
+ const bridged = {
30
+ name: tool.name,
31
+ description: tool.description,
32
+ inputSchema: tool.inputSchema,
33
+ handler: async (args) => {
34
+ const result = await client.post(`/api/${tool.name}`, args ?? {});
35
+ if (!result.ok) {
36
+ // A primary hibáját MCP-hibaként felszínre hozzuk (NEM némítjuk; dsgn-008).
37
+ const code = result.error?.errorCode ?? 'FAM-MCP-BRIDGE-001';
38
+ const message = result.error?.message ?? 'A FAM bridge REST-hívása sikertelen.';
39
+ throw new Error(`${code}: ${message}`);
40
+ }
41
+ return { data: result.data };
42
+ },
43
+ };
44
+ adapter.registerTool(bridged);
45
+ }
46
+ await adapter.startStdio();
47
+ }
48
+ }
49
+ exports.FAM_McpBridge_Util = FAM_McpBridge_Util;