Preserve preview harness, editor, email and task improvements

Snapshot current maintainer-preview application changes and regression fixtures for integration into lab. Excludes local runtime data, evaluation outputs and source backups. Focused Python regression selection: 140 passed; full suite not certified.
This commit is contained in:
pewdiepie-archdaemon
2026-10-01 01:34:26 +00:00
parent 86f376ac3a
commit 2e8413a54a
170 changed files with 11010 additions and 1094 deletions
+87 -12
View File
@@ -7,7 +7,8 @@ Holds the manage_calendar tool (CalDAV-backed event CRUD).
import json
import logging
import re
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from typing import Dict, Optional
from src.tools._common import _parse_tool_args
@@ -17,6 +18,80 @@ from src.upload_handler import reserve_upload_references
logger = logging.getLogger(__name__)
def _normalize_local_event_times(args: dict) -> dict:
args = dict(args)
for field, target in (('local_start', 'dtstart'), ('local_end', 'dtend')):
if field not in args:
continue
value = args[field]
if not isinstance(value, dict):
raise ValueError(f'{field} must contain date and time fields')
day, clock = value.get('date'), value.get('time')
if not isinstance(day, str) or not re.fullmatch(r'\d{4}-\d{2}-\d{2}', day):
raise ValueError(f'{field}.date must be YYYY-MM-DD')
if args.get('all_day') is True:
if clock:
raise ValueError(f'Omit {field}.time for an all-day event')
normalized = day
else:
if not isinstance(clock, str) or not re.fullmatch(r'\d{2}:\d{2}(?::\d{2})?', clock):
raise ValueError(f'{field}.time must be HH:MM or HH:MM:SS; put its zone in timezone')
normalized = day + 'T' + clock
parsed = datetime.fromisoformat(normalized)
if target in args and datetime.fromisoformat(str(args[target])) != parsed:
raise ValueError(f'Conflicting {field} and {target}; use only one representation')
args[target] = normalized
return args
def _saved_event_times(event) -> dict:
"""Report persisted timestamps, not the model's unnormalized input."""
def serialize(value):
if value is None:
return None
if event.all_day:
return value.date().isoformat()
return value.isoformat() + ('Z' if event.is_utc else '')
return {
'dtstart': serialize(event.dtstart),
'dtend': serialize(event.dtend),
'all_day': bool(event.all_day),
'is_utc': bool(event.is_utc),
}
def _explicit_calendar_time(raw: str, zone_name: str) -> tuple[datetime, bool]:
"""Convert a stated wall time without relying on the browser timezone."""
zone_name = str(zone_name).strip()
offset = re.fullmatch(r'(?:UTC|GMT)?([+-])(\d{2}):(\d{2})', zone_name, re.I)
if zone_name.upper() in {'UTC', 'GMT', 'Z'}:
zone = timezone.utc
elif offset:
hours, minutes = int(offset[2]), int(offset[3])
if hours > 23 or minutes > 59:
raise ValueError('Invalid timezone offset')
zone = timezone(timedelta(minutes=(hours * 60 + minutes) * (1 if offset[1] == '+' else -1)))
else:
try:
zone = ZoneInfo(zone_name)
except (ZoneInfoNotFoundError, ValueError) as exc:
raise ValueError('timezone must be UTC, a signed HH:MM offset, or an IANA zone') from exc
value = datetime.fromisoformat(str(raw).replace('Z', '+00:00'))
if value.tzinfo is not None:
if value.utcoffset() != value.astimezone(zone).utcoffset():
raise ValueError('Timestamp offset conflicts with timezone; preserve the stated wall time and zone')
return value.astimezone(timezone.utc).replace(tzinfo=None), True
candidates = set()
for fold in (0, 1):
instant = value.replace(tzinfo=zone, fold=fold).astimezone(timezone.utc)
if instant.astimezone(zone).replace(tzinfo=None) == value:
candidates.add(instant)
if len(candidates) != 1:
raise ValueError('Local time is ambiguous or nonexistent due to daylight saving; specify a valid time with explicit offset')
return candidates.pop().replace(tzinfo=None), True
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
@@ -38,6 +113,10 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None, *, impor
args = _parse_tool_args(content)
except ValueError:
return {"error": "Invalid JSON arguments", "exit_code": 1}
try:
args = _normalize_local_event_times(args)
except (ValueError, TypeError) as exc:
return {"error": str(exc), "exit_code": 1}
# ── Batch normalization ──
# Some models (e.g. deepseek-v4-flash) emit {"events": [{...}, ...]}
@@ -180,6 +259,8 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None, *, impor
def _parse_event_dt(raw: str) -> tuple[datetime, bool]:
"""Parse agent event datetimes in the user's timezone when available."""
if args.get('timezone'):
return _explicit_calendar_time(raw, args['timezone'])
return _parse_dt_pair(parse_due_for_user(raw))
def _parse_all_day_event_dt(raw: str) -> tuple[datetime, bool]:
@@ -495,12 +576,11 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None, *, impor
)
return {
"response": (
f"Event already exists: [{summary}](#event-{existing.uid}) on {dtstart_str}"
f"Event already exists: [{summary}](#event-{existing.uid}) on {_saved_event_times(existing)['dtstart']}"
+ reminder_text
),
"uid": existing.uid,
"dtstart": dtstart_str,
"all_day": bool(existing.all_day),
**_saved_event_times(existing),
"anchor": f"[{summary}](#event-{existing.uid})",
"has_reminder": bool(reminder_note_id),
"reminder_note_id": reminder_note_id,
@@ -572,10 +652,9 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None, *, impor
# that opens the calendar on that day. See the markdown
# anchor convention ([Name](#event-<uid>)).
return {
"response": f"Created event [{summary}](#event-{uid}){tag_blurb} on {dtstart_str}{reminder_blurb}",
"response": f"Created event [{summary}](#event-{uid}){tag_blurb} on {_saved_event_times(ev)['dtstart']}{reminder_blurb}",
"uid": uid,
"dtstart": dtstart_str,
"all_day": bool(all_day),
**_saved_event_times(ev),
"anchor": f"[{summary}](#event-{uid})",
"has_reminder": bool(reminder_note_id),
"reminder_note_id": reminder_note_id,
@@ -711,11 +790,7 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None, *, impor
return {
"response": f"Updated event [{ev.summary or uid}](#event-{base_uid}){reminder_text}",
"uid": base_uid,
"dtstart": (
(ev.dtstart.isoformat() + ("Z" if bool(ev.is_utc) and not bool(ev.all_day) else ""))
if ev.dtstart else None
),
"all_day": bool(ev.all_day),
**_saved_event_times(ev),
"anchor": f"[{ev.summary or uid}](#event-{base_uid})",
"has_reminder": bool(reminder_note_id) or bool(_calendar_reminder_for_event(db, owner, ev)),
"reminder_note_id": reminder_note_id,
+30 -2
View File
@@ -9,6 +9,7 @@ function-locally here.
import hashlib
import io
import uuid
import re
from pathlib import Path
from typing import Dict, Optional
@@ -25,12 +26,27 @@ async def do_edit_image(content: str, owner: Optional[str] = None) -> Dict:
action = args.get("action", "")
if not image_id or not action:
return {"error": "image_id and action are required", "exit_code": 1}
if action not in {"upscale", "rembg"}:
if action not in {"prompt", "upscale", "rembg"}:
return {
"error": f"Unsupported edit action: {action}. Use upscale or rembg.",
"error": f"Unsupported edit action: {action}. Use prompt, upscale or rembg.",
"exit_code": 1,
}
if str(image_id).startswith('odysseus://attachment/'):
from src.tool_utils import get_upload_handler
from src.settings import load_settings
ref = re.fullmatch(r'odysseus://attachment/([A-Za-z0-9_-]+(?:\.[A-Za-z0-9]+)?)', image_id)
handler = get_upload_handler()
info = handler.resolve_upload(ref[1], owner=owner, allow_admin=False) if ref and owner and handler else None
if not info or not info.get('path') or not handler.is_image_file(info.get('name') or info.get('id') or ref[1], info.get('mime', '')):
return {'error': 'Uploaded image not found or not accessible', 'exit_code': 1}
if action != 'prompt':
return {'error': 'Uploaded images support action=prompt here', 'exit_code': 1}
if not load_settings().get('image_gen_enabled', True):
return {'error': 'Image generation is disabled by the administrator.', 'exit_code': 1}
from src.ai_interaction import do_edit_image as edit_with_model
return await edit_with_model(str(args.get('prompt') or '').strip(), info['path'], owner=owner, size='auto')
from core.database import GalleryImage, SessionLocal
from src.constants import GENERATED_IMAGES_DIR
@@ -53,6 +69,18 @@ async def do_edit_image(content: str, owner: Optional[str] = None) -> Dict:
if source_name != source.filename or source_path.parent != root or not source_path.is_file():
return {"error": "Image file not found", "exit_code": 1}
if action == "prompt":
from src.settings import load_settings
if not load_settings().get('image_gen_enabled', True):
return {"error": "Image generation is disabled by the administrator.", "exit_code": 1}
prompt = str(args.get('prompt') or '').strip()
if not prompt:
return {"error": "prompt is required for instruction-based editing", "exit_code": 1}
session_id = source.session_id
db.close()
from src.ai_interaction import do_edit_image as edit_with_model
return await edit_with_model(prompt, str(source_path), session_id=session_id, owner=owner, size='auto')
from PIL import Image
with Image.open(source_path) as opened:
+37 -2
View File
@@ -53,6 +53,14 @@ async def do_manage_notes(content: str, owner: Optional[str] = None) -> Dict:
"remove": "delete",
}
action = _NOTE_ACTION_ALIASES.get(action, action)
if action == "add" and any(args.get(key) for key in ("id", "note_id", "noteId")):
return {
"error": 'Nothing saved. add creates a new note and cannot take an existing note ID. '
'To fill or change that note, retry with action="update", id set to the existing '
'note ID, and checklist_items plus note_type="checklist" for a to-do list. '
'Do not create another note.',
"exit_code": 1,
}
if action == "remove_item":
return {
"error": "To remove a checklist item, use update with id and the complete remaining checklist_items, preserving their done states. No item was changed.",
@@ -267,6 +275,29 @@ async def do_manage_notes(content: str, owner: Optional[str] = None) -> Dict:
items_raw = args.get("items")
items_json = json.dumps(items_raw) if items_raw is not None else None
note_type = args.get("note_type", "checklist" if items_raw else "note")
if not title and note_type in {"checklist", "todo", "goal"}:
from src.user_time import now_user_local
title = f"To-do - {now_user_local().date().isoformat()}"
if note_type in {"checklist", "todo", "goal"} and not isinstance(items_raw, list):
return {
"error": 'Nothing saved. Checklist creation requires checklist_items as an array of '
'{"text":"task including any stated time","done":false}. '
'Put each task in its own item, not in title. Use a short title only; '
'do not include explanations or timezone calculations. Retry with the structured items. '
'Use [] only when the user explicitly requested an empty checklist.',
"exit_code": 1,
}
if items_raw is not None and (
not isinstance(items_raw, list)
or any(not isinstance(item, dict)
or not isinstance(item.get("text"), str)
or not item["text"].strip()
or not isinstance(item.get("done", False), bool)
for item in items_raw)
):
return {"error": 'Nothing saved. checklist_items must be an array of objects with '
'nonempty text and an optional boolean done. Retry with corrected items.',
"exit_code": 1}
# Accept natural-language due_date ("tomorrow at 1pm") in
# addition to ISO. Use the user-tz-aware parser so the LLM's
# naive times ("today at 9pm") are anchored to the USER's clock,
@@ -451,7 +482,9 @@ async def do_manage_notes(content: str, owner: Optional[str] = None) -> Dict:
if "archived" in args:
note.archived = args["archived"]
db.commit()
return {"response": f"Note updated: \"{note.title or '(untitled)'}\"", "exit_code": 0}
return {"response": f"Note updated: \"{note.title or '(untitled)'}\"",
"note_id": note.id, "note_title": note.title or "",
"open_url": f"/#open=notes&note={note.id}", "exit_code": 0}
elif action == "delete":
note_id = _note_id_arg()
@@ -494,7 +527,9 @@ async def do_manage_notes(content: str, owner: Optional[str] = None) -> Dict:
elif action == "toggle_item":
note_id = _note_id_arg()
index = args.get("index", 0)
index = args.get("index")
if not isinstance(index, int) or isinstance(index, bool):
return {"error": "toggle_item requires an explicit integer index (0-based). Use view to inspect item indices if unknown; no change made.", "exit_code": 1}
note = _note_by_prefix(note_id)
if not note:
return {"error": f"Note '{note_id}' not found", "exit_code": 1}
+74 -1
View File
@@ -293,6 +293,38 @@ def _task_date_utc(value):
parsed = parsed.astimezone(timezone.utc).replace(tzinfo=None)
return parsed
def _task_structured_schedule(args, fallback_time=None):
"""Translate unambiguous day fields into the scheduler's legacy format."""
if 'day_of_month' in args:
day = args['day_of_month']
if isinstance(day, bool) or not isinstance(day, int) or not 1 <= day <= 31:
raise ValueError('day_of_month must be an integer from 1 to 31')
if ('weekdays' in args or args.get('scheduled_day') is not None
or args.get('cron_expression') or args.get('schedule') not in (None, 'monthly')
or args.get('trigger_type', 'schedule') != 'schedule'):
raise ValueError('day_of_month is only for monthly schedules; omit other day fields')
return {**args, 'schedule': 'monthly', 'scheduled_day': day}
if 'weekdays' not in args:
return args
days = args['weekdays']
names = ('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday')
if not isinstance(days, list) or not days or any(not isinstance(d, str) or d not in names for d in days):
raise ValueError('weekdays must contain weekday names from monday through sunday')
if args.get('cron_expression') or args.get('scheduled_day') is not None:
raise ValueError('Use weekdays or cron_expression/scheduled_day, not both')
if args.get('trigger_type', 'schedule') != 'schedule' or args.get('schedule') == 'once':
raise ValueError('weekdays requires a recurring schedule trigger')
from datetime import datetime
clock = args.get('scheduled_time', fallback_time)
try:
parsed = datetime.strptime(clock, '%H:%M')
except (TypeError, ValueError) as exc:
raise ValueError('scheduled_time in HH:MM UTC is required with weekdays') from exc
cron_days = ','.join(str(n) for n in sorted({names.index(d) for d in days}))
return {**args, 'schedule': 'cron', 'scheduled_time': parsed.strftime('%H:%M'),
'cron_expression': f'{parsed.minute} {parsed.hour} * * {cron_days}'}
async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
"""Handle manage_tasks tool calls: CRUD on scheduled tasks."""
import uuid as _uuid
@@ -409,6 +441,8 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
bits = [t.status or "unknown"]
if t.schedule:
bits.append(str(t.schedule))
if t.schedule == "cron" and t.cron_expression:
bits.append(t.cron_expression)
if t.scheduled_time:
bits.append(str(t.scheduled_time))
if t.next_run:
@@ -420,6 +454,7 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
return {"response": "\n".join(lines), "exit_code": 0}
elif action == "create":
args = _task_structured_schedule(args)
task_type = args.get("task_type", "llm")
trigger_type = args.get("trigger_type", "schedule")
@@ -433,14 +468,19 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
scheduled_date = None
if trigger_type == "schedule":
schedule = args.get("schedule", "daily")
if args.get('scheduled_date') and schedule != 'once':
raise ValueError('scheduled_date is only for schedule=once; use day_of_month and scheduled_time for monthly tasks, or weekdays and scheduled_time for weekly tasks')
if schedule == "once":
scheduled_date = _task_date_utc(args.get("scheduled_date"))
next_run = compute_next_run(
schedule, args.get("scheduled_time", "09:00"),
args.get("scheduled_day"), scheduled_date,
cron_expression=args.get("cron_expression"),
)
if schedule == "once" and next_run is None:
return {"error": "scheduled_date must be in the future", "exit_code": 1}
if schedule == "cron" and next_run is None:
return {"error": "A valid cron_expression is required for schedule=cron", "exit_code": 1}
task_id = str(_uuid.uuid4())
# Guard each fallback with `or`: args.get("prompt", default) returns
@@ -458,6 +498,7 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
scheduled_time=args.get("scheduled_time", "09:00") if trigger_type == "schedule" else None,
scheduled_day=args.get("scheduled_day"),
scheduled_date=scheduled_date,
cron_expression=args.get("cron_expression") if trigger_type == "schedule" else None,
trigger_type=trigger_type,
trigger_event=args.get("trigger_event"),
trigger_count=args.get("trigger_count"),
@@ -481,6 +522,30 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
if owner and task.owner != owner:
return {"error": "Access denied", "exit_code": 1}
if 'weekdays' in args or 'day_of_month' in args:
clock = task.scheduled_time
if task.schedule == 'cron':
fields = (task.cron_expression or '').split()
clock = (f'{fields[1]}:{fields[0]}' if len(fields) == 5
and fields[0].isdigit() and fields[1].isdigit() else None)
args = _task_structured_schedule({
'trigger_type': task.trigger_type or 'schedule', **args,
}, fallback_time=clock)
if ((args.get('schedule') or task.schedule) == 'cron'
and args.get('scheduled_time') is not None
and args.get('cron_expression') is None):
from datetime import datetime
try:
clock = datetime.strptime(args['scheduled_time'], '%H:%M')
except (TypeError, ValueError) as exc:
raise ValueError('scheduled_time must be HH:MM UTC') from exc
fields = (task.cron_expression or '').split()
if len(fields) != 5:
raise ValueError('Supply cron_expression to retime a schedule without a five-field cron expression')
# For cron tasks the executable clock lives in the expression,
# not the legacy scheduled_time column used by simple schedules.
args = {**args, 'scheduled_time': clock.strftime('%H:%M'),
'cron_expression': ' '.join([str(clock.minute), str(clock.hour), *fields[2:]])}
changed = []
for field in ("name", "prompt", "output_target"):
if args.get(field) is not None:
@@ -503,12 +568,14 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
changed.append("trigger_count")
schedule_changed = False
for field in ("schedule", "scheduled_time", "scheduled_day"):
for field in ("schedule", "scheduled_time", "scheduled_day", "cron_expression"):
if args.get(field) is not None:
setattr(task, field, args[field])
changed.append(field)
schedule_changed = True
if "scheduled_date" in args:
if args.get('scheduled_date') and task.schedule != 'once':
raise ValueError('scheduled_date is only for schedule=once; use day_of_month and scheduled_time for monthly tasks, or weekdays and scheduled_time for weekly tasks')
task.scheduled_date = _task_date_utc(args["scheduled_date"])
changed.append("scheduled_date")
schedule_changed = True
@@ -519,9 +586,12 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
task.next_run = compute_next_run(
task.schedule, task.scheduled_time, task.scheduled_day,
task.scheduled_date,
cron_expression=task.cron_expression,
)
if task.schedule == "once" and task.next_run is None:
raise ValueError("scheduled_date must be in the future")
if task.schedule == "cron" and task.next_run is None:
raise ValueError("A valid cron_expression is required for schedule=cron")
db.commit()
return {"response": f"Updated task '{task.name}': {', '.join(changed)}", "exit_code": 0}
@@ -552,9 +622,12 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
task.next_run = compute_next_run(
task.schedule, task.scheduled_time, task.scheduled_day,
task.scheduled_date,
cron_expression=task.cron_expression,
)
if task.schedule == "once" and task.next_run is None:
raise ValueError("A future scheduled_date is required to resume this one-off task")
if task.schedule == "cron" and task.next_run is None:
raise ValueError("A valid cron_expression is required to resume this task")
db.commit()
return {"response": f"Task '{task.name}' {action}d", "exit_code": 0}