mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-12 12:37:32 +00:00
fix(email): close remaining email-tool registry drift; classify every email tool for plan mode
Deep self-review follow-up on #3681. Three review rounds each found another hand-maintained copy of the email tool list that had drifted; this commit hunts down ALL remaining copies and pins them to BUILTIN_EMAIL_TOOLS. The same 5 tools (search_emails, draft_email, draft_email_reply, ai_draft_email_reply, download_attachment) were missing from every advertising surface, so they were dispatchable but never offered: - FUNCTION_TOOL_SCHEMAS: native function-calling models never saw them (the round-1 fix covered dispatch only); schemas added, mirroring the email server's inputSchema definitions. - TOOL_SECTIONS: fenced-block models were never told about them; prompt sections added. - tool_index: absent from the RAG embedding registry (never retrievable), the email keyword hints, and the scheduled assistant's always-available set — the latter two now derive from BUILTIN_EMAIL_TOOLS. - agent_loop._DOMAIN_TOOL_MAP["email"], tool_policy._COMMON_TOOL_NAMES, the assistant tool-selector UI groups (assistant.js), and the default Assistant crew seed (task_scheduler) now derive from / cover the set. Plan mode now classifies every email tool explicitly: - list_email_accounts and search_emails join PLAN_MODE_READONLY_TOOLS. Without this, list_email_accounts sat in the plan-mode bare denylist (schema-derived) while its qualified form passed the MCP read-only filter — and the round-2 bare/qualified alias gate would have blocked the qualified call too, regressing read-only email discovery in plan mode. - draft_email, draft_email_reply, ai_draft_email_reply, and download_attachment join the fail-closed mutator backstop (drafts create documents; download_attachment writes to disk). Tests: tests/test_email_registry_sync.py pins every registry (including the email server source and assistant.js) to BUILTIN_EMAIL_TOOLS and asserts the plan-mode partition, so the next email tool can't drift; a parse/strip mirror grid covers 192 fence shapes (tag x header x body) asserting executed <=> stripped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -136,3 +136,31 @@ def test_markdown_info_string_fence_is_left_intact_in_display():
|
||||
# so not stripped from the displayed text.
|
||||
text = 'Example:\n```python title="example.py"\nprint("hi")\n```'
|
||||
assert strip_tool_blocks(text) == text
|
||||
|
||||
|
||||
def test_parse_strip_mirror_across_fence_shape_grid():
|
||||
# Invariant for ANY single fence: either it executes AND is stripped, or
|
||||
# it doesn't execute AND stays fully visible. The one allowed exception is
|
||||
# an empty tool-shaped fence (no header, no body): never executed, but
|
||||
# stripped as noise — pre-PR behavior, kept deliberately.
|
||||
from src.agent_tools import TOOL_TAGS
|
||||
|
||||
tags = ["bash", "python", "list_emails", "bulk_email", "manage_memory",
|
||||
"python3", "bash-session", "notatool"]
|
||||
headers = ["", " ", ' title="x"', ' {title="x"}', ' {"a": 1}', " [1, 2]",
|
||||
" {bad json", ' {"a": 1} extra']
|
||||
bodies = ["", "content line\n", '{"k": "v"}\n']
|
||||
|
||||
for tag in tags:
|
||||
for header in headers:
|
||||
for body in bodies:
|
||||
text = f"before\n```{tag}{header}\n{body}```\nafter"
|
||||
blocks = parse_tool_blocks(text)
|
||||
stripped = strip_tool_blocks(text)
|
||||
case = (tag, header, body)
|
||||
if blocks:
|
||||
assert stripped == "before\n\nafter", case
|
||||
elif stripped != text:
|
||||
assert (
|
||||
tag in TOOL_TAGS and not header.strip() and not body.strip()
|
||||
), f"non-executed fence was stripped: {case}"
|
||||
|
||||
Reference in New Issue
Block a user