@futdevpro/fdp-agent-memory 1.1.113 → 1.1.115

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fdp-agent-memory",
3
- "version": "1.1.113",
3
+ "version": "1.1.115",
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": {
@@ -405,6 +405,38 @@ exports.CONFIG_CATALOG = {
405
405
  + '(pl. mindig ugyanazon a tartalmon összeomló) job nem indul újra a végtelenségig minden restartkor. `0` = '
406
406
  + 'soha ne resume-olj automatikusan.',
407
407
  },
408
+ // --- 4.5b. Weekly auto full re-scan + delete-sync (fogyasztó: FAM_ScanScheduler_ControlService) ---
409
+ 'scan.weeklyRescanEnabled': {
410
+ type: 'boolean', default: true, levels: GLOBAL_ONLY,
411
+ description: 'Heti automatikus TELJES FAM re-scan + törlés-szinkron (reconcile/delta) be/ki (2026-07-26). A meglévő '
412
+ + 'reconcile-logika (a scan orphan-soft-delete-eli a lemezről eltűnt fájlok chunkjait) HETI ütemezéssel fut, hogy '
413
+ + 'a FAM-tartalom naprakész maradjon. A cél a `scan.weeklyRescanRoot` (ha üres → a LEGUTÓBBI durable scan-job '
414
+ + 'cél-specjeit re-scanneli). Best-effort, in-process (nincs külső cron). `false` → nincs heti auto-re-scan.',
415
+ },
416
+ 'scan.weeklyRescanDayOfWeek': {
417
+ type: 'number', default: 1, min: 0, max: 6, integer: true, levels: GLOBAL_ONLY,
418
+ description: 'A heti re-scan napja (LOKÁLIS idő): `0`=vasárnap … `1`=hétfő (default) … `6`=szombat. A slot a '
419
+ + '`scan.weeklyRescanHour` órán. Ha a szerver a slot-időben nem futott (down), a következő indításkor BEPÓTOLJA '
420
+ + '(catch-up: a legutóbbi elmúlt slot óta nem futott → most fut).',
421
+ },
422
+ 'scan.weeklyRescanHour': {
423
+ type: 'number', default: 7, min: 0, max: 23, integer: true, levels: GLOBAL_ONLY,
424
+ description: 'A heti re-scan órája (LOKÁLIS idő, 0-23; default `7` = 07:00). A `scan.weeklyRescanDayOfWeek` napján ez '
425
+ + 'az óra:00 a heti slot.',
426
+ },
427
+ 'scan.weeklyRescanRoot': {
428
+ type: 'string', default: '', levels: GLOBAL_ONLY,
429
+ description: 'A heti re-scan gyökere (abszolút út). Ha megadva → e root alatt felderített projektek re-scanje '
430
+ + '(reconcile-lel). Ha ÜRES (default) → a LEGUTÓBBI durable scan-job cél-specjeit re-scanneli (zero-config '
431
+ + 'freshness). Ha egyik sincs (üres + nincs korábbi scan) → a heti futás SKIP + egyszeri warn (nincs találgatás).',
432
+ },
433
+ 'scan.weeklyRescanLastRunAt': {
434
+ type: 'number', default: 0, min: 0, max: 9999999999999, integer: true, levels: GLOBAL_ONLY,
435
+ description: 'ÁLLAPOT/observability (NEM felhasználói hangoló): a legutóbbi heti re-scan TRIGGER epoch-ms-e. A '
436
+ + 'scheduler ezzel dönti el, hogy egy heti slot óta futott-e már (idempotencia + catch-up + restart-storm '
437
+ + 'elleni védelem). `0` = még sosem futott. A scheduler írja; kézzel ne állítsd (a `fam config get`-tel '
438
+ + 'olvasható, mikor futott utoljára).',
439
+ },
408
440
  // --- 4.6. Reference / scope (fogyasztó: dsgn-002) ---
