mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-12 12:37:32 +00:00
Merge remote-tracking branch 'origin/dev'
# Conflicts: # routes/document_routes.py
This commit is contained in:
@@ -14,6 +14,7 @@ Sub-modules:
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
|
||||
from src.tool_security import BUILTIN_EMAIL_TOOLS
|
||||
from src.tool_utils import _truncate, get_mcp_manager, set_mcp_manager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -86,9 +87,10 @@ TOOL_TAGS = {"bash", "python", "web_search", "web_fetch", "read_file", "write_fi
|
||||
"manage_endpoints", "manage_mcp", "manage_webhooks",
|
||||
"manage_tokens", "manage_documents", "manage_settings",
|
||||
"manage_notes", "manage_calendar",
|
||||
"resolve_contact", "manage_contact", "list_email_accounts", "send_email", "list_emails",
|
||||
"read_email", "reply_to_email", "bulk_email", "archive_email",
|
||||
"delete_email", "mark_email_read",
|
||||
"resolve_contact", "manage_contact",
|
||||
# Email tool names come from BUILTIN_EMAIL_TOOLS (unioned below)
|
||||
# so the fence regex, dispatch, and non-admin blocklist all cover
|
||||
# the same set.
|
||||
# Cookbook tools (LLM serving + downloads). Without these
|
||||
# entries, native function calls to e.g. list_served_models
|
||||
# are rejected as "Unknown function call" before reaching
|
||||
@@ -105,7 +107,7 @@ TOOL_TAGS = {"bash", "python", "web_search", "web_fetch", "read_file", "write_fi
|
||||
# Generic loopback to any UI-button endpoint (cookbook,
|
||||
# gallery, email folders, etc.) — agent uses this when
|
||||
# there's no named tool wrapper for the action.
|
||||
"app_api"}
|
||||
"app_api"} | BUILTIN_EMAIL_TOOLS
|
||||
|
||||
ToolBlock = namedtuple("ToolBlock", ["tool_type", "content"])
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import logging
|
||||
from typing import Optional, Dict
|
||||
|
||||
from src.tool_utils import get_mcp_manager, _parse_tool_args
|
||||
from src.tool_security import BUILTIN_EMAIL_TOOLS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -706,7 +707,14 @@ async def do_manage_settings(content: str, owner: Optional[str] = None) -> Dict:
|
||||
"tasks": ["manage_tasks"],
|
||||
"notes": ["manage_notes"],
|
||||
"calendar": ["manage_calendar"],
|
||||
"email": ["mcp__email__list_emails", "mcp__email__read_email", "mcp__email__send_email"],
|
||||
# The full built-in email tool set, in BOTH spellings: the
|
||||
# qualified mcp__email__* names drive MCP schema hiding, the
|
||||
# bare names drive function-schema hiding, and the runtime
|
||||
# gate accepts either — deriving from BUILTIN_EMAIL_TOOLS
|
||||
# keeps the toggle covering every tool the email server
|
||||
# exposes instead of a hand-picked subset.
|
||||
"email": sorted(BUILTIN_EMAIL_TOOLS)
|
||||
+ [f"mcp__email__{t}" for t in sorted(BUILTIN_EMAIL_TOOLS)],
|
||||
"research": ["web_search", "web_fetch"], # research is a per-request flag, not a tool (closest analog)
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,21 @@ class WriteFileTool:
|
||||
lines = content.split("\n", 1)
|
||||
raw_path = lines[0].strip()
|
||||
body = lines[1] if len(lines) > 1 else ""
|
||||
# Decode JSON-object args (the fenced inline-args shape
|
||||
# ```write_file {"path": "...", "content": "..."}```), matching
|
||||
# ReadFileTool above. Without this the whole JSON string becomes the
|
||||
# path and the file is written under a garbage name. This is the live
|
||||
# path: there is no filesystem MCP server, so write_file always runs
|
||||
# here via _direct_fallback, not through _build_mcp_args.
|
||||
_stripped = content.strip()
|
||||
if _stripped.startswith("{"):
|
||||
try:
|
||||
_a = json.loads(_stripped)
|
||||
if isinstance(_a, dict) and "path" in _a:
|
||||
raw_path = str(_a.get("path", "")).strip()
|
||||
body = str(_a.get("content", ""))
|
||||
except (json.JSONDecodeError, TypeError, ValueError):
|
||||
pass
|
||||
try:
|
||||
path = _resolve_tool_path(raw_path)
|
||||
except ValueError as e:
|
||||
@@ -288,11 +303,26 @@ class GlobTool:
|
||||
base = os.path.abspath(root)
|
||||
if not os.path.isdir(base):
|
||||
return None, f"glob: {root}: not a directory"
|
||||
rbase = os.path.realpath(base)
|
||||
norm_pat = pattern.replace("\\", "/")
|
||||
# Fast path: literal pattern (no wildcards) → direct path lookup.
|
||||
if not any(c in norm_pat for c in "*?["):
|
||||
cand = os.path.normpath(os.path.join(base, norm_pat))
|
||||
if os.path.exists(cand):
|
||||
cand = os.path.realpath(os.path.join(base, norm_pat))
|
||||
# Keep the literal lookup inside the search root. os.path.join
|
||||
# lets an absolute pattern (or one containing ../) escape `base`,
|
||||
# which would turn glob into an existence/path oracle for
|
||||
# arbitrary host files — bypassing the workspace/allowlist
|
||||
# confinement that _resolve_search_root applies to the root.
|
||||
# An escaping literal falls through to the walk, which only ever
|
||||
# yields paths under base.
|
||||
nbase = os.path.normcase(rbase)
|
||||
try:
|
||||
inside = cand == rbase or os.path.commonpath(
|
||||
[os.path.normcase(cand), nbase]
|
||||
) == nbase
|
||||
except ValueError:
|
||||
inside = False
|
||||
if inside and os.path.exists(cand):
|
||||
return [cand], None
|
||||
# Literal not at exact path — fall through to walk so
|
||||
# e.g. "foo.py" still matches at any depth (like rglob).
|
||||
@@ -333,7 +363,13 @@ class GlobTool:
|
||||
|
||||
class GrepTool:
|
||||
async def execute(self, content: str, ctx: dict) -> dict:
|
||||
from src.tool_execution import _resolve_tool_path, _resolve_search_root, _truncate
|
||||
from src.tool_execution import (
|
||||
_SENSITIVE_FILE_PATTERNS,
|
||||
_is_sensitive_path,
|
||||
_resolve_tool_path,
|
||||
_resolve_search_root,
|
||||
_truncate,
|
||||
)
|
||||
args: Dict[str, Any] = {}
|
||||
_s = (content or "").strip()
|
||||
if _s.startswith("{"):
|
||||
@@ -369,6 +405,8 @@ class GrepTool:
|
||||
cmd.append("--ignore-case")
|
||||
if glob_pat:
|
||||
cmd += ["--glob", glob_pat]
|
||||
for _pat in _SENSITIVE_FILE_PATTERNS:
|
||||
cmd += ["--glob", f"!*{_pat}*"]
|
||||
for _d in _CODENAV_SKIP_DIRS:
|
||||
cmd += ["--glob", f"!**/{_d}/**"]
|
||||
cmd += ["--regexp", pattern, root]
|
||||
@@ -399,6 +437,8 @@ class GrepTool:
|
||||
for fp in file_iter:
|
||||
if len(hits) >= max_hits:
|
||||
break
|
||||
if _is_sensitive_path(os.path.realpath(fp)):
|
||||
continue
|
||||
try:
|
||||
with open(fp, "r", encoding="utf-8", errors="strict") as f:
|
||||
for i, line in enumerate(f, 1):
|
||||
|
||||
Reference in New Issue
Block a user