Fix dup approval flow: preserve raw message, force expenses routing, fix HTML rendering

- master_agent: thread raw user message into extra_context and peer_data so
  expenses_agent can check it directly without relying on LLM intent_summary
- master_agent: when receipts are in extra_context always route to expenses_agent,
  so replies like 'skip duplicates' still trigger expense processing
- expenses_agent: _plan() checks peer_data raw_message alongside task so
  skip/keep keywords are detected even when master rewrites the intent
- ab_ai_mail: wrap clarification message HTML in Markup() so Odoo does not
  re-escape the tags; use <br> instead of <br/>
- ab_ai_mail: convert agent plain-text replies newlines to <br> for proper
  line-break rendering in Discuss

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carlos Garcia
2026-05-16 11:55:46 -04:00
parent 462f63d11d
commit 9e3fe974dc
3 changed files with 51 additions and 25 deletions

View File

@@ -58,12 +58,20 @@ class ExpensesAgent(BaseAgent):
task = (self._directive.task if self._directive else '').lower()
receipts = getattr(self._directive.context, 'receipts', []) if self._directive else []
# The master LLM rewrites the user message into intent_summary (task).
# Also check the original raw_message threaded through peer_data so
# short replies like "skip duplicates" are detected even when rewritten.
raw_msg = ''
if self._directive and self._directive.context:
raw_msg = (self._directive.context.peer_data.get('raw_message') or '').lower()
combined = task + ' ' + raw_msg
# Detect whether the user is responding to a duplicate-approval request
skip_keywords = ('skip', 'yes', 'remove duplicate', 'exclude duplicate', 'drop duplicate')
keep_keywords = ('keep all', 'keep both', 'include all', 'no skip', "don't skip")
if any(k in task for k in skip_keywords):
if any(k in combined for k in skip_keywords):
user_dup_decision = 'skip'
elif any(k in task for k in keep_keywords):
elif any(k in combined for k in keep_keywords):
user_dup_decision = 'keep_all'
else:
user_dup_decision = 'none' # first time through — will ask if dups found