diff --git a/packages/core/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts b/packages/core/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts index 118cee70..06fed1f0 100644 --- a/packages/core/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts +++ b/packages/core/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts @@ -214,6 +214,93 @@ export class JSONModelsProvider extends ModelsProviderConnector { return this.isValidSingleModel(data); } + /** + * Determines whether a file path should be ignored by the directory watcher. + * + * This method implements a sophisticated filtering strategy for dot-segment paths + * (paths containing directories that start with a dot, like .git, .env, .cache). + * + * **Filtering Strategy:** + * 1. Paths WITHOUT dot segments: Never ignored + * 2. Paths WITH dot segments: + * - If SMYTH_PATH is not configured: All ignored + * - If SMYTH_PATH is configured: + * - Allow the watched directory even if SMYTH_PATH contains dot-segments + * (e.g., /home/user/.smyth/models/OpenAI/default.json is allowed) + * - Ignore dot-segments INSIDE the models directory + * (e.g., /home/user/.smyth/models/.hidden/model.json is ignored) + * - Paths outside watched directory: Ignored + * + * @param filePath - The file path to check + * @param watchedDir - The absolute path of the directory being watched (models folder) + * @param smythPath - The resolved SMYTH_PATH, or null if not configured + * @returns true if the path should be ignored, false if it should be watched + * + * @example + * ```typescript + * // Path without dot-segment (allowed) + * shouldIgnorePath('/models/OpenAI/default.json', '/models', '/home/.smyth') // => false + * + * // Dot-segment inside models directory (ignored) + * shouldIgnorePath('/models/.git/config', '/models', '/home/.smyth') // => true + * + * // Dot-segment in parent path only (allowed) + * shouldIgnorePath('/home/.smyth/models/OpenAI/default.json', '/home/.smyth/models', '/home/.smyth') // => false + * ``` + */ + private shouldIgnorePath(filePath: string, watchedDir: string, smythPath: string | null): boolean { + // Check if the file path contains a dot-segment (e.g., /.git/, /.env/, /.cache/) + // Regex explanation: [\\/]\. matches a path separator (/ or \) followed by a dot + const hasDotSegment = /[\\/]\./.test(filePath); + + // CASE 1: If there is NO dot-segment at all, we never ignore this path + // Examples: /models/OpenAI/default.json, /models/Anthropic/claude.json + if (!hasDotSegment) { + return false; + } + + // CASE 2: If there IS a dot-segment and SMYTH_PATH is not configured, + // we ignore all such paths to prevent watching system/hidden files + // Examples: /.git/config, /node_modules/.cache/file.json + if (hasDotSegment && !smythPath) { + return true; + } + + // Resolve the file path to an absolute path for accurate comparison + // This ensures we can reliably compare against the watched directory path + const resolvedPath = path.resolve(filePath); + + // Check if the resolved path is inside the watched directory (models folder) + // This handles two cases: + // 1. The path exactly matches the watched directory + // 2. The path is a child of the watched directory (starts with watchedDir + separator) + const isInsideWatchedDir = resolvedPath === watchedDir || resolvedPath.startsWith(watchedDir + path.sep); + + // CASE 3: If the path is outside the watched directory, ignore it + // This prevents watching unrelated files that happen to have dot-segments + if (!isInsideWatchedDir) { + return true; + } + + // CASE 4: Path is inside the watched directory + // Now we need to determine if the dot-segment is in the models directory itself + // or if it is part of the parent path (e.g., SMYTH_PATH containing .smyth) + + // Get the relative path from the watched directory to determine where the dot-segment is + const relativePath = path.relative(watchedDir, resolvedPath); + + // Check if the dot-segment appears in the relative portion (inside models directory) + // Regex explanation: (^|[\\/])\. matches a dot at the start OR after a path separator + // Examples that match: '.git/config', 'subdir/.hidden/file.json' + const hasDotSegmentInsideWatchedDir = /(^|[\\/])\./.test(relativePath); + + // FINAL DECISION: + // - If dot-segment is INSIDE the models directory (e.g., models/.git/config), IGNORE it (return true) + // - If dot-segment is OUTSIDE the models directory (e.g., /home/user/.smyth/models/OpenAI/default.json), ALLOW it (return false) + // This allows SMYTH_PATH to contain dot-segments while preventing dot-segments within the models folder + return hasDotSegmentInsideWatchedDir; + } + private initDirWatcher(dir) { const stats = fsSync.statSync(dir); @@ -257,8 +344,12 @@ export class JSONModelsProvider extends ModelsProviderConnector { maxWait: 5000, }); + const smythPath = process.env.SMYTH_PATH ? path.resolve(process.env.SMYTH_PATH) : null; + const watchedDir = path.resolve(dir); + const watcher = chokidar.watch(dir, { - ignored: /(^|[\/\\])\../, // ignore dotfiles + // Use the extracted method for path filtering + ignored: (filePath: string) => this.shouldIgnorePath(filePath, watchedDir, smythPath), persistent: true, ignoreInitial: true, // Don't fire events for files that already exist awaitWriteFinish: {