2 Commits

Author SHA1 Message Date
pewdiepie-archdaemon dc3530b8fa Show fallback model in picker 2026-07-01 13:53:51 +00:00
pewdiepie-archdaemon 2918739489 Fix merged test regressions 2026-07-01 11:12:55 +00:00
12 changed files with 37 additions and 9 deletions
+5 -3
View File
@@ -1359,9 +1359,11 @@ def setup_chat_routes(
elif chunk.startswith("event: "): elif chunk.startswith("event: "):
yield chunk yield chunk
elif chunk == "data: [DONE]\n\n": elif chunk == "data: [DONE]\n\n":
if full_response: _has_tool_events = bool((last_metrics or {}).get("tool_events"))
if full_response or _has_tool_events:
_response_to_save = full_response or "Done."
_saved_id = save_assistant_response( _saved_id = save_assistant_response(
sess, session_manager, session, full_response, last_metrics, sess, session_manager, session, _response_to_save, last_metrics,
character_name=ctx.preset.character_name, character_name=ctx.preset.character_name,
web_sources=web_sources, web_sources=web_sources,
rag_sources=ctx.rag_sources, rag_sources=ctx.rag_sources,
@@ -1371,7 +1373,7 @@ def setup_chat_routes(
if _saved_id: if _saved_id:
yield f'data: {json.dumps({"type": "message_saved", "id": _saved_id})}\n\n' yield f'data: {json.dumps({"type": "message_saved", "id": _saved_id})}\n\n'
run_post_response_tasks( run_post_response_tasks(
sess, session_manager, session, message, full_response, sess, session_manager, session, message, _response_to_save,
last_metrics, ctx.uprefs, memory_manager, memory_vector, webhook_manager, last_metrics, ctx.uprefs, memory_manager, memory_vector, webhook_manager,
incognito=incognito, compare_mode=compare_mode, incognito=incognito, compare_mode=compare_mode,
character_name=ctx.preset.character_name, character_name=ctx.preset.character_name,
+2
View File
@@ -2388,6 +2388,8 @@ def setup_email_routes():
owner: str = Depends(require_owner), owner: str = Depends(require_owner),
): ):
"""Read email body. Cached for 30m, sync IMAP work runs in a thread.""" """Read email body. Cached for 30m, sync IMAP work runs in a thread."""
mark_seen = True if mark_seen is True or str(mark_seen).lower() == "true" else False
full = True if full is True or str(full).lower() == "true" else False
fixture_result = _fixture_email_read(uid, folder, owner) fixture_result = _fixture_email_read(uid, folder, owner)
if fixture_result is not None: if fixture_result is not None:
return fixture_result return fixture_result
+2
View File
@@ -218,6 +218,8 @@ def _default_endpoint_needs_assignment(
return True return True
if current_default_id not in enabled_endpoint_ids: if current_default_id not in enabled_endpoint_ids:
return True return True
if current_default_endpoint is None:
return False
if not (current_default_model or "").strip(): if not (current_default_model or "").strip():
return True return True
visible = _endpoint_visible_model_ids(current_default_endpoint) visible = _endpoint_visible_model_ids(current_default_endpoint)
+2
View File
@@ -137,6 +137,8 @@ def setup_upload_routes(upload_handler):
session_id: Optional[str] = Form(None), session_id: Optional[str] = Form(None),
): ):
"""Upload files with enhanced security and organization.""" """Upload files with enhanced security and organization."""
if not isinstance(session_id, str):
session_id = None
if not files: if not files:
raise HTTPException(400, "No files uploaded") raise HTTPException(400, "No files uploaded")
+7
View File
@@ -254,6 +254,7 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
"calendar_href": ev.calendar_id, "calendar_href": ev.calendar_id,
"event_type": ev.event_type or "", "event_type": ev.event_type or "",
"importance": ev.importance or "normal", "importance": ev.importance or "normal",
"rrule": ev.rrule or "",
}) })
if not events: if not events:
response_text = f"No events between {start_dt.date().isoformat()} and {end_dt.date().isoformat()}." response_text = f"No events between {start_dt.date().isoformat()} and {end_dt.date().isoformat()}."
@@ -268,6 +269,8 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
line += f" #{ev['event_type']}" line += f" #{ev['event_type']}"
if ev.get("importance") and ev["importance"] != "normal": if ev.get("importance") and ev["importance"] != "normal":
line += f" !{ev['importance']}" line += f" !{ev['importance']}"
if ev.get("rrule"):
line += f" repeats({ev['rrule']})"
if ev.get("location"): if ev.get("location"):
line += f" @ {ev['location']}" line += f" @ {ev['location']}"
if ev.get("calendar"): if ev.get("calendar"):
@@ -480,6 +483,10 @@ async def do_manage_calendar(content: str, owner: Optional[str] = None) -> Dict:
ev.event_type = _tag or None ev.event_type = _tag or None
if args.get("importance") is not None: if args.get("importance") is not None:
ev.importance = args["importance"] ev.importance = args["importance"]
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"}:
ev.rrule = ""
is_caldav = ev.calendar and ev.calendar.source == "caldav" is_caldav = ev.calendar and ev.calendar.source == "caldav"
if is_caldav: if is_caldav:
ev.caldav_sync_pending = "update" ev.caldav_sync_pending = "update"
+1
View File
@@ -4051,6 +4051,7 @@ function startOdysseusApp() {
// Non-critical: load in parallel, resolve silently // Non-critical: load in parallel, resolve silently
modelsModule.refreshModels(false).then(() => { modelsModule.refreshModels(false).then(() => {
try { sessionModule.updateModelPicker(); } catch (_) {}
const modelsBox = document.getElementById('models'); const modelsBox = document.getElementById('models');
const hasModels = modelsBox && modelsBox.querySelector('.models-row'); const hasModels = modelsBox && modelsBox.querySelector('.models-row');
if (!hasModels) { if (!hasModels) {
+12
View File
@@ -754,6 +754,18 @@ export function updateModelPicker() {
// silently pre-populate the chatbox of the next user that signed in. If // silently pre-populate the chatbox of the next user that signed in. If
// we have no session model and no pending-chat pick, fall through to // we have no session model and no pending-chat pick, fall through to
// the "Select model" placeholder below. // the "Select model" placeholder below.
//
// But if the server model cache already has an online endpoint, make the
// same safe fallback visible in the picker immediately. The send path can
// already resolve a usable model; the UI should not sit on "Select model"
// and make it look broken.
if (!modelId && !currentSessionId && window.modelsModule && window.modelsModule.getCachedItems) {
const fallback = _firstAvailableModel();
if (fallback) {
_deps.setPendingChat(fallback);
modelId = fallback.modelId;
}
}
// Check if selected model is still available — fall back ONLY for pending chats with no user selection // Check if selected model is still available — fall back ONLY for pending chats with no user selection
// Never override an existing session's model — the user explicitly chose it // Never override an existing session's model — the user explicitly chose it
+1 -1
View File
@@ -1017,7 +1017,7 @@ function _showTaskDropdown(anchor, items) {
const dd = document.createElement('div'); const dd = document.createElement('div');
dd.className = 'task-dropdown'; dd.className = 'task-dropdown';
dd._anchor = anchor; dd._anchor = anchor;
dd.style.cssText = 'position:fixed;z-index:100000;background:var(--panel);border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.3);padding:4px;min-width:120px;'; dd.style.cssText = `position:fixed;z-index:${topPortalZ()};background:var(--panel);border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.3);padding:4px;min-width:120px;`;
items.forEach(item => { items.forEach(item => {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.style.cssText = 'display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:6px 10px;border:none;background:none;color:var(--fg);font-size:11px;font-family:inherit;cursor:pointer;border-radius:4px;transition:background 0.1s;'; btn.style.cssText = 'display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:6px 10px;border:none;background:none;color:var(--fg);font-size:11px;font-family:inherit;cursor:pointer;border-radius:4px;transition:background 0.1s;';
+1 -1
View File
@@ -86,7 +86,7 @@ def test_expand_non_recurring_returns_single():
def test_expand_rrule_skips_deleted_occurrence_exdate(): def test_expand_rrule_skips_deleted_occurrence_exdate():
cal = _import_calendar_helpers() cal = import_calendar_routes()
ev = _make_event( ev = _make_event(
dtstart=datetime(2026, 7, 1, 14, 0), dtstart=datetime(2026, 7, 1, 14, 0),
dtend=datetime(2026, 7, 1, 15, 0), dtend=datetime(2026, 7, 1, 15, 0),
+1 -1
View File
@@ -102,7 +102,7 @@ def test_local_windows_platform_comes_from_backend_host_state():
assert "platform: _envState.hostPlatform || _envState.platform || ''" not in text assert "platform: _envState.hostPlatform || _envState.platform || ''" not in text
assert 'return "windows" if IS_WINDOWS else ""' in routes assert 'return "windows" if IS_WINDOWS else ""' in routes
assert 'env["hostPlatform"] = _client_host_platform()' in routes assert 'env["hostPlatform"] = _client_host_platform()' in routes
assert "return _state_for_client({})" in routes assert "client_state = _state_for_client({})" in routes
assert 'env.pop("hostPlatform", None)' in routes assert 'env.pop("hostPlatform", None)' in routes
assert "delete env.hostPlatform;" in running assert "delete env.hostPlatform;" in running
@@ -35,8 +35,8 @@ def test_windows_session_commands_use_shared_powershell_wrapper_and_local_log_di
assert "host ? '$env:TEMP\\\\odysseus-sessions' : '$env:TEMP\\\\odysseus-tmux'" in source assert "host ? '$env:TEMP\\\\odysseus-sessions' : '$env:TEMP\\\\odysseus-tmux'" in source
assert "function _winPowerShellCmd(task, ps)" in source assert "function _winPowerShellCmd(task, ps)" in source
assert "const command = `powershell -Command \"${ps}\"`;" in source assert "const command = `powershell -Command \"${ps}\"`;" in source
assert "if (!task.remoteHost) return command;" in source assert "if (!host) return command;" in source
assert "return `ssh ${_sshPrefix(_getPort(task))}${task.remoteHost} ${_shQuote(command)}`;" in source assert "return `ssh ${_sshPrefix(_getPort(task))}${host} ${_shQuote(command)}`;" in source
def test_dep_install_success_recognized_from_exit_sentinel(): def test_dep_install_success_recognized_from_exit_sentinel():
+1 -1
View File
@@ -17,7 +17,7 @@ SRC = Path(__file__).resolve().parent.parent / "static/js/fileHandler.js"
def _upload_pending_body() -> str: def _upload_pending_body() -> str:
text = SRC.read_text(encoding="utf-8") text = SRC.read_text(encoding="utf-8")
start = text.index("export async function uploadPending()") start = text.index("export async function uploadPending(")
rest = text[start:] rest = text[start:]
m = re.search(r"\n(export |function )", rest[1:]) m = re.search(r"\n(export |function )", rest[1:])
return rest[: m.start() + 1] if m else rest return rest[: m.start() + 1] if m else rest