409
441
  'reference.cacheTtlMs': {
410
442
  type: 'number', default: 60000, min: 0, max: 3600000, integer: true, levels: GLOBAL_ONLY,
@@ -7,6 +7,7 @@ const fam_hydration_coordinator_control_service_1 = require("./fam-hydration-coo
7
7
  const fam_vector_search_control_service_1 = require("./fam-vector-search.control-service");
8
8
  const fam_scope_maintenance_control_service_1 = require("../../scope-reference/_services/fam-scope-maintenance.control-service");
9
9
  const fam_scan_job_control_service_1 = require("../../ingest/_services/fam-scan-job.control-service");
10
+ const fam_scan_scheduler_control_service_1 = require("../../ingest/_services/fam-scan-scheduler.control-service");
10
11
  const fam_memory_dormancy_control_service_1 = require("../../retrieval/_services/fam-memory-dormancy.control-service");
11
12
  /**
12
13
  * `FAM_EmbeddingBootstrap_ControlService` (SP-2.2/2.4) — az embedding + vektor-réteg **boot-hook**-ja.
@@ -97,6 +98,15 @@ class FAM_EmbeddingBootstrap_ControlService {
97
98
  catch (error) {
98
99
  fsm_dynamo_1.DyFM_Log.warn(`[FAM bootstrap] memory-dormancy sweep sikertelen: ${error?.message}`);
99
100
  }
101
+ // HETI AUTO RE-SCAN + DELETE-SYNC (dispatch „Problem 2", 2026-07-26): az in-process ütemező telepítése — a heti
102
+ // slotot (default hétfő 07:00) figyeli + boot-catch-up-ol. A reconcile-logika már létezik; ez csak az ütemezés.
103
+ // Best-effort (a telepítés hibája nem buktatja a bootot; a scheduler maga is best-effort végig).
104
+ try {
105
+ fam_scan_scheduler_control_service_1.FAM_ScanScheduler_ControlService.getInstance().install();
106
+ }
107
+ catch (error) {
108
+ fsm_dynamo_1.DyFM_Log.warn(`[FAM bootstrap] heti re-scan scheduler telepítése sikertelen: ${error?.message}`);
109
+ }
100
110
  }
101
111
  }
102
112
  exports.FAM_EmbeddingBootstrap_ControlService = FAM_EmbeddingBootstrap_ControlService;
@@ -30,5 +30,21 @@ class FAM_ScanJob_DataService extends nts_dynamo_1.DyNTS_DataService {
30
30
  async findResumable() {
31
31
  return this.findDataList({ status: { $in: ['running', 'pending'] } }, true);
32
32
  }
33
+ /**
34
+ * A LEGUTÓBB indított durable scan-job feloldott cél-specjei (weekly re-scan auto-derive, 2026-07-26): a `startedAt`
35
+ * szerint csökkenő rendben az ELSŐ job, amelynek nem-üres `specs`-e van. Így a heti re-scan zero-config újra-futtatja
36
+ * PONTOSAN a legutóbb szkennelt cél-halmazt (per-projekt path/scope/table), amikor nincs explicit `scan.weeklyRescanRoot`.
37
+ * `undefined`, ha még sosem volt cél-speces scan.
38
+ */
39
+ async findLatestScannedTargets() {
40
+ const found = await this.searchData({ sortBy: [{ key: 'startedAt', order: -1 }], pageSize: 25 });
41
+ for (const job of found.results ?? []) {
42
+ const specs = job.specs;
43
+ if (specs && specs.length) {
44
+ return specs;
45
+ }
46
+ }
47
+ return undefined;
48
+ }
33
49
  }
34
50
  exports.FAM_ScanJob_DataService = FAM_ScanJob_DataService;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ScanScheduler_ControlService = void 0;
4
+ const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
5
+ const fam_config_level_type_enum_1 = require("../../../_enums/fam-config-level.type-enum");
6
+ const config_control_service_1 = require("../../../_routes/server/config/config.control-service");
7
+ const fam_scan_job_control_service_1 = require("./fam-scan-job.control-service");
8
+ const fam_scan_job_data_service_1 = require("./fam-scan-job.data-service");
9
+ /**
10
+ * `FAM_ScanScheduler_ControlService` (2026-07-26, dispatch „Problem 2") — a HETI automatikus TELJES FAM re-scan +
11
+ * törlés-szinkron (reconcile/delta) IN-PROCESS ütemezője. A reconcile-logika MÁR LÉTEZIK (a scan orphan-soft-delete-eli
12
+ * a lemezről eltűnt fájlok chunkjait, dsgn `scan.reconcileIgnored`); ez a service CSAK az ÜTEMEZÉST adja hozzá, hogy a
13
+ * FAM-tartalom naprakész maradjon anélkül, hogy manuálisan re-scannelni kellene.
14
+ *
15
+ * **Slot:** LOKÁLIS idő `scan.weeklyRescanDayOfWeek` (default 1 = hétfő) @ `scan.weeklyRescanHour`:00 (default 7 → 07:00).
16
+ * **Catch-up:** egy fix időközű tick (10 perc) figyeli, hogy a LEGUTÓBBI elmúlt slot óta futott-e már (`scan.weeklyRescanLastRunAt`).
17
+ * Ha a szerver a slot-időben nem futott (down/restart), a következő indításkor BEPÓTOL — a heti freshness így restart-tűrő.
18
+ * **Restart-storm ellen:** a `lastRunAt` a config-store-ban perzisztál → egy héten belüli restart NEM indít újabb re-scant.
19
+ * **Cél:** `scan.weeklyRescanRoot` (ha megadva) VAGY a legutóbbi durable scan-job cél-specjei (zero-config). Egyik sincs → SKIP + warn.
20
+ * Best-effort végig (a hibák logolódnak, a szerver-start/futás NEM bukik el).
21
+ */
22
+ class FAM_ScanScheduler_ControlService {
23
+ static _instance;
24
+ issuer = 'FAM_ScanScheduler_ControlService';
25
+ timer;
26
+ ticking = false;
27
+ /** 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". */
28
+ static CHECK_INTERVAL_MS = 600000;
29
+ static getInstance() {
30
+ if (!FAM_ScanScheduler_ControlService._instance) {
31
+ FAM_ScanScheduler_ControlService._instance = new FAM_ScanScheduler_ControlService();
32
+ }
33
+ return FAM_ScanScheduler_ControlService._instance;
34
+ }
35
+ /** Teszt-reset (a singleton + a timer ürítése). */
36
+ static resetForTest() {
37
+ FAM_ScanScheduler_ControlService._instance?.uninstall();
38
+ FAM_ScanScheduler_ControlService._instance = undefined;
39
+ }
40
+ /**
41
+ * A boot() best-effort hívja: beállít egy periodikus tick-et (CHECK_INTERVAL_MS), ami a heti slotot figyeli, majd
42
+ * AZONNAL futtat egy első tick-et (boot-catch-up: ha a legutóbbi slot már elmúlt és nem futott, most bepótol).
43
+ * Idempotens (kétszeri install → egy timer). A timer `unref`-elt (nem tartja életben a processt önmagában).
44
+ */
45
+ install() {
46
+ if (this.timer) {
47
+ return;
48
+ }
49
+ this.timer = setInterval(() => { void this.tick(Date.now()); }, FAM_ScanScheduler_ControlService.CHECK_INTERVAL_MS);
50
+ if (typeof this.timer.unref === 'function') {
51
+ this.timer.unref();
52
+ }
53
+ fsm_dynamo_1.DyFM_Log.info(`[FAM scanScheduler] heti re-scan figyelő telepítve (tick ${FAM_ScanScheduler_ControlService.CHECK_INTERVAL_MS / 60000} percenként).`);
54
+ void this.tick(Date.now());
55
+ }
56
+ /** A timer leállítása (teszt / graceful). */
57
+ uninstall() {
58
+ if (this.timer) {
59
+ clearInterval(this.timer);
60
+ this.timer = undefined;
61
+ }
62
+ }
63
+ /**
64
+ * Egy ellenőrzési ciklus: DUE-e a heti slot? Ha igen → rögzíti a `lastRunAt`-ot (idempotencia + restart-storm ellen),
65
+ * majd elindítja a re-scant. Best-effort (nem dob). Re-entráns-védett (`ticking`), hogy egy lassú tick ne fedjen át
66
+ * a következővel.
67
+ */
68
+ async tick(nowMs) {
69
+ if (this.ticking) {
70
+ return;
71
+ }
72
+ this.ticking = true;
73
+ try {
74
+ const config = config_control_service_1.FAM_Config_ControlService.getInstance();
75
+ const enabled = (await config.resolve('scan.weeklyRescanEnabled')).value;
76
+ if (enabled === false) {
77
+ return;
78
+ }
79
+ const day = await this.resolveNum(config, 'scan.weeklyRescanDayOfWeek', 1);
80
+ const hour = await this.resolveNum(config, 'scan.weeklyRescanHour', 7);
81
+ const lastRun = await this.resolveNum(config, 'scan.weeklyRescanLastRunAt', 0);
82
+ const slot = FAM_ScanScheduler_ControlService.mostRecentSlotAtOrBefore(nowMs, day, hour);
83
+ if (lastRun >= slot) {
84
+ return; // e heti slot óta már futott (vagy még nem jött el a slot)
85
+ }
86
+ // DUE: ELŐBB rögzítjük a lastRun-t (így egy crash mid-scan sem indít újra minden restartkor; a heti freshness
87
+ // best-effort, nem exactly-once), MAJD indítjuk a scant.
88
+ await this.markRan(config, nowMs);
89
+ await this.triggerRescan();
90
+ }
91
+ catch (error) {
92
+ fsm_dynamo_1.DyFM_Log.warn(`[FAM scanScheduler] heti re-scan tick sikertelen (best-effort): ${error?.message}`);
93
+ }
94
+ finally {
95
+ this.ticking = false;
96
+ }
97
+ }
98
+ /** A TELJES re-scan indítása: `scan.weeklyRescanRoot` (ha van) VAGY a legutóbbi durable scan-job cél-specjei. */
99
+ async triggerRescan() {
100
+ const origin = { agent: 'fam-scheduler', label: 'weekly-rescan' };
101
+ const config = config_control_service_1.FAM_Config_ControlService.getInstance();
102
+ const rootValue = (await config.resolve('scan.weeklyRescanRoot')).value;
103
+ const root = typeof rootValue === 'string' ? rootValue.trim() : '';
104
+ if (root) {
105
+ await fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().start({ root: root, sourceLocation: root, origin: origin, waitMs: 0 });
106
+ fsm_dynamo_1.DyFM_Log.info(`[FAM scanScheduler] heti TELJES re-scan + reconcile INDÍTVA (root: ${root}).`);
107
+ return;
108
+ }
109
+ // Zero-config: a legutóbbi durable scan-job feloldott cél-specjei (ugyanaz a cél-halmaz, reconcile-lel).
110
+ const targets = await this.loadLatestScannedTargets();
111
+ if (targets && targets.length) {
112
+ await fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().start({ projects: targets, origin: origin, waitMs: 0 });
113
+ fsm_dynamo_1.DyFM_Log.info(`[FAM scanScheduler] heti TELJES re-scan + reconcile INDÍTVA (${targets.length} cél a legutóbbi scan-ből).`);
114
+ return;
115
+ }
116
+ fsm_dynamo_1.DyFM_Log.warn('[FAM scanScheduler] heti re-scan esedékes, DE nincs cél: állítsd be a `scan.weeklyRescanRoot`-ot '
117
+ + 'vagy futtass egy scant előbb (durable cél-specek) — most SKIP.');
118
+ }
119
+ /**
120
+ * A legutóbbi durable scan-job feloldott cél-specjei (zero-config re-scan). KÜLÖN metódus, hogy a `DyNTS_DataService`
121
+ * eager DB-resolve-ja (memory: `dynts_dataservice_eager_resolve`) NE példányosítsa a data-service-t a DB-mentes unit
122
+ * tesztekben — a teszt ezt a metódust spy-olja. A tényleges query a `FAM_ScanJob_DataService` specjében fedve.
123
+ */
124
+ async loadLatestScannedTargets() {
125
+ return new fam_scan_job_data_service_1.FAM_ScanJob_DataService({ issuer: this.issuer }).findLatestScannedTargets();
126
+ }
127
+ /** A `lastRunAt` állapot perzisztálása a config-store-ba (global szint). Best-effort. */
128
+ async markRan(config, nowMs) {
129
+ try {
130
+ await config.set('scan.weeklyRescanLastRunAt', nowMs, {
131
+ level: fam_config_level_type_enum_1.FAM_ConfigLevel.global, setBy: 'system', note: 'weekly re-scan trigger',
132
+ });
133
+ }
134
+ catch (error) {
135
+ fsm_dynamo_1.DyFM_Log.warn(`[FAM scanScheduler] lastRunAt perzisztálás sikertelen: ${error?.message}`);
136
+ }
137
+ }
138
+ /** Egy szám-config feloldása a default-tal. */
139
+ async resolveNum(config, key, fallback) {
140
+ const value = (await config.resolve(key)).value;
141
+ return typeof value === 'number' ? value : fallback;
142
+ }
143
+ /**
144
+ * PURE, tesztelhető: a `nowMs`-nál NEM-későbbi legutóbbi (`dayOfWeek` @ `hour`:00) LOKÁLIS-idő slot epoch-ms-e.
145
+ * (a) ma `hour`:00-ra állítja, (b) visszalép a cél-hétköznapra (`getDay()` diff, 0=vasárnap), (c) ha az így kapott
146
+ * slot még a JÖVŐben van (ma a cél-nap, de a `hour` még nem jött el), egy héttel korábbra lép. DST-re best-effort.
147
+ */
148
+ static mostRecentSlotAtOrBefore(nowMs, dayOfWeek, hour) {
149
+ const d = new Date(nowMs);
150
+ d.setHours(hour, 0, 0, 0);
151
+ const dayDiff = (d.getDay() - dayOfWeek + 7) % 7;
152
+ d.setDate(d.getDate() - dayDiff);
153
+ if (d.getTime() > nowMs) {
154
+ d.setDate(d.getDate() - 7);
155
+ }
156
+ return d.getTime();
157
+ }
158
+ }
159
+ exports.FAM_ScanScheduler_ControlService = FAM_ScanScheduler_ControlService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fdp-agent-memory",
3
- "version": "1.1.113",
3
+ "version": "1.1.115",
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": {