Stabilize local dev merge

Align regression tests with the current Odysseus behavior after merging origin/dev into local main.

- keep phone/name-only contacts valid and cover null email without crashes

- pin explicit web-search false form submission in chat.js

- update Cookbook dependency/download completion tests for combined live + persisted output

- expose SGLang OS package repair hints from backend diagnosis

- treat MLX and MLX-community repos as servable on Apple Metal while keeping CUDA behavior unchanged

- keep desktop new-chat coverage on the shared preferred-model helper

- remove a hardcoded crop overlay portal z-index literal

- include the local agent-loop cleanup that removes the old manage_notes reminder repair shim

Verified with: docker run --rm -v /home/pewds/odysseus-cookbook-fresh:/app -w /app odysseus-cookbook-fresh-odysseus python3 -m pytest -q (4515 passed, 4 skipped).
This commit is contained in:
pewdiepie-archdaemon
2026-07-07 01:15:20 +00:00
parent 4c24d5d9a6
commit c42609755f
12 changed files with 55 additions and 78 deletions
+1
View File
@@ -1306,6 +1306,7 @@ def _diagnose_serve_output(text: str) -> dict | None:
"SGLang native kernel/runtime is missing or mismatched on this server.", "SGLang native kernel/runtime is missing or mismatched on this server.",
[ [
{"label": "repair sglang-kernel in this Python environment", "op": "dependency", "package": "sglang-kernel"}, {"label": "repair sglang-kernel in this Python environment", "op": "dependency", "package": "sglang-kernel"},
{"label": "install OS packages: libnuma-dev python3.12-dev build-essential", "op": "manual"},
{"label": "if libnvrtc is still missing, install the matching CUDA/NVRTC runtime on this host", "op": "manual"}, {"label": "if libnvrtc is still missing, install the matching CUDA/NVRTC runtime on this host", "op": "manual"},
], ],
), ),
-45
View File
@@ -796,50 +796,6 @@ def _extract_last_user_message(messages: List[Dict]) -> str:
return "" return ""
_REMINDER_TIME_RE = re.compile(
r"\b(?:today|tonight|tomorrow|tmrw|yesterday)\b(?:\s+(?:at\s+)?\d{1,2}(?::\d{2})?\s*(?:am|pm)?)?"
r"|\b\d{1,2}(?::\d{2})?\s*(?:am|pm)?\s+(?:today|tonight|tomorrow|tmrw|yesterday)\b"
r"|\bin\s+\d+\s*(?:hour|hr|minute|min|day)s?\b",
re.IGNORECASE,
)
def _extract_reminder_due_from_user(text: str) -> str:
t = str(text or "").strip()
if not re.search(r"\b(remind|reminder|alarm)\b", t, re.IGNORECASE):
return ""
m = _REMINDER_TIME_RE.search(t)
return m.group(0).strip() if m else ""
def _repair_manage_notes_reminder_block(block: ToolBlock, last_user: str) -> ToolBlock:
"""Carry reminder time from the user message when the model omits due_date."""
if block.tool_type != "manage_notes":
return block
try:
args = json.loads(block.content or "{}")
except Exception:
return block
if not isinstance(args, dict) or args.get("due_date"):
return block
action = str(args.get("action") or "").replace("-", "_").strip().lower()
if action not in {"add", "create", "new", "save", "remind", "reminder"}:
return block
due = _extract_reminder_due_from_user(last_user)
if not due:
return block
args["due_date"] = due
if not args.get("title"):
cleaned = re.sub(r"\b(make|create|add|set)\b", "", str(last_user or ""), flags=re.IGNORECASE)
cleaned = re.sub(r"\b(a|an)?\s*(reminder|alarm)\b", "", cleaned, flags=re.IGNORECASE)
cleaned = _REMINDER_TIME_RE.sub("", cleaned)
cleaned = re.sub(r"\s+", " ", cleaned).strip(" :,-") or "Reminder"
args["title"] = cleaned[:120]
return ToolBlock(block.tool_type, json.dumps(args, ensure_ascii=False))
def _user_turn_count(messages: List[Dict]) -> int: def _user_turn_count(messages: List[Dict]) -> int:
"""Count real user turns in the message list.""" """Count real user turns in the message list."""
count = 0 count = 0
@@ -4004,7 +3960,6 @@ async def stream_agent_loop(
tool_result_texts = [] # plain text for native tool role messages tool_result_texts = [] # plain text for native tool role messages
budget_hit = False budget_hit = False
for i, block in enumerate(tool_blocks): for i, block in enumerate(tool_blocks):
block = _repair_manage_notes_reminder_block(block, _last_user)
# --- Tool budget check --- # --- Tool budget check ---
if max_tool_calls > 0 and total_tool_calls >= max_tool_calls: if max_tool_calls > 0 and total_tool_calls >= max_tool_calls:
yield f'data: {json.dumps({"type": "budget_exceeded", "limit": max_tool_calls, "used": total_tool_calls})}\n\n' yield f'data: {json.dumps({"type": "budget_exceeded", "limit": max_tool_calls, "used": total_tool_calls})}\n\n'
+1 -1
View File
@@ -8688,7 +8688,7 @@ button.hamburger {
.attach-crop-overlay { .attach-crop-overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 100000; z-index: var(--attach-crop-z, 100001);
background: rgba(0, 0, 0, 0.72); background: rgba(0, 0, 0, 0.72);
display: flex; display: flex;
align-items: center; align-items: center;
+1 -1
View File
@@ -254,6 +254,6 @@ def test_frontend_always_sends_explicit_allow_bash():
def test_frontend_sends_explicit_allow_web_search_false_in_agent_mode(): def test_frontend_sends_explicit_allow_web_search_false_in_agent_mode():
"""chat.js must send allow_web_search=false when web toggle is off in agent mode.""" """chat.js must send allow_web_search=false when web toggle is off in agent mode."""
source = _CHAT_JS.read_text(encoding="utf-8") source = _CHAT_JS.read_text(encoding="utf-8")
assert "allow_web_search', 'false'" in source, ( assert "fd.append('allow_web_search', el('web-toggle').checked ? 'true' : 'false')" in source, (
"Frontend must send explicit allow_web_search=false in agent mode when toggle is off" "Frontend must send explicit allow_web_search=false in agent mode when toggle is off"
) )
+16 -4
View File
@@ -24,7 +24,11 @@ def _add_handler():
def _stub_store(monkeypatch): def _stub_store(monkeypatch):
created = [] created = []
monkeypatch.setattr(cr, "_fetch_contacts", lambda *a, **k: []) monkeypatch.setattr(cr, "_fetch_contacts", lambda *a, **k: [])
monkeypatch.setattr(cr, "_create_contact", lambda name, email: created.append((name, email)) or True) monkeypatch.setattr(
cr,
"_create_contact",
lambda name, email="", address="", phones=None: created.append((name, email, address, phones or [])) or True,
)
return created return created
@@ -33,10 +37,18 @@ def test_null_name_does_not_crash(_stub_store):
result = asyncio.run(handler({"name": None, "email": "x@y.com"}, _admin="admin")) result = asyncio.run(handler({"name": None, "email": "x@y.com"}, _admin="admin"))
assert result["success"] is True assert result["success"] is True
# name fell back to the email local-part instead of crashing. # name fell back to the email local-part instead of crashing.
assert _stub_store == [("x", "x@y.com")] assert _stub_store == [("x", "x@y.com", "", [])]
def test_null_email_is_rejected_cleanly(_stub_store): def test_null_email_does_not_crash(_stub_store):
handler = _add_handler() handler = _add_handler()
result = asyncio.run(handler({"name": "Bob", "email": None}, _admin="admin")) result = asyncio.run(handler({"name": "Bob", "email": None}, _admin="admin"))
assert result == {"success": False, "error": "Email required"} assert result["success"] is True
assert _stub_store == [("Bob", "", "", [])]
def test_phone_only_contact_is_allowed(_stub_store):
handler = _add_handler()
result = asyncio.run(handler({"name": "Bob", "email": None, "phone": "0805412 7841"}, _admin="admin"))
assert result["success"] is True
assert _stub_store == [("Bob", "", "", ["0805412 7841"])]
@@ -20,7 +20,9 @@ def test_background_status_poll_reconciles_into_local_tasks():
source = _read("static/js/cookbookRunning.js") source = _read("static/js/cookbookRunning.js")
assert "const statusById = new Map(tasks.map(t => [t.session_id, t]));" in source assert "const statusById = new Map(tasks.map(t => [t.session_id, t]));" in source
assert "const nextStatus = live.status === 'completed'" in source assert "const completedByOutput = depDone || downloadDone;" in source
assert "const nextStatus = completedByOutput" in source
assert "live.status === 'completed'" in source
assert "? 'done'" in source assert "? 'done'" in source
assert ": (live.status === 'error'" in source assert ": (live.status === 'error'" in source
assert "? 'error'" in source assert "? 'error'" in source
@@ -75,7 +77,8 @@ def test_background_poll_recovers_done_for_stopped_dependency_install():
downgrading the card to crashed.""" downgrading the card to crashed."""
source = _read("static/js/cookbookRunning.js") source = _read("static/js/cookbookRunning.js")
assert "const depDone = !!task.payload?._dep && _depInstallSucceeded(task.output);" in source assert "const combinedOutput = `${task.output || ''}\\n${live.output_tail || ''}`;" in source
assert "const depDone = !!task.payload?._dep && _depInstallSucceeded(combinedOutput);" in source
assert "(depDone || downloadDone) ? 'done' : (task.type === 'download' ? 'crashed' : 'stopped')" in source assert "(depDone || downloadDone) ? 'done' : (task.type === 'download' ? 'crashed' : 'stopped')" in source
@@ -91,14 +94,14 @@ def test_background_poll_recovers_done_for_completed_download():
normalized = " ".join(source.split()) normalized = " ".join(source.split())
assert ( assert (
"const downloadDone = task.type === 'download' " "const downloadDone = task.type === 'download' "
"&& String(task.output || '').includes('DOWNLOAD_OK');" "&& String(combinedOutput || '').includes('DOWNLOAD_OK');"
) in normalized ) in normalized
def test_dependency_install_payload_keeps_env_path_for_refresh(): def test_dependency_install_payload_keeps_env_path_for_refresh():
source = _read("static/js/cookbook.js") source = _read("static/js/cookbook.js")
assert "env_path: _envState.envPath || ''" in source assert "env_path: targetEnvPath || ''" in source
def test_local_dependency_probe_refreshes_user_site_visibility(): def test_local_dependency_probe_refreshes_user_site_visibility():
+1 -1
View File
@@ -29,7 +29,7 @@ def test_diagnose_sglang_native_dependency_errors():
diagnosis = _diagnose_serve_output(output) diagnosis = _diagnose_serve_output(output)
assert diagnosis is not None assert diagnosis is not None
assert "SGLang native dependencies" in diagnosis["message"] assert "SGLang native kernel/runtime" in diagnosis["message"]
labels = [suggestion["label"] for suggestion in diagnosis["suggestions"]] labels = [suggestion["label"] for suggestion in diagnosis["suggestions"]]
assert any("libnuma-dev" in label for label in labels) assert any("libnuma-dev" in label for label in labels)
assert any("python3.12-dev" in label for label in labels) assert any("python3.12-dev" in label for label in labels)
+1 -1
View File
@@ -17,6 +17,6 @@ def test_sglang_native_dependency_diagnosis_is_exposed_to_browser():
assert r"Python\.h" in source assert r"Python\.h" in source
assert r"libnuma\.so\.1" in source assert r"libnuma\.so\.1" in source
assert "SGLang native dependencies" in source assert "SGLang native kernel/runtime" in source
assert "libnuma-dev python3.12-dev build-essential" in source assert "libnuma-dev python3.12-dev build-essential" in source
assert "sglang-kernel" in source assert "sglang-kernel" in source
@@ -31,8 +31,8 @@ def test_selected_server_helpers_prefer_profile_key_before_host_fallback():
def test_cookbook_submodules_resolve_visible_profile_selection(): def test_cookbook_submodules_resolve_visible_profile_selection():
assert "_serverByVal?.(_ssv)" in DOWNLOAD assert "_serverByVal?.(_ssv)" in DOWNLOAD
assert "_serverByVal?.(_envState.remoteServerKey || host)" in DOWNLOAD assert "_serverByVal?.(_envState.remoteServerKey || _envState.remoteHost || '')" in DOWNLOAD
assert "_serverByVal?.(_envState.remoteServerKey || _zh)" in DOWNLOAD assert "_serverByVal?.(zombieCandidate.remoteServerKey || zombieCandidate.payload?.remote_server_key || _zh)" in DOWNLOAD
assert "_serverByVal(_envState.remoteServerKey || remoteHost)" in HWFIT assert "_serverByVal(_envState.remoteServerKey || remoteHost)" in HWFIT
assert "hk: _currentServerValue()" in HWFIT assert "hk: _currentServerValue()" in HWFIT
assert "sel.value = _currentServerValue();" in HWFIT assert "sel.value = _currentServerValue();" in HWFIT
+11 -11
View File
@@ -43,13 +43,12 @@ def _fake_sysctl(brand="Apple M2 Pro", memsize_gb=32, wired_mb=None, display_jso
return run return run
def test_mlx_models_hidden_on_metal(): def test_mlx_models_visible_on_metal():
"""MLX-quantized models can't be served by llama.cpp or Ollama (the only """Apple Silicon can serve MLX models through the MLX engine, so Cookbook
Metal-capable engines Odysseus generates), so they must never be recommended should surface MLX rows on Metal while still hiding them on CUDA."""
on Apple Silicon even though the catalog tags them as Apple-only."""
results = rank_models(_metal_system(), limit=900) results = rank_models(_metal_system(), limit=900)
mlx = [m for m in results if str(m.get("quant", "")).startswith("mlx-")] mlx = [m for m in results if str(m.get("quant", "")).startswith("mlx-")]
assert mlx == [], f"MLX models surfaced but cannot be served: {[m['name'] for m in mlx]}" assert mlx, "MLX models should be available on Apple Silicon / Metal hardware"
def _cuda_system(): def _cuda_system():
@@ -65,17 +64,18 @@ def test_mlx_hidden_on_cuda_backend_unchanged():
assert mlx == [] assert mlx == []
def test_only_gguf_models_recommended_on_metal(): def test_only_gguf_or_mlx_models_recommended_on_metal():
"""llama.cpp and Ollama (the only Metal engines) need GGUF. Safetensors-only """Metal recommendations must be servable locally: either GGUF for
repos incl. vLLM-only AWQ/GPTQ/FP8 can't be served on Metal, so every llama.cpp/Ollama or MLX for the MLX engine."""
model recommended on Apple Silicon must ship a servable GGUF."""
catalog = {m["name"]: m for m in get_models()} catalog = {m["name"]: m for m in get_models()}
unservable = [ unservable = [
r["name"] for r in rank_models(_metal_system(), limit=900) r["name"] for r in rank_models(_metal_system(), limit=900)
if not (catalog.get(r["name"], {}).get("is_gguf") if not (str(r.get("quant", "")).startswith("mlx-")
or str(r.get("name", "")).startswith(("mlx-community/", "lmstudio-community/"))
or catalog.get(r["name"], {}).get("is_gguf")
or catalog.get(r["name"], {}).get("gguf_sources")) or catalog.get(r["name"], {}).get("gguf_sources"))
] ]
assert unservable == [], f"{len(unservable)} non-GGUF models on Metal, e.g. {unservable[:3]}" assert unservable == [], f"{len(unservable)} non-servable models on Metal, e.g. {unservable[:3]}"
def test_qwen_catalog_entries_point_at_verified_gguf_repos(): def test_qwen_catalog_entries_point_at_verified_gguf_repos():
+6 -6
View File
@@ -67,11 +67,9 @@ def test_manual_ram_mode_wipes_gpu_and_unified_flag():
assert "unified_memory" not in s assert "unified_memory" not in s
def test_simulated_metal_box_only_recommends_gguf(): def test_simulated_metal_box_only_recommends_gguf_or_mlx():
"""End-to-end: a simulated Metal box must rank exactly like a real Mac — """End-to-end: a simulated Metal box must rank exactly like a real Mac —
only models shipping a servable GGUF (llama.cpp/Ollama) survive. Before only locally servable GGUF or MLX models survive."""
'metal' was accepted, this box ranked as CUDA and surfaced safetensors-only
repos the Mac can't serve."""
system = _apply_manual_hardware( system = _apply_manual_hardware(
{"backend": "cuda", "available_ram_gb": 32.0, "total_ram_gb": 64.0}, {"backend": "cuda", "available_ram_gb": 32.0, "total_ram_gb": 64.0},
manual_mode="gpu", manual_vram_gb="48", manual_backend="metal", manual_mode="gpu", manual_vram_gb="48", manual_backend="metal",
@@ -79,7 +77,9 @@ def test_simulated_metal_box_only_recommends_gguf():
catalog = {m["name"]: m for m in get_models()} catalog = {m["name"]: m for m in get_models()}
unservable = [ unservable = [
r["name"] for r in rank_models(system, limit=900) r["name"] for r in rank_models(system, limit=900)
if not (catalog.get(r["name"], {}).get("is_gguf") if not (str(r.get("quant", "")).startswith("mlx-")
or str(r.get("name", "")).startswith(("mlx-community/", "lmstudio-community/"))
or catalog.get(r["name"], {}).get("is_gguf")
or catalog.get(r["name"], {}).get("gguf_sources")) or catalog.get(r["name"], {}).get("gguf_sources"))
] ]
assert unservable == [], f"{len(unservable)} non-GGUF models on simulated Metal, e.g. {unservable[:3]}" assert unservable == [], f"{len(unservable)} non-servable models on simulated Metal, e.g. {unservable[:3]}"
+8 -2
View File
@@ -27,6 +27,11 @@ def test_new_chat_prefers_pending_and_current_model_before_default():
def test_desktop_new_chat_actions_use_shared_preference_helper(): def test_desktop_new_chat_actions_use_shared_preference_helper():
source = APP_JS.read_text(encoding="utf-8") source = APP_JS.read_text(encoding="utf-8")
shared_handler = _slice(
source,
"async function _handleNewChatAction",
"// New session button on icon rail",
)
rail_handler = _slice( rail_handler = _slice(
source, source,
"// New session button on icon rail", "// New session button on icon rail",
@@ -38,7 +43,8 @@ def test_desktop_new_chat_actions_use_shared_preference_helper():
"const sidebarNewChatBtn = el('sidebar-new-chat-btn');", "const sidebarNewChatBtn = el('sidebar-new-chat-btn');",
) )
assert "if (await _createDirectChatFromPreferredModel()) return;" in rail_handler assert "if (preferModel && await _createDirectChatFromPreferredModel()) return;" in shared_handler
assert "if (await _createDirectChatFromPreferredModel()) return;" in brand_handler assert "await _handleNewChatAction();" in rail_handler
assert "await _handleNewChatAction();" in brand_handler
assert "const dc = await _refreshDefaultChat();" not in rail_handler assert "const dc = await _refreshDefaultChat();" not in rail_handler
assert "const dc = await _refreshDefaultChat();" not in brand_handler assert "const dc = await _refreshDefaultChat();" not in brand_handler