From e2bb133795d3dbf2f63686aaf47256851014a5ad Mon Sep 17 00:00:00 2001 From: Matthew Martella Date: Tue, 7 Jul 2026 16:36:24 +0800 Subject: [PATCH] release: v1.6.0 - Implement strict tool selection, active runtime optimization advice, and Odoo wildcard fixes --- .gitignore | 2 +- dist/bundle/index.js | 293 +++++++++++++++++++-- dist/bundle/package.json | 2 +- dist/bundle/services/domain-validator.d.ts | 26 ++ dist/bundle/tools/get_environment.d.ts | 5 + dist/bundle/tools/schemas.d.ts | 3 + dist/bundle/tools/search_records.d.ts | 16 +- dist/mcp-server.js | 2 +- dist/services/domain-validator.d.ts | 26 ++ dist/services/domain-validator.js | 233 ++++++++++++++++ dist/services/domain-validator.js.map | 1 + dist/tools/get_environment.d.ts | 5 + dist/tools/get_environment.js | 7 +- dist/tools/get_environment.js.map | 2 +- dist/tools/schemas.d.ts | 3 + dist/tools/schemas.js | 13 +- dist/tools/schemas.js.map | 2 +- dist/tools/search_records.d.ts | 16 +- dist/tools/search_records.js | 27 +- dist/tools/search_records.js.map | 2 +- gemini-extension.json | 2 +- package.json | 2 +- skills/odoo-data-ops/SKILL.md | 17 +- skills/odoo-get-started/SKILL.md | 6 +- src/mcp-server.ts | 2 +- src/services/domain-validator.ts | 265 +++++++++++++++++++ src/tools/get_environment.ts | 7 +- src/tools/schemas.ts | 13 +- src/tools/search_records.ts | 33 ++- tests/crud-tools.test.ts | 30 +++ tests/search_records_orchestration.test.ts | 44 ++++ 31 files changed, 1050 insertions(+), 57 deletions(-) create mode 100644 dist/bundle/services/domain-validator.d.ts create mode 100644 dist/services/domain-validator.d.ts create mode 100644 dist/services/domain-validator.js create mode 100644 dist/services/domain-validator.js.map create mode 100644 src/services/domain-validator.ts diff --git a/.gitignore b/.gitignore index af23d8e..ea93b7b 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,4 @@ lerna-debug.log* *.test.ts.snap /working/ /secrets.json -/reference/ +reference/ diff --git a/dist/bundle/index.js b/dist/bundle/index.js index def2ad9..cecd58a 100644 --- a/dist/bundle/index.js +++ b/dist/bundle/index.js @@ -8068,7 +8068,7 @@ function escapeJsonPtr(str) { /***/ }), -/***/ 4525: +/***/ 6906: /***/ (function(module, __unused_webpack_exports, __nccwpck_require__) { // Generated by CoffeeScript 1.10.0 @@ -8237,7 +8237,7 @@ function escapeJsonPtr(str) { XMLDTDAttList = __nccwpck_require__(3742); - XMLDTDEntity = __nccwpck_require__(4525); + XMLDTDEntity = __nccwpck_require__(6906); XMLDTDElement = __nccwpck_require__(6189); @@ -8419,7 +8419,7 @@ function escapeJsonPtr(str) { XMLDTDAttList = __nccwpck_require__(3742); - XMLDTDEntity = __nccwpck_require__(4525); + XMLDTDEntity = __nccwpck_require__(6906); XMLDTDElement = __nccwpck_require__(6189); @@ -9467,7 +9467,7 @@ function escapeJsonPtr(str) { XMLDTDElement = __nccwpck_require__(6189); - XMLDTDEntity = __nccwpck_require__(4525); + XMLDTDEntity = __nccwpck_require__(6906); XMLDTDNotation = __nccwpck_require__(7083); @@ -9752,7 +9752,7 @@ function escapeJsonPtr(str) { XMLDTDElement = __nccwpck_require__(6189); - XMLDTDEntity = __nccwpck_require__(4525); + XMLDTDEntity = __nccwpck_require__(6906); XMLDTDNotation = __nccwpck_require__(7083); @@ -38482,7 +38482,7 @@ const SEARCH_RECORDS_SCHEMA = { type: "object", properties: { model: { type: "string", description: 'Technical name of the model (e.g., "res.partner", "project.task").' }, - domain: { type: "array", items: {}, description: 'Odoo domain filter. A list of triplets: [["field", "operator", value]]. Example: [["is_company", "=", true]]. Use empty list [] for all records.' }, + domain: { type: "array", items: {}, description: 'Odoo domain filter. A list of triplets: [["field", "operator", value]]. Example: [["is_company", "=", true]]. MULTI-COMPANY TIP: By default, Brass-Monkey automatically injects allowed_company_ids so you see all authorized companies. To search within a specific company, always include [["company_id", "=", COMPANY_ID]].' }, fields: { type: "array", items: { type: "string" }, description: "Optional explicit list of field names to retrieve. If omitted, returns lightweight Breadth fields." }, limit: { type: "number", description: "Maximum number of records to return (defaults to 10)." }, offset: { type: "number", description: "Number of records to skip (for pagination, defaults to 0)." }, @@ -38491,6 +38491,7 @@ const SEARCH_RECORDS_SCHEMA = { instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model"], + description: "Search for Odoo records. WARNING: DO NOT use this tool to inspect details of a single known record ID (you MUST use 'get_record' for 360-degree dashboards). DO NOT use this tool to sum, group, count, or average numeric fields (you MUST use 'aggregate_records' for server-side SQL-level summaries).", }; const GET_RECORD_SCHEMA = { type: "object", @@ -38513,6 +38514,7 @@ const GET_RECORD_SCHEMA = { with_translations: { type: "boolean", description: "If True, translatable fields are returned in translation matrix." }, instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, + description: "MANDATORY for retrieving a comprehensive 360-degree dashboard report for a single Odoo record. Highly superior to search_records for single record lookups as it resolves sub-lines, relation display names, and chatter threads in one call. Use this immediately once you have a target record ID.", }; const GET_RECORDS_SCHEMA = { type: "object", @@ -38549,6 +38551,7 @@ const AGGREGATE_RECORDS_SCHEMA = { instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model", "groupby"], + description: "Calculate database-level summaries, aggregations, counts, sums, or averages grouped by specified fields. MANDATORY: You MUST use this tool instead of querying lists of records with search_records and computing statistics locally.", }; const GET_AUDIT_LOG_SCHEMA = { type: "object", @@ -38561,26 +38564,26 @@ const CREATE_RECORD_SCHEMA = { type: "object", properties: { model: { type: "string", description: 'Technical name of the model (e.g., "res.partner").' }, - values: { type: "object", description: "Dictionary of field values: {'field_name': value}. Use inspect_model to find writable fields." }, + values: { type: "object", description: "Dictionary of field values: {'field_name': value}. Use inspect_model to find writable fields. MULTI-COMPANY CRITICAL: Ensure you specify a valid 'company_id' in values that matches all relational fields (e.g. journals, accounts) to avoid Multi-Company Access/Validation Errors." }, justification: { type: "string", description: "MANDATORY: Explain WHY this record is being created. This is logged to Odoo Chatter and local audit logs." }, with_translations: { type: "boolean", description: "If True, translatable fields can be provided as strings (sync to all) or expanded lists." }, instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model", "values", "justification"], - description: "Create new records in a specified model with audit logging. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", + description: "Create new records in a specified model with audit logging. MULTI-COMPANY RULE: Ensure a correct 'company_id' is supplied in values. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", }; const WRITE_RECORD_SCHEMA = { type: "object", properties: { model: { type: "string", description: 'Technical name of the model (e.g., "res.partner").' }, id: { type: "number", description: "The database ID of the record to update." }, - values: { type: "object", description: "Dictionary of fields to update. PRO TIP: We take a 'Before Snapshot' automatically for reversibility." }, + values: { type: "object", description: "Dictionary of fields to update. PRO TIP: We take a 'Before Snapshot' automatically for reversibility. MULTI-COMPANY CRITICAL: Do not mix records from different companies. Ensure any relational values linked match the record's company_id." }, justification: { type: "string", description: "MANDATORY: Explain WHY this update is necessary. Logged for audit and safety." }, with_translations: { type: "boolean", description: "If True, translatable fields can be provided as strings (sync to all) or expanded lists." }, instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model", "id", "values", "justification"], - description: "Update existing records with field-level tracking. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", + description: "Update existing records with field-level tracking. MULTI-COMPANY RULE: Verify relational safety across companies before writing. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", }; const UNLINK_RECORD_SCHEMA = { type: "object", @@ -39682,11 +39685,246 @@ class OdooOrchestrator { } } +;// CONCATENATED MODULE: ./src/services/domain-validator.ts + + +class DomainValidationService { + /** + * Main entry point to validate and potentially heal an Odoo domain array. + */ + static async validateAndHeal(client, model, domain, instanceAlias = 'default') { + // 1. Ensure Domain is an array + if (!Array.isArray(domain)) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Expected a 2D array of filters, but received type '${typeof domain}'.`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Always pass the domain as an array of triplet arrays: [[field, operator, value]].", + "Example: [['is_company', '=', true]]" + ], + explanation: "The domain must be a valid list." + } + } + }; + } + // 2. Validate prefix operator grammar / Polish Notation structure + if (domain.length > 0) { + const grammarCheck = this.checkPrefixGrammar(domain); + if (!grammarCheck.valid) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Malformed Polish Notation operator structure. ${grammarCheck.error}`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Odoo prefix operators ('&' and '|') require exactly two operands following them.", + "If you have only one condition, do not include any operators. Just pass the single condition, e.g. [['is_company', '=', true]]." + ], + explanation: grammarCheck.error || '' + } + } + }; + } + } + // 3. Hydrate Model Schema (Metadata Cache) + const cache = MetadataCache.getInstance(); + let metadata = cache.get(instanceAlias, model); + if (!metadata) { + try { + metadata = await buildModelMetadata(client, model, instanceAlias); + cache.set(instanceAlias, model, metadata); + } + catch (err) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Target model '${model}' could not be resolved or does not exist.`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Verify that the model name is spelled correctly.", + "Execute list_models with a search_term to discover valid model names." + ] + } + } + }; + } + } + // 4. Retrieve all valid fields from metadata + const validFields = new Set(); + if (metadata.categorized) { + Object.keys(metadata.categorized.base || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.extended || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.computed || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.related || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.relational || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.lines || {}).forEach(f => validFields.add(f)); + } + validFields.add('id'); + validFields.add('create_date'); + validFields.add('write_date'); + validFields.add('create_uid'); + validFields.add('write_uid'); + // Combine all fields to make checking easy + const allFieldDefinitions = { + ...(metadata.categorized?.base || {}), + ...(metadata.categorized?.extended || {}), + ...(metadata.categorized?.computed || {}), + ...(metadata.categorized?.related || {}), + ...(metadata.categorized?.relational || {}), + ...(metadata.categorized?.lines || {}), + }; + // 5. Run Triplet-level Validation & Hybrid Healing + const healedDomain = []; + const validOperators = new Set(['=', '!=', 'like', 'ilike', '=like', '=ilike', 'in', 'not in', 'child_of', '<', '>', '<=', '>=']); + for (const element of domain) { + // If it is an operator string ('&', '|') or list item of operators + if (typeof element === 'string') { + const normalizedOp = element.toLowerCase(); + if (normalizedOp === 'and' || normalizedOp === '&') { + healedDomain.push('&'); + } + else if (normalizedOp === 'or' || normalizedOp === '|') { + healedDomain.push('|'); + } + else if (normalizedOp === 'not' || normalizedOp === '!') { + healedDomain.push('!'); + } + else { + healedDomain.push(element); + } + continue; + } + // If it's a condition triplet [field, operator, value] + if (Array.isArray(element)) { + if (element.length !== 3) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Invalid filter triplet structure. Expected exactly 3 elements: [field, operator, value], but received ${JSON.stringify(element)}.`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Ensure each filter is a flat array of exactly 3 elements, e.g., ['name', 'ilike', 'Matt']." + ] + } + } + }; + } + const [field, operator, value] = element; + // Check if field is valid + if (typeof field === 'string' && !validFields.has(field)) { + // Perform simple substring fuzzy match + const suggestions = Array.from(validFields).filter(vf => field.toLowerCase().includes(vf.toLowerCase()) || + vf.toLowerCase().includes(field.toLowerCase())); + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Field '${field}' does not exist on model '${model}'.`, + diagnostic_hints: { + invalid_field: field, + target_model: model, + did_you_mean_substrings: suggestions.slice(0, 5), + action_directives: [ + `You MUST run 'inspect_model(model: "${model}")' to review the valid properties of this model.`, + "Check for typings: e.g., customers/companies are commonly represented under the field 'partner_id'." + ], + explanation: `The model '${model}' does not contain any field with the name '${field}'.` + } + } + }; + } + // Validate Operator + if (!validOperators.has(operator)) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Unsupported operator '${operator}' on field '${field}'.`, + diagnostic_hints: { + invalid_operator: operator, + target_model: model, + action_directives: [ + "Use only standard Odoo operators: '=', '!=', 'like', 'ilike', 'in', 'not in', '<', '>', '<=', '>=', 'child_of'." + ], + explanation: `Odoo's query engine does not recognize the operator '${operator}'. To search for substrings, use 'ilike'.` + } + } + }; + } + // Hybrid Healing: Cast String "true"/"false" to Boolean + let healedValue = value; + const fieldDef = allFieldDefinitions[field]; + if (fieldDef && fieldDef.type === 'boolean' && typeof value === 'string') { + const valLower = value.toLowerCase(); + if (valLower === 'true' || valLower === '1') { + healedValue = true; + } + else if (valLower === 'false' || valLower === '0') { + healedValue = false; + } + } + healedDomain.push([field, operator, healedValue]); + } + else { + healedDomain.push(element); + } + } + return { + success: true, + healedDomain + }; + } + /** + * Helper to perform arity/grammar checks on prefix domains. + */ + static checkPrefixGrammar(domain) { + let expected = 1; + for (const item of domain) { + if (typeof item === 'string') { + const op = item.toLowerCase(); + if (op === '&' || op === '|' || op === 'and' || op === 'or') { + expected += 1; + } + else if (op === '!' || op === 'not') { + // Net +0 + } + else { + return { valid: false, error: `Invalid string operator found in domain: '${item}'. Only '&', '|', and '!' are supported.` }; + } + } + else if (Array.isArray(item)) { + expected -= 1; + } + else { + return { valid: false, error: `Invalid item format inside domain array: ${JSON.stringify(item)}` }; + } + if (expected < 0) { + return { valid: false, error: "Too many conditions found for the prefix operators supplied." }; + } + } + if (expected !== 0) { + return { valid: false, error: `Unbalanced operators. Expected ${expected} more conditions to satisfy the prefix logical operators.` }; + } + return { valid: true }; + } +} + ;// CONCATENATED MODULE: ./src/tools/search_records.ts + /** * Zod schema for search_records tool input. * Fully pre-processed and optimized. @@ -39734,6 +39972,12 @@ async function searchRecords(manager, input) { const { model, domain, fields, limit, offset, order, with_translations, instance_alias } = parsedInput; const client = await manager.getClient(instance_alias); const alias = instance_alias || 'default'; + // Validate and Heal Domain using decoupled DomainValidationService + const validationResult = await DomainValidationService.validateAndHeal(client, model, domain, alias); + if (!validationResult.success && validationResult.errorPayload) { + return validationResult.errorPayload; + } + const activeDomain = validationResult.healedDomain || domain; let readFields = fields; // 1. Resolve and cache metadata if fields are not specified (Breadth Default) if (!readFields || readFields.length === 0) { @@ -39749,22 +39993,22 @@ async function searchRecords(manager, input) { const targetLimit = limit || 10; const targetOffset = offset || 0; const [records, totalCount] = await Promise.all([ - client.executeKw(model, 'search_read', [domain], { + client.executeKw(model, 'search_read', [activeDomain], { fields: readFields, limit: targetLimit, offset: targetOffset, order, }), - client.executeKw(model, 'search_count', [domain]) + client.executeKw(model, 'search_count', [activeDomain]) ]); // Intent-Based Search Expansion: If zero results and domain has a name filter, retry with ilike let activeRecords = records; let activeTotalCount = totalCount; - if (activeRecords.length === 0 && domain.length > 0) { - const nameFilterIndex = domain.findIndex((d) => Array.isArray(d) && d[0] === 'name' && d[1] === '='); + if (activeRecords.length === 0 && activeDomain.length > 0) { + const nameFilterIndex = activeDomain.findIndex((d) => Array.isArray(d) && d[0] === 'name' && d[1] === '='); if (nameFilterIndex !== -1) { - const expandedDomain = [...domain]; - expandedDomain[nameFilterIndex] = ['name', 'ilike', domain[nameFilterIndex][2]]; + const expandedDomain = [...activeDomain]; + expandedDomain[nameFilterIndex] = ['name', 'ilike', activeDomain[nameFilterIndex][2]]; const [expandedRecords, expandedCount] = await Promise.all([ client.executeKw(model, 'search_read', [expandedDomain], { fields: readFields, @@ -39800,12 +40044,20 @@ async function searchRecords(manager, input) { } } // 4. Construct high-signal Breadth Envelope + const advice = []; + if (targetLimit === 1 || activeRecords.length === 1) { + advice.push("SINGLE RECORD DETECTED: You retrieved a single record ID using search_records. This is highly inefficient. For single record lookups, you MUST use the 'get_record' tool, which provides a comprehensive 360-degree dashboard of sub-lines, relationships, display names, and chatter history in a single call."); + } + if (targetLimit >= 100) { + advice.push("POTENTIAL LOCAL CALCULATION LOOP: You retrieved 100 or more records. If you are calculating sums, averages, grouping, or counting records locally, this is extremely slow and overuses token/API limits. You MUST use 'aggregate_records' for server-side, high-performance database-level calculations instead."); + } return { model, count: activeRecords.length, total_count: activeTotalCount, offset: targetOffset, limit: targetLimit, + ...(advice.length > 0 ? { optimization_advice: advice } : {}), leads: activeRecords.reduce((acc, r) => { acc[String(r.id)] = r.display_name || r.name || `ID ${r.id}`; return acc; @@ -40580,7 +40832,12 @@ async function getEnvironment(manager, input) { const summary = `šŸŒ WORLD MAP: Connected to Odoo ${res.server.version} (${res.server.database}) ${res.server.write_guard ? 'šŸ”’ WRITE_GUARD ACTIVE' : 'šŸ”“ NO GUARD'}.\nšŸ‘¤ USER: ${res.user.name} (${res.user.login})\nšŸ¢ MULTI-COMPANY: Enabled. Accessible Companies: ${companyList}\nšŸ”‘ ACTIVE SKILLS: ${res.session.active_skills.join(', ') || 'none'}\nšŸ’” TIP: You have global visibility. To filter for a specific company, use a domain: [('company_id', '=', ID)].`; return { summary, - environment: res + environment: res, + active_context: { + implicit_allowed_company_ids: user.company_ids, + visibility_scope: "GLOBAL_CROSS_COMPANY", + tip: "Brass-Monkey automatically injects 'allowed_company_ids' representing all your authorized companies into the context of every tool call. You have global read visibility. To filter for a specific company, always use an explicit domain filter, e.g., [['company_id', '=', ID]] or [['company_id', 'in', [ID1, ID2]]]." + } }; } /** @@ -40793,7 +41050,7 @@ async function getAuditLog(manager, input) { const mcp_server_dirname = external_path_default().dirname((0,external_url_.fileURLToPath)(import.meta.url)); // Read package.json for metadata -let mcp_server_version = "1.5.1"; +let mcp_server_version = "1.6.0"; try { // Try both possible locations (source vs bundled) const pkgPaths = [ diff --git a/dist/bundle/package.json b/dist/bundle/package.json index e3431f0..b4ce9eb 100644 --- a/dist/bundle/package.json +++ b/dist/bundle/package.json @@ -1 +1 @@ -{"name":"brass-monkey","version":"1.5.1","type":"module","main":"dist/index.js","scripts":{"build":"tsc && npm run bundle","bundle":"ncc build src/mcp-server.ts -o dist/bundle","test":"vitest run","test:watch":"vitest","lint":"eslint src/**/*.ts"},"keywords":["odoo","gemini","gemini-cli","cli","extension","mcp","ai-agent","xml-rpc","erp","crm"],"author":"Actinon","license":"MIT","description":"A high-fidelity Gemini CLI extension and MCP bridge for Odoo ERP/CRM.","dependencies":{"@modelcontextprotocol/sdk":"^1.29.0","keytar":"^7.9.0","xmlrpc":"^1.3.2","zod":"^4.3.6","zod-to-json-schema":"^3.25.2"},"devDependencies":{"@types/keytar":"^4.4.0","@types/node":"^25.6.0","@types/xmlrpc":"^1.3.10","@vercel/ncc":"^0.38.4","esbuild":"^0.28.0","typescript":"^6.0.3","vitest":"^4.1.4"}} \ No newline at end of file +{"name":"brass-monkey","version":"1.6.0","type":"module","main":"dist/index.js","scripts":{"build":"tsc && npm run bundle","bundle":"ncc build src/mcp-server.ts -o dist/bundle","test":"vitest run","test:watch":"vitest","lint":"eslint src/**/*.ts"},"keywords":["odoo","gemini","gemini-cli","cli","extension","mcp","ai-agent","xml-rpc","erp","crm"],"author":"Actinon","license":"MIT","description":"A high-fidelity Gemini CLI extension and MCP bridge for Odoo ERP/CRM.","dependencies":{"@modelcontextprotocol/sdk":"^1.29.0","keytar":"^7.9.0","xmlrpc":"^1.3.2","zod":"^4.3.6","zod-to-json-schema":"^3.25.2"},"devDependencies":{"@types/keytar":"^4.4.0","@types/node":"^25.6.0","@types/xmlrpc":"^1.3.10","@vercel/ncc":"^0.38.4","esbuild":"^0.28.0","typescript":"^6.0.3","vitest":"^4.1.4"}} \ No newline at end of file diff --git a/dist/bundle/services/domain-validator.d.ts b/dist/bundle/services/domain-validator.d.ts new file mode 100644 index 0000000..e4f2332 --- /dev/null +++ b/dist/bundle/services/domain-validator.d.ts @@ -0,0 +1,26 @@ +export interface ValidationResult { + success: boolean; + healedDomain?: any[]; + errorPayload?: { + isError: boolean; + message: string; + diagnostic_hints: { + invalid_field?: string; + invalid_operator?: string; + target_model: string; + did_you_mean_substrings?: string[]; + action_directives: string[]; + explanation?: string; + }; + }; +} +export declare class DomainValidationService { + /** + * Main entry point to validate and potentially heal an Odoo domain array. + */ + static validateAndHeal(client: any, model: string, domain: any[], instanceAlias?: string): Promise; + /** + * Helper to perform arity/grammar checks on prefix domains. + */ + private static checkPrefixGrammar; +} diff --git a/dist/bundle/tools/get_environment.d.ts b/dist/bundle/tools/get_environment.d.ts index fb7198d..48eb758 100644 --- a/dist/bundle/tools/get_environment.d.ts +++ b/dist/bundle/tools/get_environment.d.ts @@ -16,4 +16,9 @@ export type GetEnvironmentInput = z.infer; export declare function getEnvironment(manager: InstanceManager, input: GetEnvironmentInput): Promise<{ summary: string; environment: any; + active_context: { + implicit_allowed_company_ids: any; + visibility_scope: string; + tip: string; + }; }>; diff --git a/dist/bundle/tools/schemas.d.ts b/dist/bundle/tools/schemas.d.ts index adba78e..c2f7aec 100644 --- a/dist/bundle/tools/schemas.d.ts +++ b/dist/bundle/tools/schemas.d.ts @@ -243,6 +243,7 @@ export declare const SEARCH_RECORDS_SCHEMA: { }; }; required: string[]; + description: string; }; export declare const GET_RECORD_SCHEMA: { type: string; @@ -316,6 +317,7 @@ export declare const GET_RECORD_SCHEMA: { description: string; }; }; + description: string; }; export declare const GET_RECORDS_SCHEMA: { type: string; @@ -437,6 +439,7 @@ export declare const AGGREGATE_RECORDS_SCHEMA: { }; }; required: string[]; + description: string; }; export declare const GET_AUDIT_LOG_SCHEMA: { type: string; diff --git a/dist/bundle/tools/search_records.d.ts b/dist/bundle/tools/search_records.d.ts index 30f9255..c538c11 100644 --- a/dist/bundle/tools/search_records.d.ts +++ b/dist/bundle/tools/search_records.d.ts @@ -20,11 +20,23 @@ export type SearchRecordsInput = z.infer; * Returns a pagination envelope containing total matching count and display display-name mapping. */ export declare function searchRecords(manager: InstanceManager, input: SearchRecordsInput): Promise<{ + isError: boolean; + message: string; + diagnostic_hints: { + invalid_field?: string; + invalid_operator?: string; + target_model: string; + did_you_mean_substrings?: string[]; + action_directives: string[]; + explanation?: string; + }; +} | { + leads: any; + results: any; + optimization_advice?: string[] | undefined; model: string; count: any; total_count: any; offset: number; limit: number; - leads: any; - results: any; }>; diff --git a/dist/mcp-server.js b/dist/mcp-server.js index 862fb6a..1d1ec8c 100644 --- a/dist/mcp-server.js +++ b/dist/mcp-server.js @@ -12,7 +12,7 @@ import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Read package.json for metadata -let version = "1.5.1"; +let version = "1.6.0"; try { // Try both possible locations (source vs bundled) const pkgPaths = [ diff --git a/dist/services/domain-validator.d.ts b/dist/services/domain-validator.d.ts new file mode 100644 index 0000000..e4f2332 --- /dev/null +++ b/dist/services/domain-validator.d.ts @@ -0,0 +1,26 @@ +export interface ValidationResult { + success: boolean; + healedDomain?: any[]; + errorPayload?: { + isError: boolean; + message: string; + diagnostic_hints: { + invalid_field?: string; + invalid_operator?: string; + target_model: string; + did_you_mean_substrings?: string[]; + action_directives: string[]; + explanation?: string; + }; + }; +} +export declare class DomainValidationService { + /** + * Main entry point to validate and potentially heal an Odoo domain array. + */ + static validateAndHeal(client: any, model: string, domain: any[], instanceAlias?: string): Promise; + /** + * Helper to perform arity/grammar checks on prefix domains. + */ + private static checkPrefixGrammar; +} diff --git a/dist/services/domain-validator.js b/dist/services/domain-validator.js new file mode 100644 index 0000000..7d35d34 --- /dev/null +++ b/dist/services/domain-validator.js @@ -0,0 +1,233 @@ +import { MetadataCache } from './metadata-cache.js'; +import { buildModelMetadata } from './metadata-resolver.js'; +export class DomainValidationService { + /** + * Main entry point to validate and potentially heal an Odoo domain array. + */ + static async validateAndHeal(client, model, domain, instanceAlias = 'default') { + // 1. Ensure Domain is an array + if (!Array.isArray(domain)) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Expected a 2D array of filters, but received type '${typeof domain}'.`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Always pass the domain as an array of triplet arrays: [[field, operator, value]].", + "Example: [['is_company', '=', true]]" + ], + explanation: "The domain must be a valid list." + } + } + }; + } + // 2. Validate prefix operator grammar / Polish Notation structure + if (domain.length > 0) { + const grammarCheck = this.checkPrefixGrammar(domain); + if (!grammarCheck.valid) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Malformed Polish Notation operator structure. ${grammarCheck.error}`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Odoo prefix operators ('&' and '|') require exactly two operands following them.", + "If you have only one condition, do not include any operators. Just pass the single condition, e.g. [['is_company', '=', true]]." + ], + explanation: grammarCheck.error || '' + } + } + }; + } + } + // 3. Hydrate Model Schema (Metadata Cache) + const cache = MetadataCache.getInstance(); + let metadata = cache.get(instanceAlias, model); + if (!metadata) { + try { + metadata = await buildModelMetadata(client, model, instanceAlias); + cache.set(instanceAlias, model, metadata); + } + catch (err) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Target model '${model}' could not be resolved or does not exist.`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Verify that the model name is spelled correctly.", + "Execute list_models with a search_term to discover valid model names." + ] + } + } + }; + } + } + // 4. Retrieve all valid fields from metadata + const validFields = new Set(); + if (metadata.categorized) { + Object.keys(metadata.categorized.base || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.extended || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.computed || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.related || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.relational || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.lines || {}).forEach(f => validFields.add(f)); + } + validFields.add('id'); + validFields.add('create_date'); + validFields.add('write_date'); + validFields.add('create_uid'); + validFields.add('write_uid'); + // Combine all fields to make checking easy + const allFieldDefinitions = { + ...(metadata.categorized?.base || {}), + ...(metadata.categorized?.extended || {}), + ...(metadata.categorized?.computed || {}), + ...(metadata.categorized?.related || {}), + ...(metadata.categorized?.relational || {}), + ...(metadata.categorized?.lines || {}), + }; + // 5. Run Triplet-level Validation & Hybrid Healing + const healedDomain = []; + const validOperators = new Set(['=', '!=', 'like', 'ilike', '=like', '=ilike', 'in', 'not in', 'child_of', '<', '>', '<=', '>=']); + for (const element of domain) { + // If it is an operator string ('&', '|') or list item of operators + if (typeof element === 'string') { + const normalizedOp = element.toLowerCase(); + if (normalizedOp === 'and' || normalizedOp === '&') { + healedDomain.push('&'); + } + else if (normalizedOp === 'or' || normalizedOp === '|') { + healedDomain.push('|'); + } + else if (normalizedOp === 'not' || normalizedOp === '!') { + healedDomain.push('!'); + } + else { + healedDomain.push(element); + } + continue; + } + // If it's a condition triplet [field, operator, value] + if (Array.isArray(element)) { + if (element.length !== 3) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Invalid filter triplet structure. Expected exactly 3 elements: [field, operator, value], but received ${JSON.stringify(element)}.`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Ensure each filter is a flat array of exactly 3 elements, e.g., ['name', 'ilike', 'Matt']." + ] + } + } + }; + } + const [field, operator, value] = element; + // Check if field is valid + if (typeof field === 'string' && !validFields.has(field)) { + // Perform simple substring fuzzy match + const suggestions = Array.from(validFields).filter(vf => field.toLowerCase().includes(vf.toLowerCase()) || + vf.toLowerCase().includes(field.toLowerCase())); + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Field '${field}' does not exist on model '${model}'.`, + diagnostic_hints: { + invalid_field: field, + target_model: model, + did_you_mean_substrings: suggestions.slice(0, 5), + action_directives: [ + `You MUST run 'inspect_model(model: "${model}")' to review the valid properties of this model.`, + "Check for typings: e.g., customers/companies are commonly represented under the field 'partner_id'." + ], + explanation: `The model '${model}' does not contain any field with the name '${field}'.` + } + } + }; + } + // Validate Operator + if (!validOperators.has(operator)) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Unsupported operator '${operator}' on field '${field}'.`, + diagnostic_hints: { + invalid_operator: operator, + target_model: model, + action_directives: [ + "Use only standard Odoo operators: '=', '!=', 'like', 'ilike', 'in', 'not in', '<', '>', '<=', '>=', 'child_of'." + ], + explanation: `Odoo's query engine does not recognize the operator '${operator}'. To search for substrings, use 'ilike'.` + } + } + }; + } + // Hybrid Healing: Cast String "true"/"false" to Boolean + let healedValue = value; + const fieldDef = allFieldDefinitions[field]; + if (fieldDef && fieldDef.type === 'boolean' && typeof value === 'string') { + const valLower = value.toLowerCase(); + if (valLower === 'true' || valLower === '1') { + healedValue = true; + } + else if (valLower === 'false' || valLower === '0') { + healedValue = false; + } + } + healedDomain.push([field, operator, healedValue]); + } + else { + healedDomain.push(element); + } + } + return { + success: true, + healedDomain + }; + } + /** + * Helper to perform arity/grammar checks on prefix domains. + */ + static checkPrefixGrammar(domain) { + let expected = 1; + for (const item of domain) { + if (typeof item === 'string') { + const op = item.toLowerCase(); + if (op === '&' || op === '|' || op === 'and' || op === 'or') { + expected += 1; + } + else if (op === '!' || op === 'not') { + // Net +0 + } + else { + return { valid: false, error: `Invalid string operator found in domain: '${item}'. Only '&', '|', and '!' are supported.` }; + } + } + else if (Array.isArray(item)) { + expected -= 1; + } + else { + return { valid: false, error: `Invalid item format inside domain array: ${JSON.stringify(item)}` }; + } + if (expected < 0) { + return { valid: false, error: "Too many conditions found for the prefix operators supplied." }; + } + } + if (expected !== 0) { + return { valid: false, error: `Unbalanced operators. Expected ${expected} more conditions to satisfy the prefix logical operators.` }; + } + return { valid: true }; + } +} +//# sourceMappingURL=domain-validator.js.map \ No newline at end of file diff --git a/dist/services/domain-validator.js.map b/dist/services/domain-validator.js.map new file mode 100644 index 0000000..db9df52 --- /dev/null +++ b/dist/services/domain-validator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"domain-validator.js","sourceRoot":"","sources":["../../src/services/domain-validator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/tools/get_environment.d.ts b/dist/tools/get_environment.d.ts index fb7198d..48eb758 100644 --- a/dist/tools/get_environment.d.ts +++ b/dist/tools/get_environment.d.ts @@ -16,4 +16,9 @@ export type GetEnvironmentInput = z.infer; export declare function getEnvironment(manager: InstanceManager, input: GetEnvironmentInput): Promise<{ summary: string; environment: any; + active_context: { + implicit_allowed_company_ids: any; + visibility_scope: string; + tip: string; + }; }>; diff --git a/dist/tools/get_environment.js b/dist/tools/get_environment.js index ee4b114..123d96c 100644 --- a/dist/tools/get_environment.js +++ b/dist/tools/get_environment.js @@ -100,7 +100,12 @@ export async function getEnvironment(manager, input) { const summary = `šŸŒ WORLD MAP: Connected to Odoo ${res.server.version} (${res.server.database}) ${res.server.write_guard ? 'šŸ”’ WRITE_GUARD ACTIVE' : 'šŸ”“ NO GUARD'}.\nšŸ‘¤ USER: ${res.user.name} (${res.user.login})\nšŸ¢ MULTI-COMPANY: Enabled. Accessible Companies: ${companyList}\nšŸ”‘ ACTIVE SKILLS: ${res.session.active_skills.join(', ') || 'none'}\nšŸ’” TIP: You have global visibility. To filter for a specific company, use a domain: [('company_id', '=', ID)].`; return { summary, - environment: res + environment: res, + active_context: { + implicit_allowed_company_ids: user.company_ids, + visibility_scope: "GLOBAL_CROSS_COMPANY", + tip: "Brass-Monkey automatically injects 'allowed_company_ids' representing all your authorized companies into the context of every tool call. You have global read visibility. To filter for a specific company, always use an explicit domain filter, e.g., [['company_id', '=', ID]] or [['company_id', 'in', [ID1, ID2]]]." + } }; } /** diff --git a/dist/tools/get_environment.js.map b/dist/tools/get_environment.js.map index 8cec3ed..f8a02c9 100644 --- a/dist/tools/get_environment.js.map +++ b/dist/tools/get_environment.js.map @@ -1 +1 @@ -{"version":3,"file":"get_environment.js","sourceRoot":"","sources":["../../src/tools/get_environment.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"get_environment.js","sourceRoot":"","sources":["../../src/tools/get_environment.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/tools/schemas.d.ts b/dist/tools/schemas.d.ts index adba78e..c2f7aec 100644 --- a/dist/tools/schemas.d.ts +++ b/dist/tools/schemas.d.ts @@ -243,6 +243,7 @@ export declare const SEARCH_RECORDS_SCHEMA: { }; }; required: string[]; + description: string; }; export declare const GET_RECORD_SCHEMA: { type: string; @@ -316,6 +317,7 @@ export declare const GET_RECORD_SCHEMA: { description: string; }; }; + description: string; }; export declare const GET_RECORDS_SCHEMA: { type: string; @@ -437,6 +439,7 @@ export declare const AGGREGATE_RECORDS_SCHEMA: { }; }; required: string[]; + description: string; }; export declare const GET_AUDIT_LOG_SCHEMA: { type: string; diff --git a/dist/tools/schemas.js b/dist/tools/schemas.js index 7d9b20d..511a709 100644 --- a/dist/tools/schemas.js +++ b/dist/tools/schemas.js @@ -98,7 +98,7 @@ export const SEARCH_RECORDS_SCHEMA = { type: "object", properties: { model: { type: "string", description: 'Technical name of the model (e.g., "res.partner", "project.task").' }, - domain: { type: "array", items: {}, description: 'Odoo domain filter. A list of triplets: [["field", "operator", value]]. Example: [["is_company", "=", true]]. Use empty list [] for all records.' }, + domain: { type: "array", items: {}, description: 'Odoo domain filter. A list of triplets: [["field", "operator", value]]. Example: [["is_company", "=", true]]. MULTI-COMPANY TIP: By default, Brass-Monkey automatically injects allowed_company_ids so you see all authorized companies. To search within a specific company, always include [["company_id", "=", COMPANY_ID]].' }, fields: { type: "array", items: { type: "string" }, description: "Optional explicit list of field names to retrieve. If omitted, returns lightweight Breadth fields." }, limit: { type: "number", description: "Maximum number of records to return (defaults to 10)." }, offset: { type: "number", description: "Number of records to skip (for pagination, defaults to 0)." }, @@ -107,6 +107,7 @@ export const SEARCH_RECORDS_SCHEMA = { instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model"], + description: "Search for Odoo records. WARNING: DO NOT use this tool to inspect details of a single known record ID (you MUST use 'get_record' for 360-degree dashboards). DO NOT use this tool to sum, group, count, or average numeric fields (you MUST use 'aggregate_records' for server-side SQL-level summaries).", }; export const GET_RECORD_SCHEMA = { type: "object", @@ -129,6 +130,7 @@ export const GET_RECORD_SCHEMA = { with_translations: { type: "boolean", description: "If True, translatable fields are returned in translation matrix." }, instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, + description: "MANDATORY for retrieving a comprehensive 360-degree dashboard report for a single Odoo record. Highly superior to search_records for single record lookups as it resolves sub-lines, relation display names, and chatter threads in one call. Use this immediately once you have a target record ID.", }; export const GET_RECORDS_SCHEMA = { type: "object", @@ -165,6 +167,7 @@ export const AGGREGATE_RECORDS_SCHEMA = { instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model", "groupby"], + description: "Calculate database-level summaries, aggregations, counts, sums, or averages grouped by specified fields. MANDATORY: You MUST use this tool instead of querying lists of records with search_records and computing statistics locally.", }; export const GET_AUDIT_LOG_SCHEMA = { type: "object", @@ -177,26 +180,26 @@ export const CREATE_RECORD_SCHEMA = { type: "object", properties: { model: { type: "string", description: 'Technical name of the model (e.g., "res.partner").' }, - values: { type: "object", description: "Dictionary of field values: {'field_name': value}. Use inspect_model to find writable fields." }, + values: { type: "object", description: "Dictionary of field values: {'field_name': value}. Use inspect_model to find writable fields. MULTI-COMPANY CRITICAL: Ensure you specify a valid 'company_id' in values that matches all relational fields (e.g. journals, accounts) to avoid Multi-Company Access/Validation Errors." }, justification: { type: "string", description: "MANDATORY: Explain WHY this record is being created. This is logged to Odoo Chatter and local audit logs." }, with_translations: { type: "boolean", description: "If True, translatable fields can be provided as strings (sync to all) or expanded lists." }, instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model", "values", "justification"], - description: "Create new records in a specified model with audit logging. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", + description: "Create new records in a specified model with audit logging. MULTI-COMPANY RULE: Ensure a correct 'company_id' is supplied in values. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", }; export const WRITE_RECORD_SCHEMA = { type: "object", properties: { model: { type: "string", description: 'Technical name of the model (e.g., "res.partner").' }, id: { type: "number", description: "The database ID of the record to update." }, - values: { type: "object", description: "Dictionary of fields to update. PRO TIP: We take a 'Before Snapshot' automatically for reversibility." }, + values: { type: "object", description: "Dictionary of fields to update. PRO TIP: We take a 'Before Snapshot' automatically for reversibility. MULTI-COMPANY CRITICAL: Do not mix records from different companies. Ensure any relational values linked match the record's company_id." }, justification: { type: "string", description: "MANDATORY: Explain WHY this update is necessary. Logged for audit and safety." }, with_translations: { type: "boolean", description: "If True, translatable fields can be provided as strings (sync to all) or expanded lists." }, instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model", "id", "values", "justification"], - description: "Update existing records with field-level tracking. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", + description: "Update existing records with field-level tracking. MULTI-COMPANY RULE: Verify relational safety across companies before writing. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", }; export const UNLINK_RECORD_SCHEMA = { type: "object", diff --git a/dist/tools/schemas.js.map b/dist/tools/schemas.js.map index 500e0f7..2a2ea4e 100644 --- a/dist/tools/schemas.js.map +++ b/dist/tools/schemas.js.map @@ -1 +1 @@ -{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/tools/schemas.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/tools/schemas.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/tools/search_records.d.ts b/dist/tools/search_records.d.ts index 30f9255..c538c11 100644 --- a/dist/tools/search_records.d.ts +++ b/dist/tools/search_records.d.ts @@ -20,11 +20,23 @@ export type SearchRecordsInput = z.infer; * Returns a pagination envelope containing total matching count and display display-name mapping. */ export declare function searchRecords(manager: InstanceManager, input: SearchRecordsInput): Promise<{ + isError: boolean; + message: string; + diagnostic_hints: { + invalid_field?: string; + invalid_operator?: string; + target_model: string; + did_you_mean_substrings?: string[]; + action_directives: string[]; + explanation?: string; + }; +} | { + leads: any; + results: any; + optimization_advice?: string[] | undefined; model: string; count: any; total_count: any; offset: number; limit: number; - leads: any; - results: any; }>; diff --git a/dist/tools/search_records.js b/dist/tools/search_records.js index 118192a..dd47132 100644 --- a/dist/tools/search_records.js +++ b/dist/tools/search_records.js @@ -2,6 +2,7 @@ import { z } from 'zod'; import { MetadataCache } from '../services/metadata-cache.js'; import { buildModelMetadata } from '../services/metadata-resolver.js'; import { OdooOrchestrator } from '../services/odoo-orchestrator.js'; +import { DomainValidationService } from '../services/domain-validator.js'; /** * Zod schema for search_records tool input. * Fully pre-processed and optimized. @@ -49,6 +50,12 @@ export async function searchRecords(manager, input) { const { model, domain, fields, limit, offset, order, with_translations, instance_alias } = parsedInput; const client = await manager.getClient(instance_alias); const alias = instance_alias || 'default'; + // Validate and Heal Domain using decoupled DomainValidationService + const validationResult = await DomainValidationService.validateAndHeal(client, model, domain, alias); + if (!validationResult.success && validationResult.errorPayload) { + return validationResult.errorPayload; + } + const activeDomain = validationResult.healedDomain || domain; let readFields = fields; // 1. Resolve and cache metadata if fields are not specified (Breadth Default) if (!readFields || readFields.length === 0) { @@ -64,22 +71,22 @@ export async function searchRecords(manager, input) { const targetLimit = limit || 10; const targetOffset = offset || 0; const [records, totalCount] = await Promise.all([ - client.executeKw(model, 'search_read', [domain], { + client.executeKw(model, 'search_read', [activeDomain], { fields: readFields, limit: targetLimit, offset: targetOffset, order, }), - client.executeKw(model, 'search_count', [domain]) + client.executeKw(model, 'search_count', [activeDomain]) ]); // Intent-Based Search Expansion: If zero results and domain has a name filter, retry with ilike let activeRecords = records; let activeTotalCount = totalCount; - if (activeRecords.length === 0 && domain.length > 0) { - const nameFilterIndex = domain.findIndex((d) => Array.isArray(d) && d[0] === 'name' && d[1] === '='); + if (activeRecords.length === 0 && activeDomain.length > 0) { + const nameFilterIndex = activeDomain.findIndex((d) => Array.isArray(d) && d[0] === 'name' && d[1] === '='); if (nameFilterIndex !== -1) { - const expandedDomain = [...domain]; - expandedDomain[nameFilterIndex] = ['name', 'ilike', domain[nameFilterIndex][2]]; + const expandedDomain = [...activeDomain]; + expandedDomain[nameFilterIndex] = ['name', 'ilike', activeDomain[nameFilterIndex][2]]; const [expandedRecords, expandedCount] = await Promise.all([ client.executeKw(model, 'search_read', [expandedDomain], { fields: readFields, @@ -115,12 +122,20 @@ export async function searchRecords(manager, input) { } } // 4. Construct high-signal Breadth Envelope + const advice = []; + if (targetLimit === 1 || activeRecords.length === 1) { + advice.push("SINGLE RECORD DETECTED: You retrieved a single record ID using search_records. This is highly inefficient. For single record lookups, you MUST use the 'get_record' tool, which provides a comprehensive 360-degree dashboard of sub-lines, relationships, display names, and chatter history in a single call."); + } + if (targetLimit >= 100) { + advice.push("POTENTIAL LOCAL CALCULATION LOOP: You retrieved 100 or more records. If you are calculating sums, averages, grouping, or counting records locally, this is extremely slow and overuses token/API limits. You MUST use 'aggregate_records' for server-side, high-performance database-level calculations instead."); + } return { model, count: activeRecords.length, total_count: activeTotalCount, offset: targetOffset, limit: targetLimit, + ...(advice.length > 0 ? { optimization_advice: advice } : {}), leads: activeRecords.reduce((acc, r) => { acc[String(r.id)] = r.display_name || r.name || `ID ${r.id}`; return acc; diff --git a/dist/tools/search_records.js.map b/dist/tools/search_records.js.map index 4132332..0441ca6 100644 --- a/dist/tools/search_records.js.map +++ b/dist/tools/search_records.js.map @@ -1 +1 @@ -{"version":3,"file":"search_records.js","sourceRoot":"","sources":["../../src/tools/search_records.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"search_records.js","sourceRoot":"","sources":["../../src/tools/search_records.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/gemini-extension.json b/gemini-extension.json index e9d0877..3037fe5 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "brass-monkey", - "version": "1.5.1", + "version": "1.6.0", "description": "A high-fidelity Gemini CLI extension and MCP bridge for Odoo ERP/CRM.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/package.json b/package.json index 5d3713e..6233ac2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "brass-monkey", - "version": "1.5.1", + "version": "1.6.0", "type": "module", "main": "dist/index.js", "scripts": { diff --git a/skills/odoo-data-ops/SKILL.md b/skills/odoo-data-ops/SKILL.md index c0cd0f8..6841464 100644 --- a/skills/odoo-data-ops/SKILL.md +++ b/skills/odoo-data-ops/SKILL.md @@ -61,8 +61,10 @@ If you make a mistake or are asked to "undo" a change: ### 8. Multi-Company Logic (Golden Rule) Odoo is a multi-company environment. By default, Brass-Monkey enables cross-company visibility, but you must be precise: -- **Visibility:** You can see records from all allowed companies (retrieved during `get_environment`). +- **Implicit Context Injection:** Brass-Monkey automatically appends `context: { 'allowed_company_ids': [...] }` containing all of your authorized companies to the headers of every single tool execution. You do NOT need to switch active companies to see all your records. +- **Visibility:** You can see records from all allowed companies (retrieved via `get_environment` and available programmatically in the root `active_context` object). - **Filtering:** To query data for a specific company, ALWAYS include `['company_id', '=', ID]` in your domain. +- **Write / Relational Integrity:** When creating or writing records, Odoo enforces strict relational isolation. Do not link records belonging to different companies (e.g., a Sales Order in Co A cannot point to a Warehouse in Co B). Always ensure a valid `company_id` is supplied in values and relational targets match that company. - **Operational Safety:** NEVER attempt to modify your own `res.users` record to change your active company. This is considered **System Vandalism**. - **Correct Pattern:** If you need to "switch" companies for a query, simply use the `company_id` domain filter. @@ -79,3 +81,16 @@ You are an AI agent with high-level access. You must NEVER: - Delete system-critical records to "clean up" your view. - Bypass Odoo's business logic by directly modifying internal state fields (e.g., `state`, `move_id`) if a specialized method is available. - **Enforcement:** Attempting these actions will trigger tool-level safety blocks and may result in session termination. + +## 11. Strict Tool Selection Protocol (Efficiency Mandate) + +To prevent severe token/API overhead and extreme latency, you must adhere to the following selection matrix. Attempting to bypass these tool constraints will trigger active runtime advice warnings and is a violation of project architecture standards. + +| Business Goal / Task | Mandatory Tool Selection | FORBIDDEN / High-Overhead Pattern | +| :--- | :--- | :--- | +| **Discover, list, or filter records** | `search_records` | Querying lists without limit/offset, or looping single-record searches. | +| **Inspect a single known record ID** | `get_record` (Depth mode) | **CRITICAL FAILURE:** Calling `search_records` with `limit: 1` or filtering on an exact ID. `get_record` resolves sub-lines, relational display names, and chatter threads in one call. | +| **Calculate sum, count, average, or groups** | `aggregate_records` | **CRITICAL FAILURE:** Querying large pages with `search_records` (e.g., `limit >= 100`) and computing metrics locally. Always delegate calculations to the Odoo database via server-side SQL grouping. | +| **Audit recent actions or changes** | `get_audit_log` | Iteratively parsing records or the `ir.logging` model manually. | +| **Fetch specific attachments/binaries** | `download_file` | Attempting to pull large binary buffers through general database queries. | + diff --git a/skills/odoo-get-started/SKILL.md b/skills/odoo-get-started/SKILL.md index 9d6d9cc..96ec6d9 100644 --- a/skills/odoo-get-started/SKILL.md +++ b/skills/odoo-get-started/SKILL.md @@ -13,10 +13,14 @@ This skill ensures the Gemini agent establishes a comprehensive "World Map" of t - **Enforcement:** Do NOT attempt `search_read`, `list_models`, or any business logic until `get_environment` has returned. - **Reasoning:** Odoo environments vary wildly by version, installed apps, and company structures. Starting "blind" leads to "hallucinated" field names and inefficient tool-chaining. -### 2. Interpreting the Environment +### 2. Interpreting the Environment & Company Context When you receive the `get_environment` response, you must internalize: - **Version:** Are you on Odoo v18 (current) or an older legacy version? (Methods and fields change between versions). - **User Context:** What is your name and login? (Used for audit trails). +- **Active Context (`active_context`):** + - **Programmatic Scope:** View the root `active_context` payload to see your `implicit_allowed_company_ids`. + - **Implicit Injection:** Brass-Monkey automatically injects these company IDs into every query. You have global read visibility across all of them by default. + - **Restricting Scope:** To target a single organization, do not alter user settings. Apply an explicit domain filter, e.g., `[['company_id', '=', ID]]`. - **Organization:** How many companies exist? Which one is active? What are the local currencies and languages? - **Apps Manifest:** (If `show_manifest: true`) What modules are installed? (e.g., if `crm` is not installed, do not attempt to search for leads). diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 180d7e5..2555fcc 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -20,7 +20,7 @@ import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Read package.json for metadata -let version = "1.5.1"; +let version = "1.6.0"; try { // Try both possible locations (source vs bundled) const pkgPaths = [ diff --git a/src/services/domain-validator.ts b/src/services/domain-validator.ts new file mode 100644 index 0000000..f4ebb53 --- /dev/null +++ b/src/services/domain-validator.ts @@ -0,0 +1,265 @@ +import { MetadataCache } from './metadata-cache.js'; +import { buildModelMetadata } from './metadata-resolver.js'; + +export interface ValidationResult { + success: boolean; + healedDomain?: any[]; + errorPayload?: { + isError: boolean; + message: string; + diagnostic_hints: { + invalid_field?: string; + invalid_operator?: string; + target_model: string; + did_you_mean_substrings?: string[]; + action_directives: string[]; + explanation?: string; + }; + }; +} + +export class DomainValidationService { + /** + * Main entry point to validate and potentially heal an Odoo domain array. + */ + public static async validateAndHeal( + client: any, + model: string, + domain: any[], + instanceAlias: string = 'default' + ): Promise { + // 1. Ensure Domain is an array + if (!Array.isArray(domain)) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Expected a 2D array of filters, but received type '${typeof domain}'.`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Always pass the domain as an array of triplet arrays: [[field, operator, value]].", + "Example: [['is_company', '=', true]]" + ], + explanation: "The domain must be a valid list." + } + } + }; + } + + // 2. Validate prefix operator grammar / Polish Notation structure + if (domain.length > 0) { + const grammarCheck = this.checkPrefixGrammar(domain); + if (!grammarCheck.valid) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Malformed Polish Notation operator structure. ${grammarCheck.error}`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Odoo prefix operators ('&' and '|') require exactly two operands following them.", + "If you have only one condition, do not include any operators. Just pass the single condition, e.g. [['is_company', '=', true]]." + ], + explanation: grammarCheck.error || '' + } + } + }; + } + } + + // 3. Hydrate Model Schema (Metadata Cache) + const cache = MetadataCache.getInstance(); + let metadata = cache.get(instanceAlias, model); + if (!metadata) { + try { + metadata = await buildModelMetadata(client, model, instanceAlias); + cache.set(instanceAlias, model, metadata); + } catch (err: any) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Target model '${model}' could not be resolved or does not exist.`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Verify that the model name is spelled correctly.", + "Execute list_models with a search_term to discover valid model names." + ] + } + } + }; + } + } + + // 4. Retrieve all valid fields from metadata + const validFields = new Set(); + if (metadata.categorized) { + Object.keys(metadata.categorized.base || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.extended || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.computed || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.related || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.relational || {}).forEach(f => validFields.add(f)); + Object.keys(metadata.categorized.lines || {}).forEach(f => validFields.add(f)); + } + validFields.add('id'); + validFields.add('create_date'); + validFields.add('write_date'); + validFields.add('create_uid'); + validFields.add('write_uid'); + + // Combine all fields to make checking easy + const allFieldDefinitions: Record = { + ...(metadata.categorized?.base || {}), + ...(metadata.categorized?.extended || {}), + ...(metadata.categorized?.computed || {}), + ...(metadata.categorized?.related || {}), + ...(metadata.categorized?.relational || {}), + ...(metadata.categorized?.lines || {}), + }; + + // 5. Run Triplet-level Validation & Hybrid Healing + const healedDomain: any[] = []; + const validOperators = new Set(['=', '!=', 'like', 'ilike', '=like', '=ilike', 'in', 'not in', 'child_of', '<', '>', '<=', '>=']); + + for (const element of domain) { + // If it is an operator string ('&', '|') or list item of operators + if (typeof element === 'string') { + const normalizedOp = element.toLowerCase(); + if (normalizedOp === 'and' || normalizedOp === '&') { + healedDomain.push('&'); + } else if (normalizedOp === 'or' || normalizedOp === '|') { + healedDomain.push('|'); + } else if (normalizedOp === 'not' || normalizedOp === '!') { + healedDomain.push('!'); + } else { + healedDomain.push(element); + } + continue; + } + + // If it's a condition triplet [field, operator, value] + if (Array.isArray(element)) { + if (element.length !== 3) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Invalid filter triplet structure. Expected exactly 3 elements: [field, operator, value], but received ${JSON.stringify(element)}.`, + diagnostic_hints: { + target_model: model, + action_directives: [ + "Ensure each filter is a flat array of exactly 3 elements, e.g., ['name', 'ilike', 'Matt']." + ] + } + } + }; + } + + const [field, operator, value] = element; + + // Check if field is valid + if (typeof field === 'string' && !validFields.has(field)) { + // Perform simple substring fuzzy match + const suggestions = Array.from(validFields).filter(vf => + field.toLowerCase().includes(vf.toLowerCase()) || + vf.toLowerCase().includes(field.toLowerCase()) + ); + + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Field '${field}' does not exist on model '${model}'.`, + diagnostic_hints: { + invalid_field: field, + target_model: model, + did_you_mean_substrings: suggestions.slice(0, 5), + action_directives: [ + `You MUST run 'inspect_model(model: "${model}")' to review the valid properties of this model.`, + "Check for typings: e.g., customers/companies are commonly represented under the field 'partner_id'." + ], + explanation: `The model '${model}' does not contain any field with the name '${field}'.` + } + } + }; + } + + // Validate Operator + if (!validOperators.has(operator)) { + return { + success: false, + errorPayload: { + isError: true, + message: `Domain Validation Error: Unsupported operator '${operator}' on field '${field}'.`, + diagnostic_hints: { + invalid_operator: operator, + target_model: model, + action_directives: [ + "Use only standard Odoo operators: '=', '!=', 'like', 'ilike', 'in', 'not in', '<', '>', '<=', '>=', 'child_of'." + ], + explanation: `Odoo's query engine does not recognize the operator '${operator}'. To search for substrings, use 'ilike'.` + } + } + }; + } + + // Hybrid Healing: Cast String "true"/"false" to Boolean + let healedValue = value; + const fieldDef = allFieldDefinitions[field]; + if (fieldDef && fieldDef.type === 'boolean' && typeof value === 'string') { + const valLower = value.toLowerCase(); + if (valLower === 'true' || valLower === '1') { + healedValue = true; + } else if (valLower === 'false' || valLower === '0') { + healedValue = false; + } + } + + healedDomain.push([field, operator, healedValue]); + } else { + healedDomain.push(element); + } + } + + return { + success: true, + healedDomain + }; + } + + /** + * Helper to perform arity/grammar checks on prefix domains. + */ + private static checkPrefixGrammar(domain: any[]): { valid: boolean; error?: string } { + let expected = 1; + for (const item of domain) { + if (typeof item === 'string') { + const op = item.toLowerCase(); + if (op === '&' || op === '|' || op === 'and' || op === 'or') { + expected += 1; + } else if (op === '!' || op === 'not') { + // Net +0 + } else { + return { valid: false, error: `Invalid string operator found in domain: '${item}'. Only '&', '|', and '!' are supported.` }; + } + } else if (Array.isArray(item)) { + expected -= 1; + } else { + return { valid: false, error: `Invalid item format inside domain array: ${JSON.stringify(item)}` }; + } + + if (expected < 0) { + return { valid: false, error: "Too many conditions found for the prefix operators supplied." }; + } + } + + if (expected !== 0) { + return { valid: false, error: `Unbalanced operators. Expected ${expected} more conditions to satisfy the prefix logical operators.` }; + } + + return { valid: true }; + } +} diff --git a/src/tools/get_environment.ts b/src/tools/get_environment.ts index 7ac0698..27f4248 100644 --- a/src/tools/get_environment.ts +++ b/src/tools/get_environment.ts @@ -115,7 +115,12 @@ export async function getEnvironment(manager: InstanceManager, input: GetEnviron return { summary, - environment: res + environment: res, + active_context: { + implicit_allowed_company_ids: user.company_ids, + visibility_scope: "GLOBAL_CROSS_COMPANY", + tip: "Brass-Monkey automatically injects 'allowed_company_ids' representing all your authorized companies into the context of every tool call. You have global read visibility. To filter for a specific company, always use an explicit domain filter, e.g., [['company_id', '=', ID]] or [['company_id', 'in', [ID1, ID2]]]." + } }; } diff --git a/src/tools/schemas.ts b/src/tools/schemas.ts index ae95fed..b9853a9 100644 --- a/src/tools/schemas.ts +++ b/src/tools/schemas.ts @@ -109,7 +109,7 @@ export const SEARCH_RECORDS_SCHEMA = { type: "object", properties: { model: { type: "string", description: 'Technical name of the model (e.g., "res.partner", "project.task").' }, - domain: { type: "array", items: {}, description: 'Odoo domain filter. A list of triplets: [["field", "operator", value]]. Example: [["is_company", "=", true]]. Use empty list [] for all records.' }, + domain: { type: "array", items: {}, description: 'Odoo domain filter. A list of triplets: [["field", "operator", value]]. Example: [["is_company", "=", true]]. MULTI-COMPANY TIP: By default, Brass-Monkey automatically injects allowed_company_ids so you see all authorized companies. To search within a specific company, always include [["company_id", "=", COMPANY_ID]].' }, fields: { type: "array", items: { type: "string" }, description: "Optional explicit list of field names to retrieve. If omitted, returns lightweight Breadth fields." }, limit: { type: "number", description: "Maximum number of records to return (defaults to 10)." }, offset: { type: "number", description: "Number of records to skip (for pagination, defaults to 0)." }, @@ -118,6 +118,7 @@ export const SEARCH_RECORDS_SCHEMA = { instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model"], + description: "Search for Odoo records. WARNING: DO NOT use this tool to inspect details of a single known record ID (you MUST use 'get_record' for 360-degree dashboards). DO NOT use this tool to sum, group, count, or average numeric fields (you MUST use 'aggregate_records' for server-side SQL-level summaries).", }; export const GET_RECORD_SCHEMA = { @@ -141,6 +142,7 @@ export const GET_RECORD_SCHEMA = { with_translations: { type: "boolean", description: "If True, translatable fields are returned in translation matrix." }, instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, + description: "MANDATORY for retrieving a comprehensive 360-degree dashboard report for a single Odoo record. Highly superior to search_records for single record lookups as it resolves sub-lines, relation display names, and chatter threads in one call. Use this immediately once you have a target record ID.", }; export const GET_RECORDS_SCHEMA = { @@ -179,6 +181,7 @@ export const AGGREGATE_RECORDS_SCHEMA = { instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model", "groupby"], + description: "Calculate database-level summaries, aggregations, counts, sums, or averages grouped by specified fields. MANDATORY: You MUST use this tool instead of querying lists of records with search_records and computing statistics locally.", }; export const GET_AUDIT_LOG_SCHEMA = { @@ -193,13 +196,13 @@ export const CREATE_RECORD_SCHEMA = { type: "object", properties: { model: { type: "string", description: 'Technical name of the model (e.g., "res.partner").' }, - values: { type: "object", description: "Dictionary of field values: {'field_name': value}. Use inspect_model to find writable fields." }, + values: { type: "object", description: "Dictionary of field values: {'field_name': value}. Use inspect_model to find writable fields. MULTI-COMPANY CRITICAL: Ensure you specify a valid 'company_id' in values that matches all relational fields (e.g. journals, accounts) to avoid Multi-Company Access/Validation Errors." }, justification: { type: "string", description: "MANDATORY: Explain WHY this record is being created. This is logged to Odoo Chatter and local audit logs." }, with_translations: { type: "boolean", description: "If True, translatable fields can be provided as strings (sync to all) or expanded lists." }, instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model", "values", "justification"], - description: "Create new records in a specified model with audit logging. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", + description: "Create new records in a specified model with audit logging. MULTI-COMPANY RULE: Ensure a correct 'company_id' is supplied in values. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", }; export const WRITE_RECORD_SCHEMA = { @@ -207,13 +210,13 @@ export const WRITE_RECORD_SCHEMA = { properties: { model: { type: "string", description: 'Technical name of the model (e.g., "res.partner").' }, id: { type: "number", description: "The database ID of the record to update." }, - values: { type: "object", description: "Dictionary of fields to update. PRO TIP: We take a 'Before Snapshot' automatically for reversibility." }, + values: { type: "object", description: "Dictionary of fields to update. PRO TIP: We take a 'Before Snapshot' automatically for reversibility. MULTI-COMPANY CRITICAL: Do not mix records from different companies. Ensure any relational values linked match the record's company_id." }, justification: { type: "string", description: "MANDATORY: Explain WHY this update is necessary. Logged for audit and safety." }, with_translations: { type: "boolean", description: "If True, translatable fields can be provided as strings (sync to all) or expanded lists." }, instance_alias: { type: "string", description: "Optional alias to use an instance other than the active one." }, }, required: ["model", "id", "values", "justification"], - description: "Update existing records with field-level tracking. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", + description: "Update existing records with field-level tracking. MULTI-COMPANY RULE: Verify relational safety across companies before writing. MANDATORY: Describe your intent and the specific values in the chat message BEFORE calling this tool to ensure the user can read it clearly during approval.", }; export const UNLINK_RECORD_SCHEMA = { diff --git a/src/tools/search_records.ts b/src/tools/search_records.ts index 4a33e42..0cfc77a 100644 --- a/src/tools/search_records.ts +++ b/src/tools/search_records.ts @@ -3,6 +3,7 @@ import { InstanceManager } from '../services/instance-manager.js'; import { MetadataCache } from '../services/metadata-cache.js'; import { buildModelMetadata } from '../services/metadata-resolver.js'; import { OdooOrchestrator } from '../services/odoo-orchestrator.js'; +import { DomainValidationService } from '../services/domain-validator.js'; /** * Zod schema for search_records tool input. @@ -45,6 +46,13 @@ export async function searchRecords(manager: InstanceManager, input: SearchRecor const client = await manager.getClient(instance_alias); const alias = instance_alias || 'default'; + // Validate and Heal Domain using decoupled DomainValidationService + const validationResult = await DomainValidationService.validateAndHeal(client, model, domain, alias); + if (!validationResult.success && validationResult.errorPayload) { + return validationResult.errorPayload; + } + const activeDomain = validationResult.healedDomain || domain; + let readFields = fields; // 1. Resolve and cache metadata if fields are not specified (Breadth Default) @@ -64,23 +72,23 @@ export async function searchRecords(manager: InstanceManager, input: SearchRecor const targetOffset = offset || 0; const [records, totalCount] = await Promise.all([ - client.executeKw(model, 'search_read', [domain], { + client.executeKw(model, 'search_read', [activeDomain], { fields: readFields, limit: targetLimit, offset: targetOffset, order, }), - client.executeKw(model, 'search_count', [domain]) + client.executeKw(model, 'search_count', [activeDomain]) ]); // Intent-Based Search Expansion: If zero results and domain has a name filter, retry with ilike let activeRecords = records; let activeTotalCount = totalCount; - if (activeRecords.length === 0 && domain.length > 0) { - const nameFilterIndex = domain.findIndex((d: any) => Array.isArray(d) && d[0] === 'name' && d[1] === '='); + if (activeRecords.length === 0 && activeDomain.length > 0) { + const nameFilterIndex = activeDomain.findIndex((d: any) => Array.isArray(d) && d[0] === 'name' && d[1] === '='); if (nameFilterIndex !== -1) { - const expandedDomain = [...domain]; - expandedDomain[nameFilterIndex] = ['name', 'ilike', domain[nameFilterIndex][2]]; + const expandedDomain = [...activeDomain]; + expandedDomain[nameFilterIndex] = ['name', 'ilike', activeDomain[nameFilterIndex][2]]; const [expandedRecords, expandedCount] = await Promise.all([ client.executeKw(model, 'search_read', [expandedDomain], { @@ -121,12 +129,25 @@ export async function searchRecords(manager: InstanceManager, input: SearchRecor } // 4. Construct high-signal Breadth Envelope + const advice: string[] = []; + if (targetLimit === 1 || activeRecords.length === 1) { + advice.push( + "SINGLE RECORD DETECTED: You retrieved a single record ID using search_records. This is highly inefficient. For single record lookups, you MUST use the 'get_record' tool, which provides a comprehensive 360-degree dashboard of sub-lines, relationships, display names, and chatter history in a single call." + ); + } + if (targetLimit >= 100) { + advice.push( + "POTENTIAL LOCAL CALCULATION LOOP: You retrieved 100 or more records. If you are calculating sums, averages, grouping, or counting records locally, this is extremely slow and overuses token/API limits. You MUST use 'aggregate_records' for server-side, high-performance database-level calculations instead." + ); + } + return { model, count: activeRecords.length, total_count: activeTotalCount, offset: targetOffset, limit: targetLimit, + ...(advice.length > 0 ? { optimization_advice: advice } : {}), leads: activeRecords.reduce((acc: Record, r: any) => { acc[String(r.id)] = r.display_name || r.name || `ID ${r.id}`; return acc; diff --git a/tests/crud-tools.test.ts b/tests/crud-tools.test.ts index b014d62..5cebc2b 100644 --- a/tests/crud-tools.test.ts +++ b/tests/crud-tools.test.ts @@ -4,6 +4,7 @@ import { createRecord } from '../src/tools/create_record.js'; import { writeRecord } from '../src/tools/write_record.js'; import { unlinkRecord } from '../src/tools/unlink_record.js'; import { aggregateRecords } from '../src/tools/aggregate_records.js'; +import { MetadataCache } from '../src/services/metadata-cache.js'; describe('CRUD Tools', () => { let mockClient: any; @@ -24,6 +25,29 @@ describe('CRUD Tools', () => { getClient: vi.fn().mockResolvedValue(mockClient), getAudit: vi.fn().mockResolvedValue(mockAudit), }; + + // Pre-seed MetadataCache to avoid any background RPC metadata calls in validateAndHeal + const cache = MetadataCache.getInstance(); + cache.clear(); + cache.set('default', 'res.partner', { + baseModule: 'base', + id: 10, + name: 'Partner', + transient: false, + modules: 'base', + baseFields: ['id', 'name', 'write_date'], + categorized: { + base: { + name: { type: 'char', string: 'Name' }, + write_date: { type: 'datetime', string: 'Modified' } + }, + extended: {}, + computed: {}, + related: {}, + relational: {}, + lines: {} + } + }); }); describe('searchRecords', () => { @@ -45,12 +69,18 @@ describe('CRUD Tools', () => { total_count: 1, offset: 0, limit: 10, + optimization_advice: [ + "SINGLE RECORD DETECTED: You retrieved a single record ID using search_records. This is highly inefficient. For single record lookups, you MUST use the 'get_record' tool, which provides a comprehensive 360-degree dashboard of sub-lines, relationships, display names, and chatter history in a single call." + ], leads: { '1': 'Test' }, results: [{ id: 1, name: 'Test', write_date: '2026-05-28 12:00:00' }] }); }); it('should handle background cache warming when fields is empty', async () => { + // Clear the MetadataCache to force live metadata discovery + MetadataCache.getInstance().clear(); + mockClient.executeKw .mockResolvedValueOnce([{ id: 10, name: 'Partner', modules: 'base', transient: false }]) // buildModelMetadata (ir.model) .mockResolvedValueOnce([{ module: 'base' }]) // buildModelMetadata (ir.model.data) diff --git a/tests/search_records_orchestration.test.ts b/tests/search_records_orchestration.test.ts index e7f2dea..ce0a7eb 100644 --- a/tests/search_records_orchestration.test.ts +++ b/tests/search_records_orchestration.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { searchRecords } from '../src/tools/search_records.js'; +import { MetadataCache } from '../src/services/metadata-cache.js'; describe('searchRecords Orchestration', () => { let mockClient: any; @@ -12,6 +13,49 @@ describe('searchRecords Orchestration', () => { mockManager = { getClient: vi.fn().mockResolvedValue(mockClient), }; + + // Pre-seed MetadataCache to bypass RPC validation metadata discovery + const cache = MetadataCache.getInstance(); + cache.clear(); + cache.set('default', 'res.partner', { + baseModule: 'base', + id: 10, + name: 'Partner', + transient: false, + modules: 'base', + baseFields: ['id', 'name', 'write_date'], + categorized: { + base: { + name: { type: 'char', string: 'Name' }, + write_date: { type: 'datetime', string: 'Modified' } + }, + extended: {}, + computed: {}, + related: {}, + relational: {}, + lines: {} + } + }); + cache.set('default', 'sale.order', { + baseModule: 'sale', + id: 11, + name: 'Sales Order', + transient: false, + modules: 'sale', + baseFields: ['id', 'name', 'state', 'write_date'], + categorized: { + base: { + name: { type: 'char', string: 'Order Reference' }, + state: { type: 'selection', string: 'Status' }, + write_date: { type: 'datetime', string: 'Modified' } + }, + extended: {}, + computed: {}, + related: {}, + relational: {}, + lines: {} + } + }); }); it('should construct high-signal pagination envelopes and handle parallel count', async () => {