Consolidate Odysseus agent harness and tool contracts

This commit is contained in:
pewdiepie-archdaemon
2026-09-17 10:07:40 +00:00
parent 84aa9a91de
commit 218d762427
229 changed files with 28899 additions and 1551 deletions
+90 -17
View File
@@ -17,7 +17,7 @@ from src.upload_handler import reserve_upload_references
logger = logging.getLogger(__name__)
async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
async def do_manage_calendar(content: str, owner: Optional[str] = None, *, import_event_uid: Optional[str] = None) -> Dict:
"""Handle manage_calendar tool calls: list/create/update/delete calendar events (local SQLite)."""
from core.database import SessionLocal, CalendarCal, CalendarEvent, Note
from routes.calendar_routes import (
@@ -102,6 +102,22 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
q = q.filter(CalendarCal.owner == owner)
return q
def _event_uid_candidates(raw_uid):
"""Yield exact UID first, then unambiguous UI-anchor spellings.
Calendar results render links as ``#event-<uid>``. Models sometimes
copy that href (or drop only the leading ``#``) into the UID field.
Preserve real UIDs beginning with ``event-`` by trying the exact value
first and using the stripped form only as a not-found fallback.
"""
text = str(raw_uid or "").strip()
candidates = [text]
if text.startswith("#event-"):
candidates.append(text[len("#event-"):])
elif text.startswith("event-"):
candidates.append(text[len("event-"):])
return [item for index, item in enumerate(candidates) if item and item not in candidates[:index]]
def _first_present_arg(raw_args, *names: str):
for name in names:
if name in raw_args and raw_args.get(name) is not None:
@@ -273,11 +289,11 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
"exit_code": 1,
}
if start_raw:
start_dt = _parse_dt(start_raw)
start_dt, _ = _parse_event_dt(start_raw)
else:
start_dt = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
if end_raw:
end_dt = _parse_dt(end_raw)
end_dt, _ = _parse_event_dt(end_raw)
else:
end_dt = start_dt + timedelta(days=14)
except ValueError as e:
@@ -421,13 +437,41 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
existing = (
_event_query()
.filter(
CalendarEvent.dtstart == dtstart,
CalendarEvent.status != "cancelled",
_func.lower(CalendarEvent.summary) == summary.lower(),
*([CalendarEvent.uid == import_event_uid] if import_event_uid else [
CalendarEvent.dtstart == dtstart,
CalendarEvent.status != "cancelled",
_func.lower(CalendarEvent.summary) == summary.lower(),
]),
)
.first()
)
if existing is not None:
# Repair older email-imported events whose model-generated
# location was an unrelated map URL. A concrete meeting URL
# is stronger evidence than the existing free-text location.
incoming_location = str(args.get("location") or "").strip()
changed = False
if incoming_location and re.match(
r"^https?://(?:teams\.microsoft\.com|(?:[a-z0-9-]+\.)?zoom\.us|meet\.google\.com|(?:[a-z0-9-]+\.)?webex\.com|meet\.jit\.si)/",
incoming_location,
re.IGNORECASE,
) and (
not str(existing.location or "").strip()
or not re.match(
r"^https?://(?:teams\.microsoft\.com|(?:[a-z0-9-]+\.)?zoom\.us|meet\.google\.com|(?:[a-z0-9-]+\.)?webex\.com|meet\.jit\.si)/",
str(existing.location or "").strip(),
re.IGNORECASE,
)
):
existing.location = incoming_location
changed = True
for field in ("source_email_uid", "source_email_folder", "source_email_account_id", "source_email_message_id"):
incoming = str(args.get(field) or "").strip()
if incoming and not getattr(existing, field, None):
setattr(existing, field, incoming)
changed = True
if changed:
db.commit()
reminder_note_id = None
reminder_skipped_reason = None
minutes_before = _reminder_minutes(args)
@@ -486,7 +530,7 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
"exit_code": 1,
}
uid = str(_uuid.uuid4())
uid = import_event_uid or str(_uuid.uuid4())
ev = CalendarEvent(
uid=uid, calendar_id=cal.id, summary=summary,
description=event_description,
@@ -496,6 +540,10 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
rrule=args.get("rrule", "") or "",
event_type=event_type,
importance=importance,
source_email_uid=str(args.get("source_email_uid") or "").strip() or None,
source_email_folder=str(args.get("source_email_folder") or "").strip() or None,
source_email_account_id=str(args.get("source_email_account_id") or "").strip() or None,
source_email_message_id=str(args.get("source_email_message_id") or "").strip() or None,
caldav_sync_pending="create" if cal.source == "caldav" else None,
)
db.add(ev)
@@ -545,11 +593,17 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
uid = args.get("summary")
if not uid:
return {"error": "uid is required", "exit_code": 1}
try:
base_uid = _resolve_base_uid(uid)
except ValueError as e:
return {"error": str(e), "exit_code": 1}
ev = _event_query().filter(CalendarEvent.uid == base_uid).first()
ev = None
base_uid = ""
for candidate_uid in _event_uid_candidates(uid):
try:
candidate_base_uid = _resolve_base_uid(candidate_uid)
except ValueError:
continue
ev = _event_query().filter(CalendarEvent.uid == candidate_base_uid).first()
if ev:
base_uid = candidate_base_uid
break
if not ev:
title_matches = _event_query().filter(
CalendarEvent.summary == str(uid).strip()
@@ -581,6 +635,8 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
ev.description = args["description"]
if args.get("location") is not None:
ev.location = args["location"]
previous_dtstart = ev.dtstart
previous_dtend = ev.dtend
if args.get("dtstart") is not None:
# Anchor naive/natural-language input to the USER's timezone and
# refresh is_utc, exactly like create_event. Parsing with the
@@ -594,6 +650,13 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
ev.all_day = False
ev.dtstart, _su = _parse_event_dt(args["dtstart"])
ev.is_utc = bool(_su and not _eff_all_day)
if (
args.get("dtend") is None
and previous_dtstart is not None
and previous_dtend is not None
and previous_dtend > previous_dtstart
):
ev.dtend = ev.dtstart + (previous_dtend - previous_dtstart)
if args.get("dtend") is not None:
ev.dtend, _eu = _parse_event_dt(args["dtend"])
if args.get("all_day") is None and bool(ev.all_day) and _looks_like_timed_dt(args["dtend"]):
@@ -607,6 +670,10 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
ev.event_type = _tag or None
if args.get("importance") is not None:
ev.importance = args["importance"]
for field in ("source_email_uid", "source_email_folder", "source_email_account_id", "source_email_message_id"):
incoming = str(args.get(field) or "").strip()
if incoming:
setattr(ev, field, incoming)
if args.get("rrule") is not None:
ev.rrule = args.get("rrule") or ""
elif str(args.get("repeat") or "").strip().lower() in {"none", "no", "off", "false", "single"}:
@@ -672,11 +739,17 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
return {"error": "Multiple events have that exact title; uid is required", "exit_code": 1}
if not uid:
return {"error": "uid or exact summary is required", "exit_code": 1}
try:
base_uid = _resolve_base_uid(uid)
except ValueError as e:
return {"error": str(e), "exit_code": 1}
ev = _event_query().filter(CalendarEvent.uid == base_uid).first()
ev = None
base_uid = ""
for candidate_uid in _event_uid_candidates(uid):
try:
candidate_base_uid = _resolve_base_uid(candidate_uid)
except ValueError:
continue
ev = _event_query().filter(CalendarEvent.uid == candidate_base_uid).first()
if ev:
base_uid = candidate_base_uid
break
if not ev:
return {"error": f"Event {uid} not found", "exit_code": 1}
is_caldav = ev.calendar and ev.calendar.source == "caldav" and ev.remote_href
+23 -1
View File
@@ -1829,7 +1829,11 @@ async def do_list_cached_models(content: str, owner: Optional[str] = None) -> Di
resp.raise_for_status()
data = resp.json()
if isinstance(data, dict) and data.get('error'):
raise ValueError('cache endpoint reported an error')
scan_errors.append({
'host': host_label or 'local',
'reason': str(data.get('error'))[:500],
})
return []
ms = data.get("models", []) if isinstance(data, dict) else (data or [])
for m in ms:
m["host"] = host_label or "local"
@@ -1885,7 +1889,25 @@ async def do_list_cached_models(content: str, owner: Optional[str] = None) -> Di
and (s.get("name") == raw_host or s.get("host") == host or s.get("host") == raw_host)),
{},
)
error_start = len(scan_errors)
models = await _scan_one(raw_host, host, model_dir=_dirs_for(srv))
# Friendly Cookbook names commonly double as SSH aliases. If a
# saved LAN address goes stale after a reboot/network change,
# retry the validated alias before declaring the server offline.
# This is read-only and never mutates the saved configuration.
if not models and len(scan_errors) > error_start and host != raw_host:
try:
alias = validate_remote_host(raw_host)
except Exception:
alias = None
if alias:
configured_errors = scan_errors[error_start:]
del scan_errors[error_start:]
models = await _scan_one(
raw_host, alias, model_dir=_dirs_for(srv),
)
if not models:
scan_errors[error_start:error_start] = configured_errors
else:
# Always include local. Local's saved record is the one with no host.
local_srv = next((s for s in servers if isinstance(s, dict) and not (s.get("host") or "").strip()), {})
+17 -6
View File
@@ -16,6 +16,20 @@ from src.upload_handler import reserve_upload_references
logger = logging.getLogger(__name__)
def _search_tokens(value: str) -> list[str]:
"""Normalize lightweight singular/plural variants without fuzzy matching."""
tokens = []
for token in re.findall(r"[a-z0-9]+", str(value or "").lower()):
if token in {"the", "a", "an", "note", "notes", "checklist", "list", "todo", "todos"}:
continue
if len(token) > 4 and token.endswith("ies"):
token = token[:-3] + "y"
elif len(token) > 3 and token.endswith("s") and not token.endswith("ss"):
token = token[:-1]
tokens.append(token)
return tokens
async def do_manage_notes(content: str, owner: Optional[str] = None) -> Dict:
"""Handle manage_notes tool calls: CRUD on notes and checklists."""
import uuid as _uuid
@@ -206,19 +220,16 @@ async def do_manage_notes(content: str, owner: Optional[str] = None) -> Dict:
or ""
).strip().lower()
if query:
query_terms = [
term
for term in re.findall(r"[a-z0-9]+", query)
if term not in {"the", "a", "an", "note", "notes", "checklist", "list", "todo", "todos"}
]
query_terms = _search_tokens(query)
filtered = []
for n in notes:
haystack = " ".join(
str(part or "")
for part in (n.title, n.content, n.label, n.items)
).lower()
haystack_terms = set(_search_tokens(haystack))
if query in haystack or (
query_terms and all(term in haystack for term in query_terms)
query_terms and all(term in haystack_terms for term in query_terms)
):
filtered.append(n)
notes = filtered
+9 -2
View File
@@ -8,6 +8,7 @@ tools.
tool_implementations.py and are pulled back function-locally where needed.
"""
import re
from datetime import datetime, timezone
from typing import Any, Dict, Optional
from src.constants import DEEP_RESEARCH_DIR
@@ -92,9 +93,15 @@ async def do_manage_research(content: str, owner: Optional[str] = None) -> Dict:
# the `research-` UI prefix, while action=read expects the underlying file
# stem. Exposing the exact id prevents agents from guessing or retrying
# alternate spellings after a list call.
def _completed_label(value):
try:
return datetime.fromtimestamp(float(value), timezone.utc).isoformat().replace('+00:00', 'Z')
except (TypeError, ValueError, OSError):
return 'completion time unavailable'
rows = "\n".join(
f"- [{q or '(untitled)'}](#research-{sid}) — id: {sid} — {n} sources"
for _, sid, q, n in items[:50]
f"- [{q or '(untitled)'}](#research-{sid}) — id: {sid} — completed {_completed_label(completed)} — {n} sources"
for completed, sid, q, n in items[:50]
)
return {"output": f"Research library ({len(items)} item{'s' if len(items) != 1 else ''}):\n{rows}", "exit_code": 0}