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

@@ -101,6 +101,11 @@ class MasterAgent:
await self._log_directive_complete(directive_id, 'complete', response_text)
return MasterResponse(directive_id=directive_id, response=response_text,
status='complete')
# When receipts are present (upload flow), always dispatch expenses_agent
# even if the user's message is a one-word reply like "skip duplicates".
if (extra_context or {}).get('receipts') and 'expenses_agent' not in intent.agents:
intent.agents.append('expenses_agent')
intent.needs_clarification = False
access = await self._check_access(user_id, intent.agents)
if not access.allowed:
denied = ', '.join(access.denied_agents)
@@ -108,6 +113,10 @@ class MasterAgent:
await self._memory.append_message(user_id, 'assistant', msg, directive_id)
await self._log_directive_complete(directive_id, 'failed', msg)
return MasterResponse(directive_id=directive_id, response=msg, status='failed')
# Thread the raw user message into extra_context so agents can
# check it directly without relying on the LLM's intent_summary.
extra_context = dict(extra_context or {})
extra_context.setdefault('raw_message', message or '')
directives = await self._build_directives(intent, context, directive_id,
user_id=user_id, extra_context=extra_context)
reports = await self._dispatch_agents(directives)
@@ -221,7 +230,10 @@ class MasterAgent:
recent_findings=context.operational_findings,
conversation_summary=chr(10).join(
m['content'] for m in context.conversation[-5:] if m['role'] == 'assistant'),
peer_data={'requesting_user_id': user_id},
peer_data={
'requesting_user_id': user_id,
'raw_message': (extra_context or {}).get('raw_message', ''),
},
receipts=receipts)
d = AgentDirective(
directive_id=directive_id, agent=agent_key, task=intent.intent_summary,