From 7e28081c51fe4584147eb8ca3be5f6cf0c15380e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Thu=E1=BA=ADn=20Ph=C3=A1t?= Date: Tue, 17 Mar 2026 00:25:53 +0700 Subject: [PATCH] fix: add DISABLE_SIGNIFICANCE_FILTER env var to bypass memory filtering The isSignificantKnowledge() and isWorkspaceSignificantContent() functions use regex skipPatterns that silently reject legitimate content containing words like "email", "address", "phone", "login", "password". This causes memory extraction to return extracted: 0, skipped: 1 with no user-facing indication of why. Add DISABLE_SIGNIFICANCE_FILTER=true env var to bypass these filters. Default is false (no behavior change for existing users). Also fixes pre-existing prettier formatting in neo4j.ts. Fixes #263 --- .../memory/extract_and_operate_memory.ts | 4 + .../definitions/memory/workspace_store.ts | 4 + src/core/env.ts | 4 + src/core/knowledge_graph/backend/neo4j.ts | 126 +++++++++--------- 4 files changed, 74 insertions(+), 64 deletions(-) diff --git a/src/core/brain/tools/definitions/memory/extract_and_operate_memory.ts b/src/core/brain/tools/definitions/memory/extract_and_operate_memory.ts index 703e92bc..1b5461e8 100644 --- a/src/core/brain/tools/definitions/memory/extract_and_operate_memory.ts +++ b/src/core/brain/tools/definitions/memory/extract_and_operate_memory.ts @@ -20,6 +20,10 @@ function isSignificantKnowledge(content: string): boolean { return false; } + if (process.env.DISABLE_SIGNIFICANCE_FILTER === 'true') { + return true; + } + const text = content.toLowerCase().trim(); // Skip trivial tool results and non-technical content diff --git a/src/core/brain/tools/definitions/memory/workspace_store.ts b/src/core/brain/tools/definitions/memory/workspace_store.ts index 1c50795c..9920b4cd 100644 --- a/src/core/brain/tools/definitions/memory/workspace_store.ts +++ b/src/core/brain/tools/definitions/memory/workspace_store.ts @@ -39,6 +39,10 @@ function isWorkspaceSignificantContent(content: string): boolean { return false; } + if (process.env.DISABLE_SIGNIFICANCE_FILTER === 'true') { + return true; + } + const text = content.toLowerCase().trim(); // Skip patterns that are not workspace-relevant diff --git a/src/core/env.ts b/src/core/env.ts index e22a885f..8f3ab791 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -117,6 +117,7 @@ const envSchema = z.object({ USE_WORKSPACE_MEMORY: z.boolean().default(false), WORKSPACE_SEARCH_THRESHOLD: z.number().default(0.4), DISABLE_DEFAULT_MEMORY: z.boolean().default(false), + DISABLE_SIGNIFICANCE_FILTER: z.boolean().default(false), WORKSPACE_VECTOR_STORE_TYPE: z .enum(['qdrant', 'milvus', 'chroma', 'pinecone', 'pgvector', 'in-memory']) .optional(), @@ -350,6 +351,8 @@ export const env: EnvSchema = new Proxy({} as EnvSchema, { : 0.4; case 'DISABLE_DEFAULT_MEMORY': return process.env.DISABLE_DEFAULT_MEMORY === 'true'; + case 'DISABLE_SIGNIFICANCE_FILTER': + return process.env.DISABLE_SIGNIFICANCE_FILTER === 'true'; case 'WORKSPACE_VECTOR_STORE_TYPE': return process.env.WORKSPACE_VECTOR_STORE_TYPE; case 'WORKSPACE_VECTOR_STORE_HOST': @@ -553,6 +556,7 @@ export const validateEnv = () => { ? parseFloat(process.env.WORKSPACE_SEARCH_THRESHOLD) : 0.4, DISABLE_DEFAULT_MEMORY: process.env.DISABLE_DEFAULT_MEMORY === 'true', + DISABLE_SIGNIFICANCE_FILTER: process.env.DISABLE_SIGNIFICANCE_FILTER === 'true', WORKSPACE_VECTOR_STORE_TYPE: process.env.WORKSPACE_VECTOR_STORE_TYPE, WORKSPACE_VECTOR_STORE_HOST: process.env.WORKSPACE_VECTOR_STORE_HOST, WORKSPACE_VECTOR_STORE_PORT: process.env.WORKSPACE_VECTOR_STORE_PORT diff --git a/src/core/knowledge_graph/backend/neo4j.ts b/src/core/knowledge_graph/backend/neo4j.ts index 671bdbd7..5bb858d4 100644 --- a/src/core/knowledge_graph/backend/neo4j.ts +++ b/src/core/knowledge_graph/backend/neo4j.ts @@ -931,70 +931,68 @@ export class Neo4jBackend implements KnowledgeGraph { return converted; } - private buildFilterConstraints( - filters: NodeFilters | EdgeFilters, - prefix: string, - params: any - ): string { - const constraints: string[] = []; - - for (const [rawKey, filter] of Object.entries(filters)) { - const propertyKey = this.escapePropertyKey(rawKey); - const paramKey = `${prefix}_${Object.keys(params).length}`; - - if (typeof filter === 'object' && filter !== null && !Array.isArray(filter)) { - // Range filters - if ('gte' in filter) { - constraints.push(`${prefix}.${propertyKey} >= $${paramKey}_gte`); - params[`${paramKey}_gte`] = filter.gte; - } - if ('gt' in filter) { - constraints.push(`${prefix}.${propertyKey} > $${paramKey}_gt`); - params[`${paramKey}_gt`] = filter.gt; - } - if ('lte' in filter) { - constraints.push(`${prefix}.${propertyKey} <= $${paramKey}_lte`); - params[`${paramKey}_lte`] = filter.lte; - } - if ('lt' in filter) { - constraints.push(`${prefix}.${propertyKey} < $${paramKey}_lt`); - params[`${paramKey}_lt`] = filter.lt; - } - - // Array filters - if ('any' in filter && Array.isArray(filter.any)) { - constraints.push(`${prefix}.${propertyKey} IN $${paramKey}`); - params[paramKey] = filter.any; - } - if ('all' in filter && Array.isArray(filter.all)) { - // For 'all' filter, check if the property (assumed to be array) contains all values - constraints.push( - `all(x IN $${paramKey} WHERE x IN ${prefix}.${propertyKey})` - ); - params[paramKey] = filter.all; - } - } else { - // Direct equality - constraints.push(`${prefix}.${propertyKey} = $${paramKey}`); - params[paramKey] = filter; - } - } - - return constraints.join(' AND '); - } - - private escapePropertyKey(key: string): string { - if (key.trim().length === 0) { - throw new Error('Filter property names must not be empty.'); - } - - if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) { - return key; - } - - const escapedKey = key.replace(/`/g, '``'); - return `\`${escapedKey}\``; - } + private buildFilterConstraints( + filters: NodeFilters | EdgeFilters, + prefix: string, + params: any + ): string { + const constraints: string[] = []; + + for (const [rawKey, filter] of Object.entries(filters)) { + const propertyKey = this.escapePropertyKey(rawKey); + const paramKey = `${prefix}_${Object.keys(params).length}`; + + if (typeof filter === 'object' && filter !== null && !Array.isArray(filter)) { + // Range filters + if ('gte' in filter) { + constraints.push(`${prefix}.${propertyKey} >= $${paramKey}_gte`); + params[`${paramKey}_gte`] = filter.gte; + } + if ('gt' in filter) { + constraints.push(`${prefix}.${propertyKey} > $${paramKey}_gt`); + params[`${paramKey}_gt`] = filter.gt; + } + if ('lte' in filter) { + constraints.push(`${prefix}.${propertyKey} <= $${paramKey}_lte`); + params[`${paramKey}_lte`] = filter.lte; + } + if ('lt' in filter) { + constraints.push(`${prefix}.${propertyKey} < $${paramKey}_lt`); + params[`${paramKey}_lt`] = filter.lt; + } + + // Array filters + if ('any' in filter && Array.isArray(filter.any)) { + constraints.push(`${prefix}.${propertyKey} IN $${paramKey}`); + params[paramKey] = filter.any; + } + if ('all' in filter && Array.isArray(filter.all)) { + // For 'all' filter, check if the property (assumed to be array) contains all values + constraints.push(`all(x IN $${paramKey} WHERE x IN ${prefix}.${propertyKey})`); + params[paramKey] = filter.all; + } + } else { + // Direct equality + constraints.push(`${prefix}.${propertyKey} = $${paramKey}`); + params[paramKey] = filter; + } + } + + return constraints.join(' AND '); + } + + private escapePropertyKey(key: string): string { + if (key.trim().length === 0) { + throw new Error('Filter property names must not be empty.'); + } + + if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) { + return key; + } + + const escapedKey = key.replace(/`/g, '``'); + return `\`${escapedKey}\``; + } private async createIndexes(): Promise { const session = this.getSession();