UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

696 lines (695 loc) • 28.3 kB
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-6kR-86XC.mjs"; import { r as isPathInside } from "./path-guards-CH7noY0Z.mjs"; import { c as sleepWithAbort } from "./src-DqaLEvc_.mjs"; import { t as formatCliCommand } from "./command-format-Cte1SWM4.mjs"; import { t as createSubsystemLogger } from "./subsystem-BlrgtxyM.mjs"; import "./string-coerce-runtime-B1b8UNzb.mjs"; import "./memory-core-host-engine-storage-CTEDJ14W.mjs"; import { d as matchesExtraMemoryPathEntry, f as normalizeExtraMemoryPathEntries } from "./read-file-DIndQpFN.mjs"; import { t as isFileMissingError } from "./fs-utils-WWARJ71o.mjs"; import { r as classifyMemoryMultimodalPath } from "./multimodal-Sk6z2tSa.mjs"; import "./runtime-env-DMdB1vhM.mjs"; import { t as getFileWatchCapacityCode } from "./fs-watch-errors-D0zoLJOB.mjs"; import "./setup-tools-BWaf0HA8.mjs"; import "./file-access-runtime-ISL--r1N.mjs"; import "./memory-core-host-engine-embeddings-Dh7SoebN.mjs"; import "./memory-core-host-engine-foundation-DCyVtqPj.mjs"; import fs from "node:fs"; import path from "node:path"; import fs$1 from "node:fs/promises"; import chokidar from "chokidar"; //#region extensions/memory-core/src/memory/watch-pressure.ts const MEMORY_WATCH_PRESSURE_WARNING_THRESHOLD = 2e3; function countChokidarWatchedEntries(watcher) { const watched = watcher.getWatched(); let count = Object.keys(watched).length; for (const entries of Object.values(watched)) count += entries.length; return count; } function warnIfMemoryWatchPressureHigh(state, count, unit, pressureDetail, remediation, warn) { if (state.shown || count <= MEMORY_WATCH_PRESSURE_WARNING_THRESHOLD) return false; state.shown = true; warn(`Memory file watching is tracking ${count} ${unit}. ${pressureDetail} ${remediation}`); return true; } const MEMORY_WATCH_SETTLE_RECHECK_MS = 100; function snapshotFromStats(stats) { if (!stats || stats.isDirectory?.()) return null; if (typeof stats.size !== "number" || typeof stats.mtimeMs !== "number") return null; return { size: stats.size, mtimeMs: stats.mtimeMs }; } function snapshotsMatch(left, right) { if (left === null || right === null) return left === right; return left.size === right.size && left.mtimeMs === right.mtimeMs; } async function snapshotPath(filePath) { try { return snapshotFromStats(await fs$1.stat(filePath)); } catch { return null; } } function recordMemoryWatchEventPath(queue, watchPath, stats) { if (!watchPath) return; const trimmed = watchPath.trim(); if (!trimmed) return; queue.set(path.resolve(trimmed), snapshotFromStats(stats)); if (queue.size > 1024) queue.clear(); } async function settleMemoryWatchEventPaths(queue, signal) { signal?.throwIfAborted(); if (queue.size === 0) return true; const entries = Array.from(queue.entries()); queue.clear(); const missingBaseline = []; for (const [filePath, previousSnapshot] of entries) { signal?.throwIfAborted(); const currentSnapshot = await snapshotPath(filePath); signal?.throwIfAborted(); if (previousSnapshot === null) { if (currentSnapshot !== null) missingBaseline.push({ filePath, snapshot: currentSnapshot }); continue; } if (!snapshotsMatch(previousSnapshot, currentSnapshot) && !queue.has(filePath) && queue.size < 1024) queue.set(filePath, currentSnapshot); } if (missingBaseline.length > 0) { await sleepWithAbort(MEMORY_WATCH_SETTLE_RECHECK_MS, signal); for (const entry of missingBaseline) { signal?.throwIfAborted(); const currentSnapshot = await snapshotPath(entry.filePath); signal?.throwIfAborted(); if (!snapshotsMatch(entry.snapshot, currentSnapshot) && !queue.has(entry.filePath) && queue.size < 1024) queue.set(entry.filePath, currentSnapshot); } } return queue.size === 0; } //#endregion //#region extensions/memory-core/src/memory/manager-watch-resources.ts const MEMORY_WATCH_PRESSURE_STARTUP_CHECK_DELAY_MS = 1e4; const log$1 = createSubsystemLogger("memory"); const TEST_MEMORY_WATCH_FACTORY_KEY = Symbol.for("openclaw.test.memoryWatchFactory"); const TEST_MEMORY_NATIVE_WATCH_FACTORY_KEY = Symbol.for("openclaw.test.memoryNativeWatchFactory"); var MemoryFileWatchResources = class { constructor(agentId, watchSettings, callbacks) { this.agentId = agentId; this.watchSettings = watchSettings; this.callbacks = callbacks; this.closed = false; this.watcher = null; this.memoryWatchPressureStartupTimer = null; this.pendingWatchPaths = /* @__PURE__ */ new Map(); this.pendingChange = false; this.watchTimer = null; this.starting = null; this.reconciling = null; this.settling = null; this.lifetime = new AbortController(); this.pendingReconciliations = /* @__PURE__ */ new Map(); this.reconcileAll = false; this.nativeMemoryWatchPairs = []; this.memoryWatchPressureWarning = { shown: false }; this.memoryWatchCapacityDegraded = false; } get capacityDegraded() { return this.memoryWatchCapacityDegraded; } scheduleMemoryWatchPressureStartupCheck() { if (this.memoryWatchPressureStartupTimer || this.memoryWatchPressureWarning.shown || this.closed || this.nativeMemoryWatchPairs.length === 0 && !this.watcher) return; this.memoryWatchPressureStartupTimer = setTimeout(() => { this.memoryWatchPressureStartupTimer = null; if (this.closed || this.memoryWatchPressureWarning.shown) return; if (this.watcher) this.warnIfMemoryWatchPressure(countChokidarWatchedEntries(this.watcher), "paths"); if (this.memoryWatchPressureWarning.shown) return; let directoryCount = 0; for (const pair of this.nativeMemoryWatchPairs) directoryCount += pair.treeWatchers?.size ?? 0; this.warnIfMemoryWatchPressure(directoryCount, "directories"); }, MEMORY_WATCH_PRESSURE_STARTUP_CHECK_DELAY_MS); } warnIfMemoryWatchPressure(count, unit) { const reindexCommand = formatCliCommand(`openclaw memory index --force --agent ${this.agentId}`); warnIfMemoryWatchPressureHigh(this.memoryWatchPressureWarning, count, unit, "Large memory folders or extraPaths can make OpenClaw run out of file watchers or open files.", `Remove unnecessary memory.search.extraPaths entries or narrow their directory roots, including per-agent entries; otherwise review the host's file-watch/open-file limits. After changes, restart the Gateway. To refresh the affected index, run in the Gateway's environment: ${reindexCommand}.`, (message) => log$1.warn(message)); } closeNativeMemoryWatchChildren(pair) { if (pair.treeWatchers) { for (const entry of pair.treeWatchers.values()) this.closeNativeMemoryWatcher(entry.watcher); pair.treeWatchers.clear(); } else this.closeNativeMemoryWatcher(pair.main); pair.main = null; } closeNativeMemoryWatcher(watcher) { try { watcher?.close(); } catch {} } closeNativeMemoryWatchPair(pair) { this.closeNativeMemoryWatchChildren(pair); this.closeNativeMemoryWatcher(pair.parent); pair.parent = null; const index = this.nativeMemoryWatchPairs.indexOf(pair); if (index >= 0) this.nativeMemoryWatchPairs.splice(index, 1); } closeNativeMemoryWatchPairs() { while (this.nativeMemoryWatchPairs.length > 0) { const pair = this.nativeMemoryWatchPairs[0]; if (!pair) return; this.closeNativeMemoryWatchPair(pair); } } degradeMemoryWatchCapacity(watchPath, err, markDirty) { const code = getFileWatchCapacityCode(err); if (!code) return false; if (this.memoryWatchCapacityDegraded) return true; this.memoryWatchCapacityDegraded = true; this.callbacks.onUnavailable(); this.closeNativeMemoryWatchPairs(); const watcher = this.watcher; if (watcher) { watcher.close().catch((error) => { log$1.warn(`memory watcher close failed: ${String(error)}`); }); watcher.on("error", () => {}); } markDirty(); log$1.warn(`memory watcher capacity exhausted on ${watchPath} (${code}); watching disabled, memory will refresh on search`); return true; } start() { return this.starting ??= this.startWatching(); } markMemoryWatchDirty(watchPath, stats) { this.pendingChange = true; recordMemoryWatchEventPath(this.pendingWatchPaths, watchPath, stats); this.callbacks.onDirty?.(); this.scheduleWatchSync(); } resolveMemoryWatchFactory() { if (process.env.VITEST === "true" || false) { const override = Reflect.get(globalThis, TEST_MEMORY_WATCH_FACTORY_KEY); if (typeof override === "function") return override; } return chokidar.watch.bind(chokidar); } resolveMemoryNativeWatchFactory() { if (process.env.VITEST === "true" || false) { const override = Reflect.get(globalThis, TEST_MEMORY_NATIVE_WATCH_FACTORY_KEY); if (typeof override === "function") return override; } return fs.watch.bind(fs); } async close() { this.closed = true; this.lifetime.abort(); this.pendingReconciliations.clear(); this.reconcileAll = false; if (this.watchTimer) { clearTimeout(this.watchTimer); this.watchTimer = null; } if (this.memoryWatchPressureStartupTimer) { clearTimeout(this.memoryWatchPressureStartupTimer); this.memoryWatchPressureStartupTimer = null; } this.closeNativeMemoryWatchPairs(); await Promise.allSettled([ this.starting, this.reconciling, this.settling ]); if (this.watcher) { await this.watcher.close(); this.watcher = null; } this.pendingWatchPaths.clear(); this.pendingChange = false; } scheduleWatchSync() { if (this.closed) return; if (this.watchTimer) clearTimeout(this.watchTimer); this.watchTimer = setTimeout(() => { this.watchTimer = null; if (this.settling) return; const settle = (async () => { if (this.closed) return; await this.reconciling; if (!await settleMemoryWatchEventPaths(this.pendingWatchPaths, this.lifetime.signal)) return; if (this.closed) return; this.pendingChange = false; await this.callbacks.onChange(); })().catch((error) => { if (!this.closed) log$1.warn(`memory sync failed (watch): ${String(error)}`); }).finally(() => { if (this.settling === settle) { this.settling = null; if (this.pendingChange) this.scheduleWatchSync(); } }); this.settling = settle; }, this.watchSettings.sync.watchDebounceMs); } enqueueReconciliation(key, run) { if (this.closed) return Promise.resolve(); if (!this.reconcileAll) { this.pendingReconciliations.set(key, run); if (this.pendingReconciliations.size > 1024) { this.pendingReconciliations.clear(); this.reconcileAll = true; } } if (!this.reconciling) { const pending = Promise.resolve().then(async () => { await this.starting; while (!this.closed) { if (this.reconcileAll) { this.reconcileAll = false; const pairs = [...this.nativeMemoryWatchPairs]; for (const pair of pairs) await pair.reconcile?.(); continue; } const next = this.pendingReconciliations.entries().next().value; if (!next) { this.reconciling = null; return; } this.pendingReconciliations.delete(next[0]); await next[1](); } }).catch((error) => { if (!this.closed) { this.pendingReconciliations.clear(); this.reconcileAll = false; this.pendingChange = true; this.callbacks.onDirty?.(); this.callbacks.onUnavailable(); this.scheduleWatchSync(); log$1.warn(`memory watch reconciliation failed: ${String(error)}`); } }).finally(() => { if (this.reconciling === pending) this.reconciling = null; }); this.reconciling = pending; } return this.reconciling; } }; //#endregion //#region extensions/memory-core/src/memory/file-watcher.ts const IGNORED_MEMORY_WATCH_DIR_NAMES = /* @__PURE__ */ new Set([ ".git", "node_modules", ".pnpm-store", ".venv", "venv", ".tox", "__pycache__" ]); const log = createSubsystemLogger("memory"); function shouldIgnoreMemoryWatchPath(watchPath, stats, multimodalSettings) { const normalized = path.normalize(watchPath); if (normalized.split(path.sep).map((segment) => normalizeLowercaseStringOrEmpty(segment)).some((segment) => IGNORED_MEMORY_WATCH_DIR_NAMES.has(segment))) return true; if (stats?.isDirectory?.()) return false; if (!stats) return false; const extension = normalizeLowercaseStringOrEmpty(path.extname(normalized)); if (extension.length === 0 || extension === ".md") return false; if (!multimodalSettings) return true; return classifyMemoryMultimodalPath(normalized, multimodalSettings) === null; } /** Native Memory file coverage and settling, independent of index or provider state. */ var MemoryFileWatcher = class extends MemoryFileWatchResources { constructor(options) { super(options.agentId, options.settings, options); this.nextWatchGeneration = 0; this.workspaceDir = options.workspaceDir; this.settings = options.settings; } async startWatching() { if (this.closed) return; if (this.memoryWatchCapacityDegraded || this.watcher || this.nativeMemoryWatchPairs.length > 0) return; const fileWatchPaths = /* @__PURE__ */ new Set([path.join(this.workspaceDir, "MEMORY.md"), path.join(this.workspaceDir, "USER.md")]); const memoryDir = path.join(this.workspaceDir, "memory"); const dirWatchPaths = /* @__PURE__ */ new Set([memoryDir]); const additionalPaths = normalizeExtraMemoryPathEntries(this.workspaceDir, this.settings.extraPaths); for (const entry of additionalPaths) try { const stat = await fs$1.lstat(entry.path); if (this.closed) return; if (stat.isSymbolicLink()) continue; if (stat.isDirectory()) { dirWatchPaths.add(entry.path); continue; } if (stat.isFile() && (normalizeLowercaseStringOrEmpty(entry.path).endsWith(".md") || classifyMemoryMultimodalPath(entry.path, this.settings.multimodal) !== null)) fileWatchPaths.add(entry.path); } catch { continue; } const markDirty = (watchPath, stats) => { if (this.closed) return; if (watchPath) { if (shouldIgnoreMemoryWatchPath(watchPath, stats, this.settings.multimodal)) return; const normalizedWatchPath = path.resolve(watchPath); const matchingEntries = isPathInside(memoryDir, normalizedWatchPath) || normalizedWatchPath === path.join(this.workspaceDir, "MEMORY.md") || normalizedWatchPath === path.join(this.workspaceDir, "USER.md") ? [] : additionalPaths.filter((entry) => isPathInside(entry.path, normalizedWatchPath)); const couldBeFile = !stats?.isDirectory?.() && !shouldIgnoreMemoryWatchPath(watchPath, stats ?? {}, this.settings.multimodal); const couldBeDirectory = !stats || stats.isDirectory?.(); if (matchingEntries.length > 0 && !matchingEntries.some((entry) => couldBeFile && matchesExtraMemoryPathEntry(entry, normalizedWatchPath) || couldBeDirectory && matchesExtraMemoryPathEntry(entry, normalizedWatchPath, { directory: true }))) return; } this.markMemoryWatchDirty(watchPath, stats); }; const nativeRecursiveSupported = process.platform === "darwin" || process.platform === "win32"; for (const dir of dirWatchPaths) { if ((nativeRecursiveSupported ? await this.attachNativeMemoryWatchForDir(dir, markDirty) : process.platform === "linux" ? await this.attachLinuxMemoryDirectoryTreeWatchForDir(dir, markDirty) : "failed") !== "attached") fileWatchPaths.add(dir); if (this.memoryWatchCapacityDegraded) return; } if (fileWatchPaths.size > 0) this.attachMemoryChokidarPaths(Array.from(fileWatchPaths), markDirty); this.scheduleMemoryWatchPressureStartupCheck(); } async attachNativeMemoryWatchForDir(dir, markDirty, stillCurrent = () => true) { if (this.closed || this.memoryWatchCapacityDegraded || !stillCurrent()) return "failed"; let recordedInode; try { recordedInode = (await fs$1.stat(dir)).ino; } catch (err) { return isFileMissingError(err) ? "missing" : "failed"; } const pair = { dir, main: null, parent: null }; if (this.closed || this.memoryWatchCapacityDegraded || !stillCurrent()) return "failed"; let mainWatcher; const generation = ++this.nextWatchGeneration; try { mainWatcher = this.resolveMemoryNativeWatchFactory()(dir, { recursive: true }, (_eventType, filename) => this.enqueueReconciliation(`${generation}\0${filename ?? ""}`, async () => { if (this.closed || mainWatcher && pair.main !== mainWatcher) return; if (filename == null) { markDirty(); return; } const full = path.join(dir, filename); let stats; try { stats = await fs$1.lstat(full); } catch { stats = void 0; } if (this.closed || pair.main !== mainWatcher) return; markDirty(full, stats); })); } catch (err) { if (isFileMissingError(err)) return "missing"; if (this.degradeMemoryWatchCapacity(dir, err, markDirty)) return "failed"; log.warn(`failed to start native recursive watcher on ${dir}: ${String(err)}; falling back to chokidar`); return "failed"; } pair.main = mainWatcher; mainWatcher.on("error", (err) => { if (pair.main !== mainWatcher) return; if (this.degradeMemoryWatchCapacity(dir, err, markDirty)) return; const message = err instanceof Error ? err.message : String(err); log.warn(`memory native watcher error on ${dir}: ${message}`); this.closeNativeMemoryWatchPair(pair); if (this.closed) return; markDirty(); this.attachMemoryChokidarFallback(dir, markDirty); }); this.nativeMemoryWatchPairs.push(pair); pair.reconcile = async () => markDirty(); await this.attachNativeMemoryParentWatch(pair, recordedInode, markDirty, "native", () => this.attachNativeMemoryWatchForDir(dir, markDirty, () => this.nativeMemoryWatchPairs.includes(pair)), stillCurrent); if (!stillCurrent()) { this.closeNativeMemoryWatchPair(pair); return "failed"; } return "attached"; } async attachNativeMemoryParentWatch(pair, recordedInode, markDirty, label, reattach, stillCurrent) { const { dir } = pair; let watchedInode = recordedInode; const parentDir = path.dirname(dir); const baseName = path.basename(dir); let parentInode; try { parentInode = (await fs$1.stat(parentDir)).ino; } catch (err) { log.warn(`memory ${label} parent watcher could not start on ${parentDir}: ${String(err)}`); return; } if (this.closed || !stillCurrent() || !this.nativeMemoryWatchPairs.includes(pair)) return; let parentWatcher = null; const reconcileRoot = async () => { if (this.closed || !this.nativeMemoryWatchPairs.includes(pair)) return; let currentInode = null; let result = "missing"; try { currentInode = (await fs$1.stat(dir)).ino; } catch (err) { result = isFileMissingError(err) ? "missing" : "failed"; if (result === "missing") try { if ((await fs$1.stat(parentDir)).ino !== parentInode) result = "failed"; } catch { result = "failed"; } } if (this.closed || !this.nativeMemoryWatchPairs.includes(pair)) return; if (currentInode === watchedInode && result !== "failed") return; this.closeNativeMemoryWatchChildren(pair); watchedInode = null; markDirty(); if (currentInode !== null) result = await reattach(); if (this.closed || !this.nativeMemoryWatchPairs.includes(pair)) return; if (result === "missing" && pair.parent) return; this.closeNativeMemoryWatchPair(pair); if (result !== "attached") this.attachMemoryChokidarFallback(dir, markDirty); }; const reconcileChildren = pair.reconcile; pair.reconcile = async () => { await reconcileRoot(); if (!this.closed && this.nativeMemoryWatchPairs.includes(pair) && pair.main) await reconcileChildren?.(); }; const generation = ++this.nextWatchGeneration; try { parentWatcher = this.resolveMemoryNativeWatchFactory()(parentDir, { recursive: false }, (_eventType, filename) => { if (this.closed || pair.parent !== parentWatcher) return; if (filename !== null && filename !== baseName && (pair.main || filename !== path.basename(parentDir))) return; return this.enqueueReconciliation(`${generation}\0parent`, reconcileRoot); }); const attachedParent = parentWatcher; attachedParent.on("error", (err) => { if (pair.parent !== attachedParent) return; if (this.degradeMemoryWatchCapacity(dir, err, markDirty)) return; const message = err instanceof Error ? err.message : String(err); log.warn(`memory ${label} parent watcher error on ${path.dirname(dir)}: ${message}`); this.closeNativeMemoryWatcher(attachedParent); pair.parent = null; if (!pair.main) { this.closeNativeMemoryWatchPair(pair); if (!this.closed) { markDirty(); this.attachMemoryChokidarFallback(dir, markDirty); } } }); pair.parent = attachedParent; await reconcileRoot(); } catch (err) { if (this.degradeMemoryWatchCapacity(dir, err, markDirty)) return; log.warn(`memory ${label} parent watcher could not start on ${path.dirname(dir)}: ${String(err)}`); } } async attachLinuxMemoryDirectoryTreeWatchForDir(dir, markDirty, stillCurrent = () => true) { if (this.closed || this.memoryWatchCapacityDegraded || !stillCurrent()) return "failed"; let recordedInode; try { recordedInode = (await fs$1.stat(dir)).ino; } catch (err) { return isFileMissingError(err) ? "missing" : "failed"; } let pair = null; const treeWatchers = /* @__PURE__ */ new Map(); let rootMissing = false; let installing = true; const current = () => !this.closed && !this.memoryWatchCapacityDegraded && (!installing || stillCurrent()) && (!pair || this.nativeMemoryWatchPairs.includes(pair)); const closeAndFallback = (message, cause) => { if (this.memoryWatchCapacityDegraded) return; if (cause !== void 0 && this.degradeMemoryWatchCapacity(dir, cause, markDirty)) return; log.warn(message); if (pair) this.closeNativeMemoryWatchPair(pair); if (this.closed) return; markDirty(); this.attachMemoryChokidarFallback(dir, markDirty); }; const closeDirectorySubtree = (watchDir) => { const watchDirPrefix = `${watchDir}${path.sep}`; for (const [entryDir, entry] of Array.from(treeWatchers.entries())) { if (entryDir !== watchDir && !entryDir.startsWith(watchDirPrefix)) continue; this.closeNativeMemoryWatcher(entry.watcher); treeWatchers.delete(entryDir); } }; const attachDirectory = async (watchDir) => { if (!current()) return null; let currentInode; try { const currentStat = await fs$1.stat(watchDir); if (!currentStat.isDirectory()) return null; currentInode = currentStat.ino; } catch (err) { rootMissing ||= watchDir === dir && isFileMissingError(err); return null; } if (!current()) return null; const existing = treeWatchers.get(watchDir); if (existing) { if (existing.ino === currentInode) return existing.watcher; closeDirectorySubtree(watchDir); } let watcher; const generation = ++this.nextWatchGeneration; try { watcher = this.resolveMemoryNativeWatchFactory()(watchDir, { recursive: false }, (eventType, filename) => this.enqueueReconciliation(`${generation}\0${filename ?? ""}`, async () => { if (this.closed || watcher && treeWatchers.get(watchDir)?.watcher !== watcher) return; if (filename == null) { markDirty(); if (!await this.attachLinuxSubtree(watchDir, attachDirectory, current)) closeAndFallback(`failed to refresh Linux memory directory watchers under ${watchDir}; falling back to chokidar`); return; } const full = path.join(watchDir, filename); let stats; try { stats = await fs$1.lstat(full); } catch { stats = void 0; } if (!current() || treeWatchers.get(watchDir)?.watcher !== watcher) return; if (!stats) closeDirectorySubtree(full); if (stats?.isDirectory()) { if (eventType === "rename") closeDirectorySubtree(full); if (!await this.attachLinuxSubtree(full, attachDirectory, current)) { closeAndFallback(`failed to attach Linux memory directory watcher under ${full}; falling back to chokidar`); return; } } markDirty(full, stats); })); } catch (err) { rootMissing ||= watchDir === dir && isFileMissingError(err); if (this.degradeMemoryWatchCapacity(watchDir, err, markDirty)) return null; if (watchDir === dir && !rootMissing) log.warn(`failed to start Linux memory directory watcher on ${watchDir}: ${String(err)}; falling back to chokidar`); return null; } treeWatchers.set(watchDir, { watcher, ino: currentInode }); watcher.on("error", (err) => { if (treeWatchers.get(watchDir)?.watcher !== watcher) return; const detail = err instanceof Error ? err.message : String(err); closeAndFallback(`memory Linux directory watcher error on ${watchDir}: ${detail}`, err); }); return watcher; }; const mainWatcher = await attachDirectory(dir); if (!mainWatcher) return rootMissing ? "missing" : "failed"; pair = { dir, main: mainWatcher, parent: null, treeWatchers }; this.nativeMemoryWatchPairs.push(pair); pair.reconcile = async () => { if (!current()) return; markDirty(); const visited = /* @__PURE__ */ new Set([dir]); if (!await this.attachLinuxSubtree(dir, attachDirectory, current, visited)) { closeAndFallback(`failed to refresh Linux memory directory watchers under ${dir}; falling back to chokidar`); return; } for (const watchedDir of treeWatchers.keys()) if (!visited.has(watchedDir)) closeDirectorySubtree(watchedDir); }; let subtreeAttached = await this.attachLinuxSubtree(dir, attachDirectory, current); try { subtreeAttached = (await fs$1.stat(dir)).ino === recordedInode && subtreeAttached; } catch (err) { this.closeNativeMemoryWatchPair(pair); if (!this.closed) markDirty(); return isFileMissingError(err) ? "missing" : "failed"; } if (!current()) { this.closeNativeMemoryWatchPair(pair); return "failed"; } if (!subtreeAttached) { closeAndFallback(`failed to attach Linux memory directory watcher subtree under ${dir}; falling back to chokidar`); return "attached"; } await this.attachNativeMemoryParentWatch(pair, recordedInode, markDirty, "Linux", () => this.attachLinuxMemoryDirectoryTreeWatchForDir(dir, markDirty, () => this.nativeMemoryWatchPairs.includes(pair)), stillCurrent); if (!stillCurrent()) { this.closeNativeMemoryWatchPair(pair); return "failed"; } installing = false; return "attached"; } async attachLinuxSubtree(root, attachDirectory, current, visited) { if (!current()) return true; let rootStats; try { rootStats = await fs$1.lstat(root); } catch { return false; } if (!rootStats?.isDirectory() || shouldIgnoreMemoryWatchPath(root, rootStats, this.settings.multimodal)) return true; if (!current()) return true; if (!await attachDirectory(root)) return false; visited?.add(root); let entries; try { entries = await fs$1.readdir(root, { withFileTypes: true }); } catch { return false; } for (const entry of entries) { if (!entry.isDirectory() || entry.isSymbolicLink()) continue; if (!await this.attachLinuxSubtree(path.join(root, entry.name), attachDirectory, current, visited)) return false; } return true; } attachMemoryChokidarFallback(dir, markDirty) { if (this.closed || this.memoryWatchCapacityDegraded) return; try { this.attachMemoryChokidarPaths(dir, markDirty); } catch (err) { log.warn(`failed to attach chokidar fallback for ${dir}: ${String(err)}`); } } attachMemoryChokidarPaths(paths, markDirty) { if (this.closed || this.memoryWatchCapacityDegraded) return; if (this.watcher) { this.watcher.add(paths); return; } const watcher = this.resolveMemoryWatchFactory()(typeof paths === "string" ? [paths] : paths, { ignoreInitial: true, ignored: (watchPath, stats) => shouldIgnoreMemoryWatchPath(watchPath, stats, this.settings.multimodal) }); this.watcher = watcher; watcher.on("add", markDirty); watcher.on("change", markDirty); watcher.on("unlink", (watchPath) => markDirty(watchPath, { isDirectory: () => false })); watcher.on("unlinkDir", (watchPath) => markDirty(watchPath, { isDirectory: () => true })); watcher.on("error", (err) => { if (this.degradeMemoryWatchCapacity("chokidar", err, markDirty)) return; const message = err instanceof Error ? err.message : String(err); log.warn(`memory watcher error: ${message}`); }); watcher.once("ready", () => { this.warnIfMemoryWatchPressure(countChokidarWatchedEntries(watcher), "paths"); }); } }; //#endregion export { MemoryFileWatcher as t };