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
+144
View File
@@ -0,0 +1,144 @@
"""Opt-in Ajax smoke test: temporary SQLite docs, stubbed web, no real mutations.
Run from repository root with PYTHONPATH=. and ODYSSEUS_EDITOR_TEST_ENDPOINT set.
Optional ODYSSEUS_EDITOR_ACTIONS selects comma-separated actions.
"""
import asyncio, json, os, re, tempfile
from pathlib import Path
from types import SimpleNamespace
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from core import database
from src import database as compat
from src.agent_tools import TOOL_HANDLERS
import src.clean_agent_preview as runner
from src.tool_policy import ToolPolicy
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
from src.turn_contract import requested_capabilities, selected_tools_for_request, preserve_bound_editor_selected_tools, resolve_turn_contract
menu=Path('static/js/document.js').read_text().split('const _AI_WRITING_ACTIONS = Object.freeze({',1)[1].split('});',1)[0]
actions=dict(re.findall(r"\s+(\w+): '([^']+)'",menu))
image_body = '<p>Old caption</p><p><img src="/api/generated-image/fixture.png" alt="Fixture image" width="320" height="180"></p><p>Keep this paragraph unchanged.</p>'
email_body = ('To: sam@example.com\nSubject: Re: Design review\nIn-Reply-To: <fixture@example.com>\n---\n'
'<div class="email-reply-edit-slot"><p>Hi Sam,</p><p>I can attend on Tuesday.</p><p>Thanks,<br>Alex</p></div>'
'<div class="email-quoted-history" contenteditable="false"><p>Sam wrote: Can you attend on Tuesday?</p></div>')
edit_cases = {
'rich-image-caption': ('richtext', image_body,
'Change Old caption to New caption in the open document. Preserve the embedded image, its attributes, and all other text.',
lambda value: value == image_body.replace('Old caption', 'New caption')),
'email-reply-only': ('email', email_body,
'Change Tuesday to Wednesday only in my reply, not in the quoted email. Keep the recipient, subject, signature and quoted history unchanged.',
lambda value: value == email_body.replace('I can attend on Tuesday.', 'I can attend on Wednesday.')),
'email-subject-only': ('email', email_body,
'Change only the subject of this open email draft to Schedule confirmation. Leave all other headers, reply text and quoted history unchanged.',
lambda value: value == email_body.replace('Subject: Re: Design review', 'Subject: Schedule confirmation')),
'email-recipient-only': ('email', email_body,
'Change only the recipient of this open email draft to jordan@example.com. Do not send it. Keep the greeting, subject, body and quoted history unchanged.',
lambda value: value == email_body.replace('To: sam@example.com', 'To: jordan@example.com')),
'email-signature-only': ('email', email_body,
'Change Alex to Alexandra only in my reply signature. Keep all headers, other reply text, formatting and quoted history unchanged.',
lambda value: value == email_body.replace('Thanks,<br>Alex', 'Thanks,<br>Alexandra')),
'delete-line': ('text', 'Alpha\nRemove this line\nGamma\n',
'Delete only the line Remove this line, including its newline. Keep everything else unchanged.',
lambda value: value == 'Alpha\nGamma\n'),
'replace-all': ('text', 'alpha is first.\nalpha is repeated.\nKeep: violet-72\n',
'Replace every occurrence of alpha with beta. Keep everything else unchanged.',
lambda value: value == 'beta is first.\nbeta is repeated.\nKeep: violet-72\n'),
'append-line': ('text', 'Alpha\nBeta\n',
'Append Gamma on a new final line, ending with a newline. Keep the existing two lines unchanged and do not add a blank line.',
lambda value: value == 'Alpha\nBeta\nGamma\n'),
'markdown-table': ('markdown', '| Item | Status |\n| --- | --- |\n| Alpha | pending |\n| Beta | pending |\n\nKeep this paragraph unchanged.',
'Change only the Beta row status from pending to done. Keep the Alpha row and all other text unchanged.',
lambda value: value == '| Item | Status |\n| --- | --- |\n| Alpha | pending |\n| Beta | done |\n\nKeep this paragraph unchanged.'),
'rich-link-text': ('richtext', '<p>Read <a href="https://example.com/manual" target="_blank">old guide</a> before starting.</p><p><strong>Keep this paragraph unchanged.</strong></p>',
'Change the link text from old guide to setup guide. Keep its URL, attributes, formatting and all other text unchanged.',
lambda value: value == '<p>Read <a href="https://example.com/manual" target="_blank">setup guide</a> before starting.</p><p><strong>Keep this paragraph unchanged.</strong></p>'),
'json-value': ('json', '{"name":"outer","settings":{"name":"inner","enabled":true},"count":7}',
'Change settings.name to updated. Leave the outer name and all other values unchanged.',
lambda value: json.loads(value) == {'name':'outer','settings':{'name':'updated','enabled':True},'count':7}),
'repeated': ('text', 'First: alpha\nSecond: alpha\nKeep: violet-72',
'Change only the second alpha to beta. Keep everything else unchanged.',
lambda value: value == 'First: alpha\nSecond: beta\nKeep: violet-72'),
'quoted-code': ('javascript', 'const label = "old";\nconst count = 7;\n',
'Change the label string to new. Keep the count and all other code unchanged.',
lambda value: value == 'const label = "new";\nconst count = 7;\n'),
'svg-color': ('svg', '<svg xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="40" fill="orange"/></svg>',
'Make the circle white without changing its size or position.',
lambda value: value == '<svg xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="40" fill="white"/></svg>'),
}
async def main():
failures=[]
selected_actions = os.environ.get('ODYSSEUS_EDITOR_ACTIONS', ','.join([*actions,'edit','update'])).split(',')
unknown = set(selected_actions) - (set(actions) | set(edit_cases) | {'edit', 'update'})
if unknown: raise ValueError('Unknown editor test actions: ' + ', '.join(sorted(unknown)))
with tempfile.TemporaryDirectory(prefix='editor-fixture-') as tmp:
engine=create_engine('sqlite:///'+tmp+'/test.db');database.Base.metadata.create_all(engine)
factory=sessionmaker(bind=engine);database.SessionLocal=compat.SessionLocal=factory
for action in selected_actions:
body='The calendar has events and notes about Python scripts. This sentnce is unecessarily long and it is very very unclear.\n\nKeep this paragraph unchanged.'
if action=='sources': body='Water freezes at 10 degrees Celsius at standard pressure.\n\nKeep this paragraph unchanged.'
rich_fixture = os.environ.get('ODYSSEUS_EDITOR_RICH_FIXTURE') == '1'
if rich_fixture and action != 'sources':
body = ''.join(f'<p>Section {i}: This sentnce has an unecessary delay. That banana is ripe; being patient helps.</p>' for i in range(1, 9)) + '<p><strong>Keep this paragraph unchanged.</strong></p>'
language = 'richtext' if rich_fixture else 'markdown'
if action in edit_cases:
language, body, _, _ = edit_cases[action]
with factory() as db:
db.add(database.Document(id=action,owner='fixture-owner',title='Fixture',language=language,current_content=body));db.commit()
doc=SimpleNamespace(id=action,title='Fixture',language=language,current_content=body)
prompt=actions.get(action, 'Fix sentnce to sentence in the open document.' if action=='edit' else 'Rewrite the whole open document as two short bullet points.')
if action in edit_cases: prompt = edit_cases[action][2]
if action in actions:
if action=='style': prompt+='\n\nUse this configured writing style as the source of truth:\n---\nUse plain, concise English.\n---'
prompt+='\n\nImportant scope: work only on this selected passage and do not suggest changes elsewhere in the document. Selected passage:\n---\n'+body.split('\n\n')[0]+'\n---'
if rich_fixture:
prompt = prompt.split('\n\nImportant scope:')[0] + '\n\nThere is no selection. Work on the whole open document.'
calls=[]
async def execute(block, **kwargs):
name=block.tool_type
calls.append(name)
if name in {'web_search','web_fetch','private_browser'}:
return 'fixture evidence',{'output':json.dumps({'results':[{'title':'Freezing point of water','url':'https://example.com/water','content':'At standard pressure, water freezes at 0 degrees Celsius.'}]}),'exit_code':0}
assert name in {'suggest_document','edit_document','update_document'},name
result=await TOOL_HANDLERS[name](block.content,{'owner':'fixture-owner','doc_id':action})
return name,result
runner.execute_tool_block=execute
policy=ToolPolicy();families=requested_capabilities(prompt,active_document=True)
selected=preserve_bound_editor_selected_tools(prompt,selected_tools_for_request(prompt),active_document=True)
contract=resolve_turn_contract(capabilities=families,schemas=FUNCTION_TOOL_SCHEMAS,policy=policy,selected_tools=selected)
events=[]
async for chunk in runner.stream_preview(endpoint_url=os.environ['ODYSSEUS_EDITOR_TEST_ENDPOINT'],model='Ajax',headers={},turn_contract=contract,messages=[{'role':'user','content':prompt}],session_id='fixture-editor',owner='fixture-owner',disabled_tools=set(),tool_policy=policy,thinking_mode='off',active_document=doc,max_tokens=int(os.environ.get("ODYSSEUS_EDITOR_MAX_TOKENS", "4096"))):
if chunk.startswith('data: ') and '[DONE]' not in chunk:
event=json.loads(chunk[6:]); events.append(event)
if event.get('type') == 'tool_output': print('CHECK', action, event.get('tool'), 'round=' + str(event.get('round')), 'error=' + str(event.get('error')), flush=True)
with factory() as db: after=db.get(database.Document,action).current_content
suggestions=[e for e in events if e.get('type')=='doc_suggestions']
outputs=[{'tool':e['tool'],'error':e.get('error'),'output':e.get('output','')[:200], 'arguments':e.get('command','')[:2000]} for e in events if e.get('type')=='tool_output']
ok=bool(suggestions) and after==body if action in actions and action!='proofread' else after!=body
if action=='sources':ok=ok and any(n in calls for n in ['web_search','web_fetch','private_browser'])
if action in actions and action!='proofread':
ok=ok and all(item['find'] in body.split('\n\n')[0] for event in suggestions for item in event['suggestions'])
if action=='concise':
from src.clean_agent_preview import document_suggestion_quality_error
ok=ok and all(document_suggestion_quality_error('suggest_document',
{'suggestions': event['suggestions']}, user_text=prompt) is None for event in suggestions)
if action in {'edit','proofread'}: ok=ok and 'Keep this paragraph unchanged.' in after
if action == 'proofread': ok = ok and 'sentnce' not in after and 'unecessarily' not in after
if action in edit_cases: ok = edit_cases[action][3](after)
if rich_fixture and action=='proofread':
ok = ok and 'sentnce' not in after and 'unecessary' not in after
ok = ok and after.count('That banana is ripe; being patient helps.') == 8
ok = ok and after.count('<p>') == 9 and '<strong>Keep this paragraph unchanged.</strong>' in after
if not ok: failures.append(action)
answer = ''
for event in events:
if event.get('type') == 'final_response': answer = event.get('content') or ''
elif isinstance(event.get('delta'), str):
if event.get('replacement_scope') == 'turn': answer = ''
answer += event['delta']
print(json.dumps({'action':action,'pass':ok,'failed_attempts':sum(bool(o.get('error')) for o in outputs),'calls':calls,'suggestions':suggestions,'outputs':outputs,'changed':after!=body,'answer':answer}),flush=True)
if os.environ.get('ODYSSEUS_EDITOR_TRACE'):
print('EVENTS', json.dumps(events), flush=True)
engine.dispose()
if failures: raise SystemExit('Failed writing actions: '+', '.join(failures))
if __name__ == '__main__':
asyncio.run(main())
+404
View File
@@ -0,0 +1,404 @@
"""Live model + real organizer handlers with disposable SQLite storage.
No scheduler runs; calendar fixtures are local, with no external sync.
Run with PYTHONPATH=. ODYSSEUS_EDITOR_TEST_ENDPOINT=<chat completions URL>.
"""
import asyncio
import json
import os
import re
import tempfile
from types import SimpleNamespace
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from core import database
from src.tools.notes import do_manage_notes
from src.tools.system import do_manage_tasks
from src.tools.calendar import do_manage_calendar
import jsonschema
from src import clean_agent_preview as runner
from src.tool_policy import ToolPolicy
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
from src.turn_contract import requested_capabilities, selected_tools_for_request, resolve_turn_contract
CASES = [
('checklist', 'Make a todo list: buy milk, return the keys, call Sam.', 'manage_notes'),
('plain-note', 'Save a note titled Project ideas with the text: Build a small weather display.', 'manage_notes'),
('daily', 'Create a task to summarize technology news every day at 07:30 UTC.', 'manage_tasks'),
('weekly', 'Every Monday at 09:00 UTC research new battery technology for me.', 'manage_tasks'),
('calendar', 'Add a calendar event named Design review on October 5, 2026 at 14:00 UTC for one hour.', 'manage_calendar'),
('calendar-all-day', 'Add an all-day calendar event named Office closed on October 8, 2026. Only that day.', 'manage_calendar'),
('calendar-offset', 'Add a calendar event named Tokyo call on October 6, 2026 at 18:00 Japan time (UTC+09:00) for 30 minutes.', 'manage_calendar'),
('calendar-tokyo-simple', 'Add a calendar event named Tokyo call on October 6, 2026 at 6pm Tokyo time for 30 minutes.', 'manage_calendar'),
('calendar-half-offset', 'Create a calendar event called Early call on October 6, 2026 at 00:15 UTC+05:30, lasting 30 minutes.', 'manage_calendar'),
('calendar-negative', 'Create a calendar event called Late call on October 6, 2026 at 22:00 UTC-04:00, lasting 30 minutes.', 'manage_calendar'),
('calendar-dst', 'Add Summer call to my calendar on July 6, 2027 at 10am America/New_York time for 30 minutes.', 'manage_calendar'),
('calendar-move', 'Move it to 16:30 UTC on the same day. Keep its duration.', 'manage_calendar'),
('calendar-rename', 'Rename it to Planning review. Keep its date and time unchanged.', 'manage_calendar'),
('calendar-shorten', 'Make that event 30 minutes long. Keep its start time and title.', 'manage_calendar'),
('calendar-midnight', 'Move that event to October 5, 2026 at 23:30 UTC. Keep its one-hour duration and title.', 'manage_calendar'),
('calendar-move-zone', 'Move that event to October 6, 2026 at 18:00 Tokyo time. Keep its one-hour duration and title.', 'manage_calendar'),
('calendar-relative-hours', 'Push that event back by two hours.', 'manage_calendar'),
('calendar-relative-day', 'Move that event to the following day at the same time.', 'manage_calendar'),
('calendar-hypothetical', 'What time would that event start if it were pushed back by two hours? Do not change it.', 'manage_calendar'),
('calendar-hypothetical-later', 'What time would that event start if it began two hours later? Do not change it.', 'manage_calendar'),
('calendar-hypothetical-earlier', 'What time would that event start if it began two hours earlier? Do not change it.', 'manage_calendar'),
('calendar-no-change', 'Do not move that event. Just remind me of its current start time.', 'manage_calendar'),
('calendar-wording', 'Suggest a short message asking to postpone that event by two hours. Do not modify my calendar.', 'manage_calendar'),
('note-append', 'Add "Check the battery life." to the end of that note. Keep what is already there.', 'manage_notes'),
('checklist-remove', 'Remove return the keys from that checklist. Leave the other items and their checked states alone.', 'manage_notes'),
('checklist-check', 'Mark call Sam as done in that checklist. Keep everything else.', 'manage_notes'),
('checklist-rename', 'Rename that checklist to Weekend errands. Keep all items and their checked states.', 'manage_notes'),
('checklist-uncheck', 'Uncheck buy milk in that checklist. Keep the other items unchanged.', 'manage_notes'),
('checklist-uncheck-last', 'Uncheck call Sam in that checklist. Keep the other items unchanged.', 'manage_notes'),
('checklist-reorder', 'Move call Sam to the top of that checklist. Keep the other items in order and preserve their checked states.', 'manage_notes'),
('checklist-append', 'Add pick up parcel at the end of that checklist, unchecked. Keep the existing items and their checked states.', 'manage_notes'),
('checklist-unfinished', 'List only the unfinished items in that checklist. Do not change anything.', 'manage_notes'),
('checklist-finished', 'Which items in that checklist are already done? List only those. Do not change anything.', 'manage_notes'),
('task-pause', 'Pause that task.', 'manage_tasks'),
('task-retime', 'Change that task to 08:45 UTC every day. Keep its name and prompt.', 'manage_tasks'),
('task-rename', 'Rename that task to Morning briefing. Keep everything else.', 'manage_tasks'),
('task-offset', 'Create a task to summarize technology news every day at 07:30 UTC+09:00.', 'manage_tasks'),
('task-weekdays', 'Create one task to summarize technology news every Monday, Wednesday and Friday at 09:15 UTC.', 'manage_tasks'),
('task-monthly', 'Create a task to review my budget on the 15th of every month at 08:00 UTC.', 'manage_tasks'),
('task-cron-retime', 'Move that task to 10:30 UTC. Keep the same weekdays.', 'manage_tasks'),
('task-cron-days', 'Run that task on Tuesdays and Thursdays instead. Keep the same time.', 'manage_tasks'),
('task-cron-pause', 'Pause that task.', 'manage_tasks'),
('task-cron-resume', 'Resume that task with its existing schedule.', 'manage_tasks'),
('task-monthly-day', 'Move that task to the 20th of each month. Keep the same time and instructions.', 'manage_tasks'),
('task-monthly-time', 'Run that task at 11:45 UTC instead. Keep the same day of the month and instructions.', 'manage_tasks'),
]
CALENDAR_EXPECTED = {
'calendar-offset': ('Tokyo call', '2026-10-06T09:00:00', '2026-10-06T09:30:00'),
'calendar-tokyo-simple': ('Tokyo call', '2026-10-06T09:00:00', '2026-10-06T09:30:00'),
'calendar-half-offset': ('Early call', '2026-10-05T18:45:00', '2026-10-05T19:15:00'),
'calendar-negative': ('Late call', '2026-10-07T02:00:00', '2026-10-07T02:30:00'),
'calendar-dst': ('Summer call', '2027-07-06T14:00:00', '2027-07-06T14:30:00'),
'calendar-move': ('Design review', '2026-10-05T16:30:00', '2026-10-05T17:30:00'),
'calendar-rename': ('Planning review', '2026-10-05T14:00:00', '2026-10-05T15:00:00'),
'calendar-shorten': ('Design review', '2026-10-05T14:00:00', '2026-10-05T14:30:00'),
'calendar-midnight': ('Design review', '2026-10-05T23:30:00', '2026-10-06T00:30:00'),
'calendar-move-zone': ('Design review', '2026-10-06T09:00:00', '2026-10-06T10:00:00'),
'calendar-relative-hours': ('Design review', '2026-10-05T16:00:00', '2026-10-05T17:00:00'),
'calendar-relative-day': ('Design review', '2026-10-06T14:00:00', '2026-10-06T15:00:00'),
'calendar-hypothetical': ('Design review', '2026-10-05T14:00:00', '2026-10-05T15:00:00'),
'calendar-hypothetical-later': ('Design review', '2026-10-05T14:00:00', '2026-10-05T15:00:00'),
'calendar-hypothetical-earlier': ('Design review', '2026-10-05T14:00:00', '2026-10-05T15:00:00'),
'calendar-no-change': ('Design review', '2026-10-05T14:00:00', '2026-10-05T15:00:00'),
'calendar-wording': ('Design review', '2026-10-05T14:00:00', '2026-10-05T15:00:00'),
}
async def seed_followup(case, owner):
if case in {'calendar-move', 'calendar-rename', 'calendar-shorten', 'calendar-midnight', 'calendar-move-zone', 'calendar-relative-hours', 'calendar-relative-day', 'calendar-no-change', 'calendar-wording'} or case.startswith('calendar-hypothetical'):
tool = 'manage_calendar'
prompt = 'Create Design review on my calendar, October 5, 2026, 14:00 UTC for one hour.'
args = dict(action='create_event', summary='Design review', dtstart='2026-10-05T14:00:00Z', dtend='2026-10-05T15:00:00Z')
result = await do_manage_calendar(json.dumps(args), owner=owner)
elif case == 'note-append':
tool = 'manage_notes'
prompt = 'Save a note titled Project ideas with the text: Build a small weather display.'
args = dict(action='add', title='Project ideas', content='Build a small weather display.')
result = await do_manage_notes(json.dumps(args), owner=owner)
elif case.startswith('checklist-'):
tool = 'manage_notes'
prompt = 'Save a checklist called Errands: buy milk (done), return the keys, call Sam.'
args = dict(action='add', title='Errands', note_type='checklist', checklist_items=[
{'text': 'buy milk', 'done': True}, {'text': 'return the keys', 'done': False},
{'text': 'call Sam', 'done': False}])
if case == 'checklist-uncheck-last':
prompt = 'Save a checklist called Errands: buy milk (done), return the keys, call Sam (done).'
args['checklist_items'][2]['done'] = True
result = await do_manage_notes(json.dumps(args), owner=owner)
elif case in {'task-monthly-day', 'task-monthly-time'}:
tool = 'manage_tasks'
prompt = 'Create a task called Budget review to review my budget on the 15th of each month at 08:00 UTC.'
args = dict(action='create', name='Budget review', prompt='Review my budget.',
day_of_month=15, scheduled_time='08:00', task_type='llm')
result = await do_manage_tasks(json.dumps(args), owner=owner)
elif case.startswith('task-cron-'):
tool = 'manage_tasks'
prompt = 'Create one task called Tech digest to summarize technology news every Monday, Wednesday and Friday at 09:15 UTC.'
args = dict(action='create', name='Tech digest', prompt='Summarize technology news.',
weekdays=['monday', 'wednesday', 'friday'], scheduled_time='09:15', task_type='llm')
result = await do_manage_tasks(json.dumps(args), owner=owner)
elif case in {'task-pause', 'task-retime', 'task-rename'}:
tool = 'manage_tasks'
prompt = 'Create a task called Tech digest to summarize technology news every day at 07:30 UTC.'
args = dict(action='create', name='Tech digest', prompt='Summarize technology news.',
schedule='daily', scheduled_time='07:30', task_type='llm')
result = await do_manage_tasks(json.dumps(args), owner=owner)
else:
return [], None
assert result.get('exit_code', 0) == 0, result
history = [{'role': 'user', 'content': prompt}, {'role': 'assistant',
'content': result['response'], 'metadata': {
'tool_events': [{'tool': tool, 'exit_code': 0}],
'clean_v3_turn': [
{'role': 'assistant', 'content': None, 'tool_calls': [{'id': 'seed1', 'type': 'function',
'function': {'name': tool, 'arguments': json.dumps(args)}}]},
{'role': 'tool', 'tool_call_id': 'seed1', 'content': json.dumps(result)},
],
}}]
if case == 'task-cron-resume':
paused = await do_manage_tasks(json.dumps({'action': 'pause', 'task_id': result['task_id']}), owner=owner)
assert paused.get('exit_code') == 0, paused
history.extend([{'role': 'user', 'content': 'Pause that task.'},
{'role': 'assistant', 'content': paused['response']}])
return history, result.get('uid') or result.get('note_id') or result.get('task_id')
def validate_case(case, args):
if case == 'checklist':
assert args['action'] == 'add' and args['note_type'] == 'checklist'
texts = [item['text'].lower() for item in args['checklist_items']]
assert len(texts) == 3
assert all(any(word in text for text in texts) for word in ('milk', 'keys', 'sam'))
assert all(not item.get('done') for item in args['checklist_items'])
elif case == 'plain-note':
assert args['action'] == 'add' and args['title'] == 'Project ideas'
assert 'weather display' in args.get('content', '').lower()
elif case in {'daily', 'weekly'}:
assert args['action'] == 'create'
assert args.get('name') and args.get('prompt')
assert args['schedule'] == case
assert args['scheduled_time'] == ('07:30' if case == 'daily' else '09:00')
if case == 'weekly': assert args['scheduled_day'] == 0
else:
assert args['action'] == 'create_event'
assert args['summary'] == 'Design review'
assert args['dtstart'].startswith('2026-10-05T14:00')
assert args['dtend'].startswith('2026-10-05T15:00')
def visible_answer(events):
answer = ''
for item in events:
if item.get('type') == 'final_response':
answer = item.get('content') or ''
elif isinstance(item.get('delta'), str):
if item.get('replacement_scope') == 'turn':
answer = ''
answer += item['delta']
return answer
async def main():
if os.environ.get('ODYSSEUS_ORGANIZER_TRACE'):
original_choice = runner.provider_compatible_tool_choice_request
original_lines = runner.preview_lines_until_finish
def capture_request(request, model):
request = original_choice(request, model)
if os.environ.get('ODYSSEUS_ORGANIZER_AUTO_CHOICE') and request.get('tools'):
request = {**request, 'tool_choice': 'auto'}
print('WIRE_REQUEST', json.dumps({key: request.get(key) for key in
('model', 'tool_choice', 'tools', 'max_tokens', 'stop')}), flush=True)
if os.environ.get('ODYSSEUS_ORGANIZER_TRACE_MESSAGES'):
print('WIRE_MESSAGES', json.dumps(request.get('messages', [])), flush=True)
return request
async def capture_lines(response, finish_event):
arguments = {}
async for line in original_lines(response, finish_event):
if line.startswith('data: ') and line[6:] != '[DONE]':
payload = json.loads(line[6:])
for choice in payload.get('choices', []):
for call in choice.get('delta', {}).get('tool_calls', []):
index = call['index']
arguments[index] = arguments.get(index, '') + call.get('function', {}).get('arguments', '')
yield line
print('WIRE_ARGUMENTS', json.dumps(arguments), flush=True)
runner.provider_compatible_tool_choice_request = capture_request
runner.preview_lines_until_finish = capture_lines
failures = []
for case, prompt, expected in CASES:
if os.environ.get('ODYSSEUS_ORGANIZER_CASES') and case not in os.environ['ODYSSEUS_ORGANIZER_CASES'].split(','):
continue
calls = []
owner = 'fixture-' + case
history, original_id = await seed_followup(case, owner)
async def execute(block, **kwargs):
print('PROPOSED', case, block.tool_type, block.content[:2000], flush=True)
if block.tool_type != expected:
return block.tool_type, {'exit_code': 1, 'error': 'This recording fixture only implements the requested organizer tool.'}
args = json.loads(block.content)
schema = next(s['function']['parameters'] for s in FUNCTION_TOOL_SCHEMAS
if s['function']['name'] == expected)
jsonschema.validate(args, schema)
calls.append(args)
if expected == 'manage_notes':
return expected, await do_manage_notes(block.content, owner=owner)
if expected == 'manage_tasks':
return expected, await do_manage_tasks(block.content, owner=owner)
return expected, await do_manage_calendar(block.content, owner=owner)
runner.execute_tool_block = execute
policy = ToolPolicy()
contract = resolve_turn_contract(
capabilities=requested_capabilities(prompt, history), schemas=FUNCTION_TOOL_SCHEMAS,
policy=policy, selected_tools=selected_tools_for_request(prompt),
required_tools=selected_tools_for_request(prompt) or (), message=prompt, history=history,
)
events = []
try:
async for chunk in runner.stream_preview(
endpoint_url=os.environ['ODYSSEUS_EDITOR_TEST_ENDPOINT'], model='Ajax', headers={},
messages=[{'role': 'user', 'content': prompt}], turn_contract=contract,
history_session=SimpleNamespace(history=history),
session_id='fixture-organizer-' + case, owner=owner,
disabled_tools=set(), tool_policy=policy, thinking_mode='off', max_rounds=4,
):
if chunk.startswith('data: ') and '[DONE]' not in chunk:
events.append(json.loads(chunk[6:]))
answer = visible_answer(events)
if case in {'checklist-unfinished', 'checklist-finished'}:
assert all(args['action'] in {'view', 'list', 'search'} for args in calls), calls
expected_words = ('keys', 'sam') if case == 'checklist-unfinished' else ('milk',)
excluded_words = ('milk',) if case == 'checklist-unfinished' else ('keys', 'sam')
assert all(word in answer.lower() for word in expected_words), answer
assert not any(word in answer.lower() for word in excluded_words), answer
if case in {'calendar-no-change', 'calendar-wording'} or case.startswith('calendar-hypothetical'):
assert all(args['action'] in {'list_events', 'list_calendars'} for args in calls), calls
assert answer.strip(), f'Empty answer; events: {events}'
assert 'Open the document you want reviewed' not in answer, answer
if case.startswith('calendar-hypothetical'):
pattern = (r'\b(?:12:00|12(?::00)?\s*p\.?m\.?|noon)\b'
if case == 'calendar-hypothetical-earlier'
else r'\b(?:16:00|4(?::00)?\s*p\.?m\.?)\b')
assert re.search(pattern, answer, re.I), answer
with database.SessionLocal() as db:
if expected == 'manage_notes':
rows = db.query(database.Note).filter_by(owner=owner).all()
assert len(rows) == 1, f'Expected one note, got {len(rows)}'
row = rows[0]
if case == 'note-append':
assert row.id == original_id
assert row.title == 'Project ideas'
assert 'Build a small weather display.' in row.content
assert row.content.rstrip().endswith('Check the battery life.')
elif case.startswith('checklist-'):
assert row.id == original_id
assert row.title == ('Weekend errands' if case == 'checklist-rename' else 'Errands')
assert row.note_type == 'checklist'
wanted = [{'text': 'buy milk', 'done': case != 'checklist-uncheck'}]
if case != 'checklist-remove':
wanted.append({'text': 'return the keys', 'done': False})
wanted.append({'text': 'call Sam', 'done': case == 'checklist-check'})
if case == 'checklist-reorder':
wanted.insert(0, wanted.pop())
elif case == 'checklist-append':
wanted.append({'text': 'pick up parcel', 'done': False})
assert json.loads(row.items) == wanted, f'Expected {wanted}, got {row.items}'
else:
validate_case(case, {'action': 'add', 'title': row.title, 'content': row.content or '',
'note_type': row.note_type, 'checklist_items': json.loads(row.items or '[]')})
elif expected == 'manage_tasks':
rows = db.query(database.ScheduledTask).filter_by(owner=owner).all()
assert len(rows) == 1, f'Expected one task, got {len(rows)}'
row = rows[0]
if case.startswith('task-cron-'):
from croniter import croniter
from datetime import datetime
assert row.id == original_id and row.name == 'Tech digest'
assert row.prompt == 'Summarize technology news.'
assert row.status == ('paused' if case == 'task-cron-pause' else 'active')
cron = croniter(row.cron_expression, datetime(2026, 10, 4))
days = (6, 8, 13) if case == 'task-cron-days' else (5, 7, 9)
hour, minute = (10, 30) if case == 'task-cron-retime' else (9, 15)
assert [cron.get_next(datetime) for _ in range(3)] == [
datetime(2026, 10, day, hour, minute) for day in days]
assert row.next_run is not None
elif case in {'task-pause', 'task-retime', 'task-rename'}:
assert row.id == original_id
assert row.name == ('Morning briefing' if case == 'task-rename' else 'Tech digest')
assert row.prompt == 'Summarize technology news.'
assert row.schedule == 'daily'
assert row.scheduled_time == ('08:45' if case == 'task-retime' else '07:30')
assert row.status == ('paused' if case == 'task-pause' else 'active')
elif case == 'task-offset':
assert row.schedule == 'daily' and row.scheduled_time == '22:30'
assert 'technology' in row.prompt.lower()
elif case == 'task-weekdays':
from croniter import croniter
from datetime import datetime
assert row.schedule == 'cron' and row.cron_expression
cron = croniter(row.cron_expression, datetime(2026, 10, 4, 0, 0))
assert [cron.get_next(datetime) for _ in range(3)] == [
datetime(2026, 10, 5, 9, 15), datetime(2026, 10, 7, 9, 15),
datetime(2026, 10, 9, 9, 15)]
assert row.next_run is not None
elif case in {'weekly', 'task-monthly', 'task-monthly-day', 'task-monthly-time'}:
from datetime import datetime
from src.task_scheduler import compute_next_run
after = datetime(2026, 10, 4)
actual = []
for _ in range(3):
after = compute_next_run(row.schedule, row.scheduled_time,
row.scheduled_day, after=after, cron_expression=row.cron_expression)
actual.append(after)
wanted = ([datetime(2026, 10, day, 9) for day in (5, 12, 19)] if case == 'weekly'
else [datetime(2026, month, 15, 8) for month in (10, 11, 12)])
if original_id:
assert row.id == original_id and row.name == 'Budget review'
assert row.prompt == 'Review my budget.' and row.status == 'active'
day = 20 if case == 'task-monthly-day' else 15
hour, minute = (11, 45) if case == 'task-monthly-time' else (8, 0)
wanted = [datetime(2026, month, day, hour, minute) for month in (10, 11, 12)]
assert actual == wanted, f'Expected {wanted}, got {actual}'
else:
validate_case(case, {'action': 'create', 'name': row.name, 'prompt': row.prompt,
'schedule': row.schedule, 'scheduled_time': row.scheduled_time, 'scheduled_day': row.scheduled_day})
else:
rows = db.query(database.CalendarEvent).join(database.CalendarCal).filter(
database.CalendarCal.owner == owner).all()
assert len(rows) == 1, f'Expected one calendar event, got {len(rows)}'
row = rows[0]
if original_id:
assert row.uid == original_id, 'Follow-up must update the existing event'
print('SAVED_CALENDAR', json.dumps({'summary': row.summary,
'start': row.dtstart.isoformat(), 'end': row.dtend.isoformat(),
'is_utc': row.is_utc, 'all_day': row.all_day}), flush=True)
assert not row.rrule, 'One-off event unexpectedly recurs'
assert not row.caldav_sync_pending, 'Fixture must not sync externally'
if case == 'calendar-all-day':
assert row.summary == 'Office closed' and row.all_day
assert row.dtstart.isoformat() == '2026-10-08T00:00:00'
assert row.dtend.isoformat() == '2026-10-09T00:00:00'
elif case in CALENDAR_EXPECTED:
title, start, end = CALENDAR_EXPECTED[case]
assert row.summary == title and row.is_utc and not row.all_day
assert row.dtstart.isoformat() == start, f'Expected UTC start {start}, got {row.dtstart}'
assert row.dtend.isoformat() == end, f'Expected UTC end {end}, got {row.dtend}'
else:
assert row.is_utc and not row.all_day
validate_case(case, {'action': 'create_event', 'summary': row.summary,
'dtstart': row.dtstart.isoformat(), 'dtend': row.dtend.isoformat()})
passed = True
except Exception as exc:
passed = False
failures.append(case)
print(case, type(exc).__name__, str(exc), flush=True)
outputs = [e for e in events if e.get('type') == 'tool_output']
print(json.dumps({'case': case, 'passed': passed, 'calls': calls,
'outputs': outputs, 'answer': visible_answer(events)}, ensure_ascii=False), flush=True)
if failures:
raise SystemExit('Failed: ' + ', '.join(failures))
if __name__ == '__main__':
with tempfile.TemporaryDirectory(prefix='organizer-fixture-') as tmp:
engine = create_engine('sqlite:///' + tmp + '/test.db')
database.Base.metadata.create_all(engine)
database.SessionLocal = sessionmaker(bind=engine)
try:
asyncio.run(main())
finally:
engine.dispose()