@futdevpro/fdp-agent-memory 1.1.116 → 1.1.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +1 -1
- package/build/src/_cli/_commands/remote.command.js +141 -0
- package/build/src/_cli/_services/fam-client.service.js +7 -3
- package/build/src/_cli/register-commands.js +2 -0
- package/build/src/_collections/config-catalog.const.js +38 -2
- package/build/src/_collections/fam-remote-config.util.js +94 -0
- package/build/src/_models/data-models/fam-scan-job.data-model.js +7 -0
- package/build/src/_modules/ingest/_services/fam-scan-job.control-service.js +45 -6
- package/build/src/_modules/ingest/_services/fam-scan-job.data-service.js +18 -7
- package/build/src/_modules/ingest/_services/fam-scan-scheduler.control-service.js +37 -6
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +23 -6
- package/build/src/_routes/server/config/config.control-service.js +14 -1
- package/build/src/app.server.js +14 -0
- package/package.json +1 -1
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futdevpro/fdp-agent-memory",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.122",
|
|
4
4
|
"description": "Local-first, vector-backed multi-table agent memory exposed as an MCP server (read/write/capabilities). Public, FDP-Templates-free, no auth.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_Remote_Util = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const fam_remote_config_util_1 = require("../../_collections/fam-remote-config.util");
|
|
6
|
+
const fam_output_util_1 = require("../_collections/fam-output.util");
|
|
7
|
+
const fam_cli_const_1 = require("../_collections/fam-cli.const");
|
|
8
|
+
const fam_client_service_1 = require("../_services/fam-client.service");
|
|
9
|
+
/**
|
|
10
|
+
* `fam remote show|set|clear` — a TÁVOLI ELÉRÉS beállítása (user-FR 2026-07-28: „kéne építsünk a FAM-hoz beállítást,
|
|
11
|
+
* hogy távoli elérésként is működhessen — pl. virtuális gépről a host-ra kapcsolva, hogy a host futtat, de a VM is
|
|
12
|
+
* eléri, más címen/útvonalon").
|
|
13
|
+
*
|
|
14
|
+
* A beállítás a gép-lokális `~/.fam/config.json`-ben él (NEM a DB-config — az a szerveren van, amihez épp csatlakozni
|
|
15
|
+
* akarunk; és NEM új env-var — a hookok/MCP/CLI külön process-ek, egy fájl mindnek szól).
|
|
16
|
+
*
|
|
17
|
+
* **Tipikus VM→host setup:**
|
|
18
|
+
* - HOST-on: `fam remote set --bind 192.168.56.1` (a host-only adapter IP-je) + `fam serve --restart`
|
|
19
|
+
* - VM-ben: `fam remote set --url http://192.168.56.1:39265` → a VM CLI/hook/MCP a hostot használja
|
|
20
|
+
* - Reverse proxy mögött: `fam remote set --url https://host.local/fam` (az útvonal-prefix támogatott)
|
|
21
|
+
*/
|
|
22
|
+
class FAM_Remote_Util {
|
|
23
|
+
/** A `remote` parancs-deszkriptor (show/set/clear alparanccsal). */
|
|
24
|
+
static command() {
|
|
25
|
+
const command = new commander_1.Command('remote');
|
|
26
|
+
command.description('Távoli elérés beállítása (~/.fam/config.json): kliens szerver-URL + szerver bind-host');
|
|
27
|
+
command.addCommand(FAM_Remote_Util.showCommand());
|
|
28
|
+
command.addCommand(FAM_Remote_Util.setCommand());
|
|
29
|
+
command.addCommand(FAM_Remote_Util.clearCommand());
|
|
30
|
+
return command;
|
|
31
|
+
}
|
|
32
|
+
/** `remote show` — az aktuális (feloldott) beállítás + a tényleges elérhetőség. */
|
|
33
|
+
static showCommand() {
|
|
34
|
+
const command = new commander_1.Command('show');
|
|
35
|
+
command
|
|
36
|
+
.description('A jelenlegi távoli-elérés beállítás + elérhetőség-próba')
|
|
37
|
+
.action(async () => {
|
|
38
|
+
fam_output_util_1.FAM_Output_Util.exit(await FAM_Remote_Util.runShow(command));
|
|
39
|
+
});
|
|
40
|
+
return command;
|
|
41
|
+
}
|
|
42
|
+
/** `remote set` — a szerver-URL és/vagy a bind-host beállítása. */
|
|
43
|
+
static setCommand() {
|
|
44
|
+
const command = new commander_1.Command('set');
|
|
45
|
+
command
|
|
46
|
+
.description('A kliens szerver-URL és/vagy a szerver bind-host beállítása')
|
|
47
|
+
.option('--url <url>', 'KLIENS: a FAM szerver base-URL-je (útvonal-prefix is lehet, pl. https://host/fam)')
|
|
48
|
+
.option('--bind <host>', 'SZERVER: bind-host (127.0.0.1 = csak lokális, 0.0.0.0 vagy "*" = minden interfész, '
|
|
49
|
+
+ 'vagy egy konkrét adapter-IP). Csak a szerver ÚJRAINDÍTÁSA után lép életbe.')
|
|
50
|
+
.action(async () => {
|
|
51
|
+
fam_output_util_1.FAM_Output_Util.exit(await FAM_Remote_Util.runSet(command));
|
|
52
|
+
});
|
|
53
|
+
return command;
|
|
54
|
+
}
|
|
55
|
+
/** `remote clear` — a beállítások törlése (vissza a lokális default-ra). */
|
|
56
|
+
static clearCommand() {
|
|
57
|
+
const command = new commander_1.Command('clear');
|
|
58
|
+
command
|
|
59
|
+
.description('A távoli-elérés beállítások törlése (vissza a lokális default-ra)')
|
|
60
|
+
.action(async () => {
|
|
61
|
+
fam_output_util_1.FAM_Output_Util.exit(await FAM_Remote_Util.runClear(command));
|
|
62
|
+
});
|
|
63
|
+
return command;
|
|
64
|
+
}
|
|
65
|
+
/** A `remote show` futtatása: a feloldott értékek + honnan jönnek + elérhető-e a szerver. */
|
|
66
|
+
static async runShow(command) {
|
|
67
|
+
const globals = command.optsWithGlobals();
|
|
68
|
+
const file = fam_remote_config_util_1.FAM_RemoteConfig_Util.load();
|
|
69
|
+
const serverUrl = fam_remote_config_util_1.FAM_RemoteConfig_Util.resolveServerUrl({
|
|
70
|
+
override: globals.serverUrl, env: process.env.FAM_SERVER_URL, file: file,
|
|
71
|
+
});
|
|
72
|
+
const bindHost = fam_remote_config_util_1.FAM_RemoteConfig_Util.resolveBindHost({ env: process.env.FAM_SERVER_HOST, file: file });
|
|
73
|
+
const client = new fam_client_service_1.FAM_CliClient_Service(globals.serverUrl);
|
|
74
|
+
return fam_output_util_1.FAM_Output_Util.success({
|
|
75
|
+
options: globals,
|
|
76
|
+
command: 'remote show',
|
|
77
|
+
data: {
|
|
78
|
+
configPath: fam_remote_config_util_1.FAM_RemoteConfig_Util.CONFIG_PATH,
|
|
79
|
+
serverUrl: serverUrl,
|
|
80
|
+
serverUrlFrom: FAM_Remote_Util.describeSource(globals.serverUrl, process.env.FAM_SERVER_URL, file.serverUrl),
|
|
81
|
+
bindHost: bindHost,
|
|
82
|
+
bindHostFrom: FAM_Remote_Util.describeSource(undefined, process.env.FAM_SERVER_HOST, file.bindHost),
|
|
83
|
+
bindIsLoopbackOnly: fam_remote_config_util_1.FAM_RemoteConfig_Util.isLoopbackHost(bindHost),
|
|
84
|
+
reachable: await client.isReachable(),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** A `remote set` futtatása. */
|
|
89
|
+
static async runSet(command) {
|
|
90
|
+
const globals = command.optsWithGlobals();
|
|
91
|
+
const local = command.opts();
|
|
92
|
+
if (!local.url && !local.bind) {
|
|
93
|
+
return fam_output_util_1.FAM_Output_Util.failure({
|
|
94
|
+
options: globals, command: 'remote set',
|
|
95
|
+
error: {
|
|
96
|
+
errorCode: 'FAM-CLI-REMOTE-001',
|
|
97
|
+
message: 'Adj meg legalább egyet: `--url` (kliens) vagy `--bind` (szerver).',
|
|
98
|
+
fixHint: 'Pl. VM-ből a hostra: `fam remote set --url http://192.168.56.1:39265`; '
|
|
99
|
+
+ 'a hoston: `fam remote set --bind 192.168.56.1` + `fam serve --restart`.',
|
|
100
|
+
},
|
|
101
|
+
exitCode: fam_cli_const_1.FAM_CliExitCode.usageError,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const saved = fam_remote_config_util_1.FAM_RemoteConfig_Util.save({ serverUrl: local.url, bindHost: local.bind });
|
|
105
|
+
const notes = [];
|
|
106
|
+
if (local.bind) {
|
|
107
|
+
notes.push('A bind-host CSAK a szerver újraindítása után lép életbe: `fam serve --restart`.');
|
|
108
|
+
const effective = fam_remote_config_util_1.FAM_RemoteConfig_Util.resolveBindHost({ file: saved });
|
|
109
|
+
if (!fam_remote_config_util_1.FAM_RemoteConfig_Util.isLoopbackHost(effective)) {
|
|
110
|
+
notes.push('FIGYELEM: a FAM-nak NINCS authentikációja (ADR-004) — a nem-loopback bind a TELJES '
|
|
111
|
+
+ 'korpuszt elérhetővé teszi az adott hálózaton. Csak megbízható (host-only / VPN) hálón használd.');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return fam_output_util_1.FAM_Output_Util.success({
|
|
115
|
+
options: globals, command: 'remote set',
|
|
116
|
+
data: { configPath: fam_remote_config_util_1.FAM_RemoteConfig_Util.CONFIG_PATH, config: saved, notes: notes },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** A `remote clear` futtatása. */
|
|
120
|
+
static async runClear(command) {
|
|
121
|
+
const globals = command.optsWithGlobals();
|
|
122
|
+
const saved = fam_remote_config_util_1.FAM_RemoteConfig_Util.save({ serverUrl: null, bindHost: null });
|
|
123
|
+
return fam_output_util_1.FAM_Output_Util.success({
|
|
124
|
+
options: globals, command: 'remote clear',
|
|
125
|
+
data: { configPath: fam_remote_config_util_1.FAM_RemoteConfig_Util.CONFIG_PATH, config: saved,
|
|
126
|
+
notes: ['Vissza a lokális default-ra (kliens: http://127.0.0.1:39265, bind: 127.0.0.1). '
|
|
127
|
+
+ 'A bind változás a szerver újraindításakor lép életbe.'] },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/** Melyik forrásból jött a feloldott érték (flag > env > fájl > default) — a `show` átláthatóságához (PURE). */
|
|
131
|
+
static describeSource(flag, env, file) {
|
|
132
|
+
if (flag?.trim()) {
|
|
133
|
+
return 'flag (--server-url)';
|
|
134
|
+
}
|
|
135
|
+
if (env?.trim()) {
|
|
136
|
+
return 'env';
|
|
137
|
+
}
|
|
138
|
+
return file?.trim() ? 'config-fájl (~/.fam/config.json)' : 'beépített default';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.FAM_Remote_Util = FAM_Remote_Util;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FAM_CliClient_Service = void 0;
|
|
4
|
-
const
|
|
4
|
+
const fam_remote_config_util_1 = require("../../_collections/fam-remote-config.util");
|
|
5
5
|
/**
|
|
6
6
|
* `FAM_CliClient_Service` — a CLI → szerver elérési réteg (dsgn-010 §9). A `read`/`write`/`scan`/
|
|
7
7
|
* `stats`/`errors`/`config`/`capabilities` parancsok ezen át hívják a futó szerver REST-felületét
|
|
@@ -25,8 +25,12 @@ class FAM_CliClient_Service {
|
|
|
25
25
|
* `FAM_SERVER_URL`, különben a `FAM_DEFAULT_SERVER_URL`.
|
|
26
26
|
*/
|
|
27
27
|
constructor(serverUrlOverride) {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// TÁVOLI ELÉRÉS (user-FR 2026-07-28): a `~/.fam/config.json` `serverUrl`-je is beleszól — így egy VM-ből a
|
|
29
|
+
// host-on futó FAM elérhető anélkül, hogy MINDEN hívóhoz (CLI/hook/MCP külön process!) env-et kellene fűzni.
|
|
30
|
+
// Precedencia: `--server-url` flag > `FAM_SERVER_URL` env > config-fájl > `http://127.0.0.1:39265`.
|
|
31
|
+
this.baseUrl = fam_remote_config_util_1.FAM_RemoteConfig_Util.resolveServerUrl({
|
|
32
|
+
override: serverUrlOverride, env: process.env.FAM_SERVER_URL, file: fam_remote_config_util_1.FAM_RemoteConfig_Util.load(),
|
|
33
|
+
});
|
|
30
34
|
}
|
|
31
35
|
/** A feloldott szerver base-URL (a fix-tippekhez / diaghoz). */
|
|
32
36
|
getBaseUrl() {
|
|
@@ -17,6 +17,7 @@ const seed_command_1 = require("./_commands/seed.command");
|
|
|
17
17
|
const import_command_1 = require("./_commands/import.command");
|
|
18
18
|
const export_command_1 = require("./_commands/export.command");
|
|
19
19
|
const capture_command_1 = require("./_commands/capture.command");
|
|
20
|
+
const remote_command_1 = require("./_commands/remote.command");
|
|
20
21
|
/**
|
|
21
22
|
* Regisztrálja az összes FDP Agent Memory-specifikus CLI command-ot (dsgn-010 §2 `buildCli()`).
|
|
22
23
|
*
|
|
@@ -52,6 +53,7 @@ function registerFAMCommands(program, version) {
|
|
|
52
53
|
export_command_1.FAM_Export_Util.command(),
|
|
53
54
|
capture_command_1.FAM_Capture_Util.command(),
|
|
54
55
|
config_command_1.FAM_Config_Util.command(),
|
|
56
|
+
remote_command_1.FAM_Remote_Util.command(),
|
|
55
57
|
stats_command_1.FAM_Stats_Util.command(),
|
|
56
58
|
find_duplicates_command_1.FAM_FindDuplicates_Util.command(),
|
|
57
59
|
errors_command_1.FAM_Errors_Util.command(),
|
|
@@ -55,6 +55,10 @@ exports.SCAN_IGNORE_PATTERNS_DEFAULT = [
|
|
|
55
55
|
// Cookies / Web Data = SZEMÉLYES adat), olvashatatlanok + junk. (Lelet 2026-06-22: fdp-assistant/__agent/
|
|
56
56
|
// .local-state = 403 MB / 2452 fájl → 2208 `FAM-SCAN-READ-001`. Az `__agent` MAGA MARAD — csak a state junk.)
|
|
57
57
|
'**/.local-state/**', '**/browser-profiles/**', '**/Cache_Data/**',
|
|
58
|
+
// CSOMAGKEZELŐ content-addressed STORE/cache (2026-07-26 lelet): a repo-lokális `.pnpm-store` a `node_modules`-tól
|
|
59
|
+
// KÜLÖNÁLLÓ mappa, ezért a `**/node_modules/**` NEM fedte → egyetlen NPM-csomagban 18 881 junk-fájl került a
|
|
60
|
+
// felderítésbe (fdp-e2e-helpers: 22 618 „forrás"-fájlból 18 881 a store). Sosem forrás — hash-elt tarball-tartalom.
|
|
61
|
+
'**/.pnpm-store/**', '**/.yarn/cache/**', '**/.yarn/unplugged/**', '**/.pnp.cjs', '**/.pnp.js',
|
|
58
62
|
// A `.fdpfamignore` SAJÁT MAGÁT kizárja: ignore-spec-ként OLVASSUK (FAM_FamIgnore_Util.loadFromDir,
|
|
59
63
|
// a walk/routing megkerülésével), de SOHA nem ingesteljük content-ként (a router amúgy is skip-elné).
|
|
60
64
|
'**/.fdpfamignore',
|
|
@@ -67,12 +71,36 @@ exports.CONFIG_CATALOG = {
|
|
|
67
71
|
// --- 4.1. Read / retrieval limitek (fogyasztó: dsgn-005) ---
|
|
68
72
|
'read.topK': {
|
|
69
73
|
type: 'number', default: 5, min: 1, max: 200, integer: true, levels: ALL_LEVELS,
|
|
70
|
-
|
|
74
|
+
// PER-TÁR default (user-FR 2026-07-28): „amikor rule-okat vagy más FONTOS dolgot keresünk, ott nagyobb max
|
|
75
|
+
// találattal kell dolgozni, hogy MINDEN fontos elem/szabály MINDIG belekerüljön". A `rules` a legkritikusabb
|
|
76
|
+
// (egy hard-rule kimaradása = rossz döntés), utána a kurált `coding_patterns` / `knowledge` / `memory`. A
|
|
77
|
+
// `documents`/`codebase` marad az 5-ös alapon (ott a nagy korpusz miatt a szűk top-N a helyes — a `depth:'deep'`
|
|
78
|
+
// (`read.deepTopK`) adja a mély feltárást).
|
|
79
|
+
defaultsByTable: {
|
|
80
|
+
[fam_table_type_enum_1.FAM_Table.rules]: 30,
|
|
81
|
+
[fam_table_type_enum_1.FAM_Table.codingPatterns]: 20,
|
|
82
|
+
[fam_table_type_enum_1.FAM_Table.knowledge]: 15,
|
|
83
|
+
[fam_table_type_enum_1.FAM_Table.memory]: 15,
|
|
84
|
+
},
|
|
85
|
+
description: 'Alap visszaadott találat-szám (CCAP DEFAULT_TOP_K=5). **Per-tár default** (user-FR 2026-07-28): '
|
|
86
|
+
+ '`rules` 30 · `coding_patterns` 20 · `knowledge`/`memory` 15 — hogy a FONTOS elemek (kanonikus szabályok, '
|
|
87
|
+
+ 'kurált minták/tanulságok) MIND beleférjenek, ne csak a top-5. A `documents`/`codebase` marad 5 (nagy '
|
|
88
|
+
+ 'korpusz → szűk top-N; a mély feltárás a `depth:"deep"` / `read.deepTopK`). Bármely EXPLICIT beállítás '
|
|
89
|
+
+ '(scope/table/global) felülírja a per-tár default-ot.',
|
|
71
90
|
},
|
|
72
91
|
'read.deepTopK': {
|
|
73
92
|
type: 'number', default: 25, min: 1, max: 200, integer: true, levels: ALL_LEVELS,
|
|
93
|
+
// A mély feltárás a FONTOS tárakon még bőségesebb (a normál per-tár topK FÖLÖTT kell maradnia — user-FR 2026-07-28).
|
|
94
|
+
defaultsByTable: {
|
|
95
|
+
[fam_table_type_enum_1.FAM_Table.rules]: 60,
|
|
96
|
+
[fam_table_type_enum_1.FAM_Table.codingPatterns]: 40,
|
|
97
|
+
[fam_table_type_enum_1.FAM_Table.knowledge]: 35,
|
|
98
|
+
[fam_table_type_enum_1.FAM_Table.memory]: 35,
|
|
99
|
+
},
|
|
74
100
|
description: 'A `depth:"deep"` (mély feltárás) read-mód találat-száma: ennyit ad vissza, ha a query NEM '
|
|
75
|
-
+ 'ad explicit topK-t. Sokkal több chunk/kontextus egy-egy mély feltáráshoz (a query topK felülírja).'
|
|
101
|
+
+ 'ad explicit topK-t. Sokkal több chunk/kontextus egy-egy mély feltáráshoz (a query topK felülírja). '
|
|
102
|
+
+ '**Per-tár default:** `rules` 60 · `coding_patterns` 40 · `knowledge`/`memory` 35 — a fontos tárak mély '
|
|
103
|
+
+ 'feltárása a normál per-tár topK FÖLÖTT maradjon.',
|
|
76
104
|
},
|
|
77
105
|
'read.maxResults': {
|
|
78
106
|
type: 'number', default: 50, min: 1, max: 500, integer: true, levels: ALL_LEVELS,
|
|
@@ -80,6 +108,14 @@ exports.CONFIG_CATALOG = {
|
|
|
80
108
|
},
|
|
81
109
|
'read.maxTokens': {
|
|
82
110
|
type: 'number', default: 4000, min: 256, max: 32000, integer: true, levels: ALL_LEVELS,
|
|
111
|
+
// A megemelt per-tár topK ÖNMAGÁBAN nem elég: a token-budget levágná a többletet (a 30 szabály ~15k char ≈
|
|
112
|
+
// 3,7k token → a 4000-es budget pont elvágná). A fontos tárakon ezért a budget is arányosan nő (user-FR 2026-07-28).
|
|
113
|
+
defaultsByTable: {
|
|
114
|
+
[fam_table_type_enum_1.FAM_Table.rules]: 12000,
|
|
115
|
+
[fam_table_type_enum_1.FAM_Table.codingPatterns]: 8000,
|
|
116
|
+
[fam_table_type_enum_1.FAM_Table.knowledge]: 8000,
|
|
117
|
+
[fam_table_type_enum_1.FAM_Table.memory]: 8000,
|
|
118
|
+
},
|
|
83
119
|
description: 'Az agent context védelme + a read-válasz INLINE-tartása (max token; ~4 char/token). A read-tool '
|
|
84
120
|
+ 'ÉRVÉNYESÍTI (FAM-REV: korábban dead-config volt → korlátlan hit-tartalom → a host fájlba mentette a választ). '
|
|
85
121
|
+ 'A budget felett a hit-tartalom levágódik + a túlcsorduló hit-ek elhagyva (`truncated:true`).',
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_RemoteConfig_Util = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const os_1 = require("os");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
/**
|
|
8
|
+
* `FAM_RemoteConfig_Util` — a gép-lokális `~/.fam/config.json` olvasása/írása + a TÁVOLI ELÉRÉS feloldása
|
|
9
|
+
* (user-FR 2026-07-28: „kéne építsünk a FAM-hoz beállítást, hogy távoli elérésként is működhessen — pl. virtuális
|
|
10
|
+
* gépről a host-ra kapcsolva, hogy a host futtat, de a VM is eléri, más címen/útvonalon").
|
|
11
|
+
*
|
|
12
|
+
* **Miért fájl és nem (új) env-var?** Az env csak abban a process-fában él, ahol beállították — a CC-hookok, az MCP-
|
|
13
|
+
* adapter és a CLI KÜLÖN process-ek, így env-vel mindegyiket külön kéne felkonfigurálni (és a workspace-guardrail is
|
|
14
|
+
* tiltja az új env-kulcsok szaporítását). A fájl EGY helyen, perzisztensen, minden hívónak érvényes. A MEGLÉVŐ
|
|
15
|
+
* `FAM_SERVER_URL` env továbbra is működik és ERŐSEBB a fájlnál (ad-hoc felülírás).
|
|
16
|
+
*
|
|
17
|
+
* **Precedencia (kliens):** `--server-url` flag > `FAM_SERVER_URL` env > `~/.fam/config.json` > `http://127.0.0.1:39265`.
|
|
18
|
+
* **Precedencia (szerver bind):** `FAM_SERVER_HOST` env > `~/.fam/config.json` > `127.0.0.1`.
|
|
19
|
+
*/
|
|
20
|
+
class FAM_RemoteConfig_Util {
|
|
21
|
+
/** A gép-lokális config-fájl útja (`~/.fam/config.json`). */
|
|
22
|
+
static CONFIG_PATH = (0, path_1.join)((0, os_1.homedir)(), '.fam', 'config.json');
|
|
23
|
+
/** A kliens-default (változatlan visszafelé-kompatibilis érték). */
|
|
24
|
+
static DEFAULT_SERVER_URL = 'http://127.0.0.1:39265';
|
|
25
|
+
/**
|
|
26
|
+
* A szerver BIND-default: **loopback**. Biztonsági alapállás — a FAM-nak nincs authja (ADR-004), ezért
|
|
27
|
+
* alapból NEM hallgatózik a hálózaton; a távoli elérés EXPLICIT opt-in (`bindHost`).
|
|
28
|
+
*/
|
|
29
|
+
static DEFAULT_BIND_HOST = '127.0.0.1';
|
|
30
|
+
/**
|
|
31
|
+
* A config-fájl beolvasása. Best-effort: hiányzó/olvashatatlan/érvénytelen JSON → üres objektum (a FAM
|
|
32
|
+
* default-okkal indul; egy elrontott fájl SOHA nem akadályozhatja meg a szerver indulását).
|
|
33
|
+
*/
|
|
34
|
+
static load() {
|
|
35
|
+
try {
|
|
36
|
+
if (!(0, fs_1.existsSync)(FAM_RemoteConfig_Util.CONFIG_PATH)) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(FAM_RemoteConfig_Util.CONFIG_PATH, 'utf8'));
|
|
40
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
const raw = parsed;
|
|
44
|
+
return {
|
|
45
|
+
serverUrl: typeof raw.serverUrl === 'string' && raw.serverUrl.trim() ? raw.serverUrl.trim() : undefined,
|
|
46
|
+
bindHost: typeof raw.bindHost === 'string' && raw.bindHost.trim() ? raw.bindHost.trim() : undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** A config-fájl MÓDOSÍTÁSA (merge a meglévővel; a mappát létrehozza). A `null` érték TÖRLI a kulcsot. */
|
|
54
|
+
static save(patch) {
|
|
55
|
+
const current = FAM_RemoteConfig_Util.load();
|
|
56
|
+
const next = { ...current };
|
|
57
|
+
if (patch.serverUrl !== undefined) {
|
|
58
|
+
next.serverUrl = patch.serverUrl === null ? undefined : patch.serverUrl.trim();
|
|
59
|
+
}
|
|
60
|
+
if (patch.bindHost !== undefined) {
|
|
61
|
+
next.bindHost = patch.bindHost === null ? undefined : patch.bindHost.trim();
|
|
62
|
+
}
|
|
63
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(FAM_RemoteConfig_Util.CONFIG_PATH), { recursive: true });
|
|
64
|
+
(0, fs_1.writeFileSync)(FAM_RemoteConfig_Util.CONFIG_PATH, `${JSON.stringify(next, null, 4)}\n`, 'utf8');
|
|
65
|
+
return next;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A KLIENS szerver-URL feloldása (PURE, tesztelhető — a bemenetek explicitek).
|
|
69
|
+
* Precedencia: flag > env > fájl > default. A záró `/`-eket levágja (a hívó `${base}/api/...`-t fűz).
|
|
70
|
+
*/
|
|
71
|
+
static resolveServerUrl(set) {
|
|
72
|
+
const raw = set.override?.trim()
|
|
73
|
+
|| set.env?.trim()
|
|
74
|
+
|| set.file?.serverUrl?.trim()
|
|
75
|
+
|| FAM_RemoteConfig_Util.DEFAULT_SERVER_URL;
|
|
76
|
+
return raw.replace(/\/+$/, '');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A SZERVER bind-host feloldása (PURE). Precedencia: env > fájl > loopback-default.
|
|
80
|
+
* A `'*'` kényelmi alias a `0.0.0.0`-ra (minden interfész).
|
|
81
|
+
*/
|
|
82
|
+
static resolveBindHost(set) {
|
|
83
|
+
const raw = set.env?.trim() || set.file?.bindHost?.trim() || FAM_RemoteConfig_Util.DEFAULT_BIND_HOST;
|
|
84
|
+
return raw === '*' ? '0.0.0.0' : raw;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Loopback-e a bind (PURE)? A nem-loopback bind FIGYELMEZTETÉST érdemel: a FAM auth NÉLKÜLI (ADR-004), így a
|
|
88
|
+
* hálózaton bárki írhatja/olvashatja a teljes korpuszt. Csak megbízható (host-only/VPN) hálón indokolt.
|
|
89
|
+
*/
|
|
90
|
+
static isLoopbackHost(host) {
|
|
91
|
+
return host === '127.0.0.1' || host === 'localhost' || host === '::1';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.FAM_RemoteConfig_Util = FAM_RemoteConfig_Util;
|
|
@@ -39,6 +39,12 @@ class FAM_ScanJob_DataModel extends fsm_dynamo_1.DyFM_Metadata {
|
|
|
39
39
|
resumeCount;
|
|
40
40
|
/** Terminál ok-szöveg (pl. „resume-cap túllépve") — `failed`/`cancelled` job-on, a diagnózishoz. */
|
|
41
41
|
failureReason;
|
|
42
|
+
/**
|
|
43
|
+
* KI indította a job-ot (`FAM_AgentOrigin`: agent/sessionId/workspace/label) — **DURABLE** (2026-07-26 lelet: eddig
|
|
44
|
+
* csak in-memory volt, ezért restart + auto-resume után `origin: undefined` lett → a logból/`listJobs`-ból NEM
|
|
45
|
+
* derült ki, hogy a HETI AUTO re-scan (`agent:'fam-scheduler'`) futott-e, vagy egy manuális/CLI scan).
|
|
46
|
+
*/
|
|
47
|
+
origin;
|
|
42
48
|
constructor(set) {
|
|
43
49
|
super(set);
|
|
44
50
|
if (set) {
|
|
@@ -69,5 +75,6 @@ exports.famScanJob_dataParams = new fsm_dynamo_1.DyFM_DataModel_Params({
|
|
|
69
75
|
sourceLocation: { type: 'string' },
|
|
70
76
|
resumeCount: { type: 'number' },
|
|
71
77
|
failureReason: { type: 'string' },
|
|
78
|
+
origin: { type: 'object' },
|
|
72
79
|
},
|
|
73
80
|
});
|
|
@@ -98,8 +98,13 @@ class FAM_ScanJob_ControlService {
|
|
|
98
98
|
void this.persistUpdate(job);
|
|
99
99
|
fsm_dynamo_1.DyFM_Log.warn(`[scanJob ${job.jobId}] ${job.failureReason}`);
|
|
100
100
|
});
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
// INDÍTÁS-LOG (2026-07-26 bővítés — „derüljön ki a logból, KI indította"): az origin (agent/label/session) + a
|
|
102
|
+
// TELJES cél-lista a log-bufferbe (`.log`, nem `.info` — csak az előbbi pufferelődik a `/api/logs/get`-be).
|
|
103
|
+
fsm_dynamo_1.DyFM_Log.log(`[scanJob ${job.jobId}] INDÍTVA — indító: ${FAM_ScanJob_ControlService.describeOrigin(job.origin)} `
|
|
104
|
+
+ `| ${specs.length} cél | cooldown ${cooldownMs}ms (ha ≥${cooldownMinChunks} embed chunk)`);
|
|
105
|
+
specs.forEach((spec, index) => {
|
|
106
|
+
fsm_dynamo_1.DyFM_Log.log(`[scanJob ${job.jobId}] cél ${index + 1}/${specs.length}: ${spec.project} → ${spec.path}`);
|
|
107
|
+
});
|
|
103
108
|
// Fast-path (user-FR 2026-06-22): a `start` legfeljebb `waitMs`-ig (default 15s) megvárja a befejezést — ha
|
|
104
109
|
// a job ennyin belül kész, a TELJES eredményt adjuk vissza AZONNAL (nincs külön poll-hívás); különben a futó
|
|
105
110
|
// job-ot (`jobId` + `running` + ETA) → a hívó poll-oz. `0` = ne várj.
|
|
@@ -120,6 +125,26 @@ class FAM_ScanJob_ControlService {
|
|
|
120
125
|
await fam_cooldown_util_1.FAM_Cooldown_Util.sleep(100);
|
|
121
126
|
}
|
|
122
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Az INDÍTÓ ember-olvasható leírása a logokhoz (2026-07-26): `agent[/label][ · session]`. Aláírás nélküli (régi/
|
|
130
|
+
* kézi) job → `ismeretlen (aláíratlan)` — hogy a log EXPLICIT jelezze: nem tudjuk, ki indította (nem „hallgat").
|
|
131
|
+
*/
|
|
132
|
+
static describeOrigin(origin) {
|
|
133
|
+
if (!origin || (!origin.agent && !origin.label && !origin.sessionId)) {
|
|
134
|
+
return 'ismeretlen (aláíratlan)';
|
|
135
|
+
}
|
|
136
|
+
const label = origin.label ? `/${origin.label}` : '';
|
|
137
|
+
const session = origin.sessionId ? ` · session ${String(origin.sessionId).slice(0, 8)}` : '';
|
|
138
|
+
return `${origin.agent ?? 'ismeretlen-agent'}${label}${session}`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Van-e AKTÍV (running/pending) scan-job? A heti auto-re-scan SINGLE-FLIGHT kapuja (2026-07-26 lelet: restart +
|
|
142
|
+
* auto-resume mellett HÁROM egyidejű 13-célos job futott) — soha ne indítsunk párhuzamos TELJES scant (a
|
|
143
|
+
* hidratált pool + embed erőforrás-kontenció miatt ez a gépet is megfoghatja).
|
|
144
|
+
*/
|
|
145
|
+
hasActiveJob() {
|
|
146
|
+
return Array.from(this.jobs.values()).some((job) => job.status === 'running' || job.status === 'pending');
|
|
147
|
+
}
|
|
123
148
|
/** Egy job állapota (poll). `undefined`, ha ismeretlen (a hívó 404-et ad). Az ETA + stall frissítve. */
|
|
124
149
|
getStatus(jobId) {
|
|
125
150
|
const job = this.jobs.get(jobId);
|
|
@@ -214,7 +239,8 @@ class FAM_ScanJob_ControlService {
|
|
|
214
239
|
job.finishedAt = Date.now();
|
|
215
240
|
job.failureReason = `felhasználói megszakítás a(z) ${index + 1}/${specs.length} projektnél`;
|
|
216
241
|
await this.persistUpdate(job);
|
|
217
|
-
fsm_dynamo_1.DyFM_Log.
|
|
242
|
+
fsm_dynamo_1.DyFM_Log.log(`[scanJob ${job.jobId}] MEGSZAKÍTVA — ${job.failureReason} | indító: `
|
|
243
|
+
+ `${FAM_ScanJob_ControlService.describeOrigin(job.origin)}`);
|
|
218
244
|
return;
|
|
219
245
|
}
|
|
220
246
|
// RESUME (FEAT-008 Phase 4): a már `ok` projekteket ÁTUGORJUK (a megszakítási ponttól folytatunk —
|
|
@@ -227,7 +253,11 @@ class FAM_ScanJob_ControlService {
|
|
|
227
253
|
job.lastProgressAt = Date.now(); // egy projekt feldolgozásának KEZDETE is haladás (a stall-óra resetelődik)
|
|
228
254
|
// Per-projekt PROGRESS a process-logba (user-FR 2026-06-25 — „hanyadik projektből hányadikon járunk"): a
|
|
229
255
|
// detached FAM-ablakot figyelve egyből látszik a pozíció (`[12/48] <projekt>`), nem kell a REST-et poll-ozni.
|
|
230
|
-
|
|
256
|
+
// `.log` (NEM `.info`) — a DyNTS log-buffer (`/api/logs/get`) CSAK a `.log`/`.warn`-t puffereli, ezért a
|
|
257
|
+
// `.info` kontextus-sorok korábban LÁTHATATLANOK voltak a log-lekérdezésben (2026-07-26 lelet: a user a
|
|
258
|
+
// `[famIngest] … feldolgozás` sort látta, de azt NEM, hogy melyik job/indító/cél-index tartozik hozzá).
|
|
259
|
+
fsm_dynamo_1.DyFM_Log.log(`[scanJob ${job.jobId}] [cél ${index + 1}/${specs.length}] ${specs[index].project} `
|
|
260
|
+
+ `(${specs[index].path}) — feldolgozás indul | indító: ${FAM_ScanJob_ControlService.describeOrigin(job.origin)}`);
|
|
231
261
|
const result = await runner(specs[index]);
|
|
232
262
|
job.projects[index] = result;
|
|
233
263
|
job.lastProgressAt = Date.now(); // per-target BEFEJEZÉS → friss haladás-jel a stall-detekciónak
|
|
@@ -252,7 +282,9 @@ class FAM_ScanJob_ControlService {
|
|
|
252
282
|
job.status = 'completed';
|
|
253
283
|
job.finishedAt = Date.now();
|
|
254
284
|
await this.persistUpdate(job);
|
|
255
|
-
fsm_dynamo_1.DyFM_Log.
|
|
285
|
+
fsm_dynamo_1.DyFM_Log.log(`[scanJob ${job.jobId}] KÉSZ: ${job.okCount}/${specs.length} ok | indító: `
|
|
286
|
+
+ `${FAM_ScanJob_ControlService.describeOrigin(job.origin)} | teljes futásidő `
|
|
287
|
+
+ `${Math.round(((job.finishedAt ?? Date.now()) - job.startedAt) / 1000)}s`);
|
|
256
288
|
}
|
|
257
289
|
/** A job durable rekordjának LÉTREHOZÁSA (best-effort — a perzisztencia hibája nem buktatja a scant). */
|
|
258
290
|
async persistCreate(job, specs, sourceLocation) {
|
|
@@ -262,6 +294,9 @@ class FAM_ScanJob_ControlService {
|
|
|
262
294
|
okCount: job.okCount, cooldownMs: job.cooldownMs, cooldownMinChunks: job.cooldownMinChunks,
|
|
263
295
|
startedAt: job.startedAt, projects: job.projects, specs: specs, sourceLocation: sourceLocation,
|
|
264
296
|
resumeCount: job.resumeCount ?? 0,
|
|
297
|
+
// KI indította — DURABLE-be is (2026-07-26 lelet): eddig csak in-memory volt, ezért a `listJobs`/log a
|
|
298
|
+
// restart+auto-resume után `origin: undefined`-ot mutatott → NEM derült ki, hogy a heti AUTO-scan futott-e.
|
|
299
|
+
origin: job.origin,
|
|
265
300
|
}));
|
|
266
301
|
}
|
|
267
302
|
catch (error) {
|
|
@@ -316,6 +351,9 @@ class FAM_ScanJob_ControlService {
|
|
|
316
351
|
resumeCount: priorResumes + 1,
|
|
317
352
|
cancelRequested: false,
|
|
318
353
|
projects: doc.projects ?? doc.specs.map((spec) => ({ project: spec.project, path: spec.path, status: 'pending' })),
|
|
354
|
+
// Az INDÍTÓ visszaállítása a durable rekordból — így a resume-olt job is beazonosítható (heti
|
|
355
|
+
// AUTO-scan vs manuális); enélkül a restart „elmosta" az origint (2026-07-26 lelet).
|
|
356
|
+
origin: doc.origin,
|
|
319
357
|
};
|
|
320
358
|
this.jobs.set(job.jobId, job);
|
|
321
359
|
this.stallThresholdMsByJob.set(job.jobId, stallThresholdMs);
|
|
@@ -323,7 +361,8 @@ class FAM_ScanJob_ControlService {
|
|
|
323
361
|
// számról indul → a cap konvergál; különben egy crash-loop sosem érné el a capet).
|
|
324
362
|
await dataService.updateJobByJobId(job.jobId, { resumeCount: job.resumeCount });
|
|
325
363
|
const doneCount = job.projects.filter((project) => project.status === 'ok').length;
|
|
326
|
-
fsm_dynamo_1.DyFM_Log.
|
|
364
|
+
fsm_dynamo_1.DyFM_Log.log(`[scanJob ${job.jobId}] RESUME (${job.resumeCount}/${maxResumeCount}) — eredeti indító: `
|
|
365
|
+
+ `${FAM_ScanJob_ControlService.describeOrigin(job.origin)} | ${doneCount}/${job.total} kész — folytatás a megszakítási ponttól`);
|
|
327
366
|
void this.process(job, doc.specs).catch((error) => {
|
|
328
367
|
job.status = 'failed';
|
|
329
368
|
job.finishedAt = Date.now();
|
|
@@ -31,20 +31,31 @@ class FAM_ScanJob_DataService extends nts_dynamo_1.DyNTS_DataService {
|
|
|
31
31
|
return this.findDataList({ status: { $in: ['running', 'pending'] } }, true);
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* A
|
|
35
|
-
* szerint csökkenő rendben
|
|
36
|
-
*
|
|
34
|
+
* A KORÁBBAN SZKENNELT célok UNIÓJA a durable scan-job előzményből (weekly re-scan auto-derive, 2026-07-26). A
|
|
35
|
+
* `startedAt` szerint csökkenő rendben végigmegy a legutóbbi job-okon, és `path` szerint deduplikálva gyűjti a
|
|
36
|
+
* cél-specjeket — a FRISSEBB job spec-je nyer (az aktuálisabb scope/table beállítással).
|
|
37
|
+
*
|
|
38
|
+
* **Miért unió és nem „a legutóbbi job listája"?** (2026-07-26 lelet) A legutóbbi job gyakran RÉSZLEGES (pl. csak
|
|
39
|
+
* 13 NPM-csomag), így a „heti TELJES re-scan" valójában nem lett volna teljes — az azelőtt szkennelt projektek
|
|
40
|
+
* kimaradtak volna, és a törlés-szinkron sem futott volna rájuk. Az unió a „mindent, amit valaha szkenneltünk"
|
|
41
|
+
* közelítése. (A garantáltan teljes lefedettség továbbra is az explicit `scan.weeklyRescanRoot`.)
|
|
42
|
+
*
|
|
37
43
|
* `undefined`, ha még sosem volt cél-speces scan.
|
|
38
44
|
*/
|
|
39
45
|
async findLatestScannedTargets() {
|
|
40
|
-
const found = await this.searchData({ sortBy: [{ key: 'startedAt', order: -1 }], pageSize:
|
|
46
|
+
const found = await this.searchData({ sortBy: [{ key: 'startedAt', order: -1 }], pageSize: 100 });
|
|
47
|
+
const byPath = new Map();
|
|
41
48
|
for (const job of found.results ?? []) {
|
|
42
49
|
const specs = job.specs;
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
for (const spec of specs ?? []) {
|
|
51
|
+
const key = String(spec?.path ?? '').trim().toLowerCase();
|
|
52
|
+
// A frissebb job ELŐBB jön (startedAt desc) → az első találat a legfrissebb; a régebbit nem írjuk felül.
|
|
53
|
+
if (key && !byPath.has(key)) {
|
|
54
|
+
byPath.set(key, spec);
|
|
55
|
+
}
|
|
45
56
|
}
|
|
46
57
|
}
|
|
47
|
-
return undefined;
|
|
58
|
+
return byPath.size ? Array.from(byPath.values()) : undefined;
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
61
|
exports.FAM_ScanJob_DataService = FAM_ScanJob_DataService;
|
|
@@ -31,6 +31,8 @@ class FAM_ScanScheduler_ControlService {
|
|
|
31
31
|
ticking = false;
|
|
32
32
|
/** A slot-ellenőrzés fix pollozási időköze (ms) — 10 perc. NEM a heti slot; csak „megnéztük-e már, hogy due-e". */
|
|
33
33
|
static CHECK_INTERVAL_MS = 600000;
|
|
34
|
+
/** Nap-nevek a logokhoz (a `getDay()` 0=vasárnap indexelése szerint) — hogy a log ember-olvasható legyen. */
|
|
35
|
+
static DAY_NAMES = ['vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat'];
|
|
34
36
|
static getInstance() {
|
|
35
37
|
if (!FAM_ScanScheduler_ControlService._instance) {
|
|
36
38
|
FAM_ScanScheduler_ControlService._instance = new FAM_ScanScheduler_ControlService();
|
|
@@ -79,23 +81,43 @@ class FAM_ScanScheduler_ControlService {
|
|
|
79
81
|
const config = config_control_service_1.FAM_Config_ControlService.getInstance();
|
|
80
82
|
const enabled = (await config.resolve('scan.weeklyRescanEnabled')).value;
|
|
81
83
|
if (enabled === false) {
|
|
84
|
+
fsm_dynamo_1.DyFM_Log.log('[FAM scanScheduler] heti re-scan KIKAPCSOLVA (`scan.weeklyRescanEnabled=false`) — kihagyva.');
|
|
82
85
|
return;
|
|
83
86
|
}
|
|
84
87
|
// HIDRATÁLÁS-KAPU: a scheduler-t a boot ELEJÉN telepítjük (nem a lassú boot-karbantartás mögött), ezért a
|
|
85
88
|
// tick a hidratálás ALATT is tüzelhet — de ILYENKOR NE indítsunk TELJES re-scant (erőforrás-kontenció a
|
|
86
89
|
// 100k+ vektor hidratálásával). Nem rögzítünk lastRun-t → a következő tick (a hidratálás után) BEPÓTOL.
|
|
87
|
-
|
|
90
|
+
const hydration = embedding_1.FAM_VectorSearch_ControlService.getInstance().hydrationSummary();
|
|
91
|
+
if (!hydration.complete) {
|
|
92
|
+
fsm_dynamo_1.DyFM_Log.log(`[FAM scanScheduler] hidratálás MÉG FOLYAMATBAN (${hydration.hydratedTables}/${hydration.totalTables} tár, `
|
|
93
|
+
+ `vár: ${hydration.pendingTables.join(', ')}) — a heti re-scan most kihagyva, a következő tick bepótol.`);
|
|
88
94
|
return;
|
|
89
95
|
}
|
|
90
96
|
const day = await this.resolveNum(config, 'scan.weeklyRescanDayOfWeek', 1);
|
|
91
97
|
const hour = await this.resolveNum(config, 'scan.weeklyRescanHour', 7);
|
|
92
98
|
const lastRun = await this.resolveNum(config, 'scan.weeklyRescanLastRunAt', 0);
|
|
93
99
|
const slot = FAM_ScanScheduler_ControlService.mostRecentSlotAtOrBefore(nowMs, day, hour);
|
|
100
|
+
const iso = (ms) => (ms > 0 ? new Date(ms).toISOString() : 'SOHA');
|
|
94
101
|
if (lastRun >= slot) {
|
|
95
|
-
|
|
102
|
+
// NEM esedékes — a döntést + a MIÉRT-et is logoljuk (a user kérése: derüljön ki a logból; a következő
|
|
103
|
+
// slot időpontjával, hogy látható legyen, mikor futna legközelebb).
|
|
104
|
+
fsm_dynamo_1.DyFM_Log.log(`[FAM scanScheduler] heti re-scan NEM esedékes — utolsó futás ${iso(lastRun)} ≥ aktuális heti `
|
|
105
|
+
+ `slot ${iso(slot)} (slot: ${FAM_ScanScheduler_ControlService.DAY_NAMES[day] ?? day} ${hour}:00). `
|
|
106
|
+
+ `Következő slot: ${iso(slot + 7 * 24 * 3600000)}.`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// SINGLE-FLIGHT (2026-07-26 lelet — „max heti 1x, ÚJRAINDULÁSTÓL FÜGGETLENÜL"): ha MÁR fut egy scan-job
|
|
110
|
+
// (pl. a boot auto-resume folytatta a megszakadt heti scant), NE indítsunk MÉG EGYET. Nem rögzítünk
|
|
111
|
+
// lastRun-t → ha a futó job nem a heti volt, a következő tick újra megvizsgálja.
|
|
112
|
+
if (fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().hasActiveJob()) {
|
|
113
|
+
fsm_dynamo_1.DyFM_Log.log('[FAM scanScheduler] heti re-scan esedékes lenne, DE MÁR FUT egy scan-job '
|
|
114
|
+
+ '(single-flight kapu) — most kihagyva, a következő tick újrapróbálja.');
|
|
115
|
+
return;
|
|
96
116
|
}
|
|
97
|
-
// DUE: ELŐBB rögzítjük a lastRun-t (így egy crash mid-scan sem indít újra minden
|
|
98
|
-
//
|
|
117
|
+
// DUE: ELŐBB rögzítjük a lastRun-t (így egy crash/restart mid-scan sem indít újra minden bootkor — ez a
|
|
118
|
+
// „max heti 1x, újraindulástól függetlenül" garanciájának a MAGJA), MAJD indítjuk a scant.
|
|
119
|
+
fsm_dynamo_1.DyFM_Log.log(`[FAM scanScheduler] heti re-scan ESEDÉKES — utolsó futás ${iso(lastRun)} < heti slot ${iso(slot)} `
|
|
120
|
+
+ `(slot: ${FAM_ScanScheduler_ControlService.DAY_NAMES[day] ?? day} ${hour}:00). Indítás…`);
|
|
99
121
|
await this.markRan(config, nowMs);
|
|
100
122
|
await this.triggerRescan();
|
|
101
123
|
}
|
|
@@ -113,15 +135,24 @@ class FAM_ScanScheduler_ControlService {
|
|
|
113
135
|
const rootValue = (await config.resolve('scan.weeklyRescanRoot')).value;
|
|
114
136
|
const root = typeof rootValue === 'string' ? rootValue.trim() : '';
|
|
115
137
|
if (root) {
|
|
138
|
+
fsm_dynamo_1.DyFM_Log.log(`[FAM scanScheduler] cél-forrás: EXPLICIT \`scan.weeklyRescanRoot\` = ${root} `
|
|
139
|
+
+ '(a root alatt felderített ÖSSZES projekt — teljes lefedettség).');
|
|
116
140
|
await fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().start({ root: root, sourceLocation: root, origin: origin, waitMs: 0 });
|
|
117
141
|
fsm_dynamo_1.DyFM_Log.log(`[FAM scanScheduler] heti TELJES re-scan + reconcile INDÍTVA (root: ${root}).`);
|
|
118
142
|
return;
|
|
119
143
|
}
|
|
120
|
-
// Zero-config: a
|
|
144
|
+
// Zero-config: a KORÁBBAN SZKENNELT célok UNIÓJA (nem csak a legutóbbi job listája — 2026-07-26 lelet: a
|
|
145
|
+
// „legutóbbi job" egy RÉSZLEGES lista volt (13 NPM-csomag), így a „TELJES re-scan" nem volt teljes).
|
|
121
146
|
const targets = await this.loadLatestScannedTargets();
|
|
122
147
|
if (targets && targets.length) {
|
|
148
|
+
fsm_dynamo_1.DyFM_Log.log(`[FAM scanScheduler] cél-forrás: ZERO-CONFIG — a korábban szkennelt célok uniója a durable `
|
|
149
|
+
+ `scan-job előzményből (${targets.length} cél). FIGYELEM: ez CSAK azt fedi, amit már szkenneltünk; a `
|
|
150
|
+
+ 'garantáltan TELJES lefedettséghez állítsd be a `scan.weeklyRescanRoot`-ot a workspace-gyökérre.');
|
|
151
|
+
targets.forEach((spec, index) => {
|
|
152
|
+
fsm_dynamo_1.DyFM_Log.log(`[FAM scanScheduler] cél ${index + 1}/${targets.length}: ${spec.project} → ${spec.path}`);
|
|
153
|
+
});
|
|
123
154
|
await fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().start({ projects: targets, origin: origin, waitMs: 0 });
|
|
124
|
-
fsm_dynamo_1.DyFM_Log.log(`[FAM scanScheduler] heti TELJES re-scan + reconcile INDÍTVA (${targets.length} cél
|
|
155
|
+
fsm_dynamo_1.DyFM_Log.log(`[FAM scanScheduler] heti TELJES re-scan + reconcile INDÍTVA (${targets.length} cél, zero-config unió).`);
|
|
125
156
|
return;
|
|
126
157
|
}
|
|
127
158
|
fsm_dynamo_1.DyFM_Log.warn('[FAM scanScheduler] heti re-scan esedékes, DE nincs cél: állítsd be a `scan.weeklyRescanRoot`-ot '
|
|
@@ -45,16 +45,33 @@ class FAM_ReadTool_Service {
|
|
|
45
45
|
// Token-budget ÉRVÉNYESÍTÉS (FAM-REV 2026-06-22): a read-válasz INLINE marad (különben a nagy találat-tartalom
|
|
46
46
|
// miatt a MCP-host FÁJLBA menti → extra olvasás-lépés). A `read.maxTokens` (default 4000) felett a hit-tartalom
|
|
47
47
|
// levágódik + a túlcsorduló hit-ek elhagyva, `truncated:true`-val. A FAM mindig DIREKT adja vissza a találatokat.
|
|
48
|
-
FAM_ReadTool_Service.applyTokenBudget(response, await FAM_ReadTool_Service.resolveMaxTokens());
|
|
48
|
+
FAM_ReadTool_Service.applyTokenBudget(response, await FAM_ReadTool_Service.resolveMaxTokens(input));
|
|
49
49
|
this.logReadActivity(input, response);
|
|
50
50
|
return response;
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* A `read.maxTokens` config feloldása (default/fallback 4000, min 256) — a QUERY-K TÁRAIRA feloldva (user-FR
|
|
54
|
+
* 2026-07-28). A fontos tárak (`rules`/`coding_patterns`/`knowledge`/`memory`) per-tár budget-default-ot kapnak,
|
|
55
|
+
* hogy a megemelt per-tár `topK` ne essen áldozatul a szűk token-budgetnek. A budget KÖZÖS az egész válaszra, ezért
|
|
56
|
+
* multi-tár read-nél a LEGNAGYOBB érintett tár-budget nyer (a fontos tár igénye nem veszhet el egy „olcsóbb" tár
|
|
57
|
+
* mellett). Tár nélküli (all-table) read → a globális feloldás.
|
|
58
|
+
*/
|
|
59
|
+
static async resolveMaxTokens(input) {
|
|
54
60
|
try {
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
61
|
+
const config = config_control_service_1.FAM_Config_ControlService.getInstance();
|
|
62
|
+
const tables = Array.from(new Set((input.queries ?? []).flatMap((query) => query.tables ?? [])));
|
|
63
|
+
const contexts = tables.length
|
|
64
|
+
? tables.map((table) => ({ table: table }))
|
|
65
|
+
: [{}];
|
|
66
|
+
const values = [];
|
|
67
|
+
for (const context of contexts) {
|
|
68
|
+
const resolved = await config.resolve('read.maxTokens', context);
|
|
69
|
+
const value = typeof resolved.value === 'number' ? resolved.value : Number(resolved.value);
|
|
70
|
+
if (Number.isFinite(value) && value >= 256) {
|
|
71
|
+
values.push(Math.floor(value));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return values.length ? Math.max(...values) : 4000;
|
|
58
75
|
}
|
|
59
76
|
catch {
|
|
60
77
|
return 4000;
|
|
@@ -98,7 +98,20 @@ class FAM_Config_ControlService {
|
|
|
98
98
|
if (globalHit) {
|
|
99
99
|
return this.toResolved(globalHit, 'global');
|
|
100
100
|
}
|
|
101
|
-
// 4. builtin default (
|
|
101
|
+
// 4. PER-TÁR builtin default (`defaultsByTable`, 2026-07-28): ahol egy tár természetes igénye eltér a
|
|
102
|
+
// globálistól (pl. a `rules` tárban MINDEN releváns szabálynak be kell férnie, nem csak a top-5-nek), ott a
|
|
103
|
+
// tár saját default-ja érvényesül — DB-írás/seed nélkül. A fenti EXPLICIT szintek (scope/table/global) győznek.
|
|
104
|
+
// A katalógus `satisfies`-a megőrzi a kulcs-literál típusokat (szigorú `FAM_ConfigKey` union), ezért a közös
|
|
105
|
+
// opcionális mezők eléréséhez tipizált NÉZET kell — `as` cast NÉLKÜL (az entry-k `satisfies`-olják az interfészt).
|
|
106
|
+
const entry = config_catalog_const_1.CONFIG_CATALOG[key];
|
|
107
|
+
const byTable = table ? entry.defaultsByTable?.[table] : undefined;
|
|
108
|
+
if (byTable !== undefined) {
|
|
109
|
+
return {
|
|
110
|
+
value: byTable,
|
|
111
|
+
resolvedFrom: 'builtin',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// 5. builtin default (mindig van — dsgn-007 §3 / §10.3).
|
|
102
115
|
return {
|
|
103
116
|
value: config_catalog_const_1.CONFIG_CATALOG[key].default,
|
|
104
117
|
resolvedFrom: 'builtin',
|
package/build/src/app.server.js
CHANGED
|
@@ -6,6 +6,7 @@ const nts_dynamo_1 = require("@futdevpro/nts-dynamo");
|
|
|
6
6
|
const logs_1 = require("@futdevpro/nts-dynamo/logs");
|
|
7
7
|
const package_json_1 = require("../package.json");
|
|
8
8
|
const fam_db_models_const_1 = require("./_collections/fam-db-models.const");
|
|
9
|
+
const fam_remote_config_util_1 = require("./_collections/fam-remote-config.util");
|
|
9
10
|
const api_controller_1 = require("./_routes/server/api/api.controller");
|
|
10
11
|
const client_app_controller_1 = require("./_routes/server/client-app/client-app.controller");
|
|
11
12
|
const errors_controller_1 = require("./_routes/server/errors/errors.controller");
|
|
@@ -31,6 +32,18 @@ class App extends nts_dynamo_1.DyNTS_App {
|
|
|
31
32
|
const dbName = process.env.MONGODB_DB_NAME || 'fdp-agent-memory';
|
|
32
33
|
// Connection-string: MONGODB_URI (dsgn-012 §7) vagy a régi MONGO_URL, vagy lokális default.
|
|
33
34
|
const mongoBase = process.env.MONGODB_URI || process.env.MONGO_URL || 'mongodb://0.0.0.0:29017';
|
|
35
|
+
// TÁVOLI ELÉRÉS (user-FR 2026-07-28): a bind-host a `~/.fam/config.json` `bindHost`-jából (env felülírja).
|
|
36
|
+
// DEFAULT: loopback — a FAM-nak SZÁNDÉKOSAN nincs authja (ADR-004), ezért alapból NEM hallgatózik a hálózaton.
|
|
37
|
+
// A VM→host elérés EXPLICIT opt-in: `fam remote set --bind 0.0.0.0` (vagy a host-only adapter konkrét IP-je).
|
|
38
|
+
const bindHost = fam_remote_config_util_1.FAM_RemoteConfig_Util.resolveBindHost({
|
|
39
|
+
env: process.env.FAM_SERVER_HOST, file: fam_remote_config_util_1.FAM_RemoteConfig_Util.load(),
|
|
40
|
+
});
|
|
41
|
+
if (!fam_remote_config_util_1.FAM_RemoteConfig_Util.isLoopbackHost(bindHost)) {
|
|
42
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM] TÁVOLI ELÉRÉS AKTÍV — a szerver a(z) '${bindHost}' címen hallgat, NEM csak loopbackon. `
|
|
43
|
+
+ 'A FAM-nak NINCS authentikációja (ADR-004: lokális, single-user), ezért a hálózaton bárki '
|
|
44
|
+
+ 'olvashatja/írhatja a TELJES tudás-korpuszt. Csak MEGBÍZHATÓ hálózaton (host-only VM-adapter / VPN) '
|
|
45
|
+
+ 'használd; publikus/megosztott hálón NE. Visszaállítás: `fam remote set --bind 127.0.0.1`.');
|
|
46
|
+
}
|
|
34
47
|
return new nts_dynamo_1.DyNTS_App_Params({
|
|
35
48
|
name: 'FDP Agent Memory Server',
|
|
36
49
|
title: title_const_1.FAM_title,
|
|
@@ -38,6 +51,7 @@ class App extends nts_dynamo_1.DyNTS_App {
|
|
|
38
51
|
dbName: dbName,
|
|
39
52
|
dbUri: `${mongoBase}/${dbName}`,
|
|
40
53
|
systemShortCodeName: 'FAM',
|
|
54
|
+
openHost: bindHost,
|
|
41
55
|
});
|
|
42
56
|
}
|
|
43
57
|
overrideDynamoNTSGlobalSettings() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futdevpro/fdp-agent-memory",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.122",
|
|
4
4
|
"description": "Local-first, vector-backed multi-table agent memory exposed as an MCP server (read/write/capabilities). Public, FDP-Templates-free, no auth.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|