diff --git a/README.md b/README.md index 80472242ec562dfa9385ef4d047a742d3f2ec730..072def5bf5ab6bdb23b5ae2e9802660f69c5ec4c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ pinned: false # Global Marine Foundation Data Agent -Current UI release: **v4.6.19**. +Current UI release: **v4.6.20**. ## v3.4.0 稳定性重构(第一阶段) diff --git a/VERSION b/VERSION index 6ee1bd81050822d000a79a767c29d0722ecd0cc2..77217322951bdf2fd5615c671a4f64b304db054a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.6.19 +4.6.20 diff --git a/codex_harness.py b/codex_harness.py index 0c40b01a06c0be20a8728fc95bf4dc8de788196d..a309086d4855a4f0ab6a23bf858f72d62b597b88 100644 --- a/codex_harness.py +++ b/codex_harness.py @@ -12,6 +12,8 @@ from dataclasses import dataclass class CodexResult: final_response: str finish_reason: str = "completed" + tool_names: tuple[str, ...] = () + tool_events: tuple[str, ...] = () class CodexHarness: @@ -171,7 +173,7 @@ class CodexHarness: result = getattr(marine_mcp, short)(**args) return CodexHarness._jsonable(result) - def _request(self, body: dict) -> dict: + def _request(self, body: dict, session_id: str) -> dict: request = urllib.request.Request( self.base_url + "/responses", data=json.dumps(body, ensure_ascii=False).encode("utf-8"), @@ -181,7 +183,7 @@ class CodexHarness: "Content-Type": "application/json", "Accept": "application/json", "User-Agent": "Global-Marine-Foundation/4.5", - "x-opencode-session": str(getattr(self, "_session_id", uuid.uuid4())), + "x-opencode-session": str(session_id), }, ) try: @@ -222,7 +224,7 @@ class CodexHarness: def run(self, prompt, session_id=None): if not self.api_key: raise RuntimeError("OPENCODE_GO_API_KEY is not configured") - self._session_id = str(session_id or uuid.uuid4()) + request_session_id = str(session_id or uuid.uuid4()) full_prompt = (self.system_prompt + "\n\n" + prompt).strip() tools = self._tools() payload = self._request({ @@ -230,7 +232,9 @@ class CodexHarness: "input": full_prompt, "tools": tools, "tool_choice": "auto", - }) + }, request_session_id) + tool_names = [] + tool_events = [] # Responses can contain multiple tool calls. Execute them locally, # then continue the same Responses conversation until text is final. @@ -240,10 +244,18 @@ class CodexHarness: break outputs = [] for call in calls: + if call["name"] not in tool_names: + tool_names.append(call["name"]) try: value = self._run_tool(call["name"], call["arguments"]) except Exception as exc: value = {"status": "error", "detail": str(exc)[:1000]} + if len(tool_events)<40: + tool_events.append(json.dumps({ + "name":call["name"], + "arguments":call["arguments"], + "result":value, + },ensure_ascii=False,default=str)[:12000]) outputs.append({ "type": "function_call_output", "call_id": call["call_id"], @@ -256,11 +268,11 @@ class CodexHarness: "tools": tools, "tool_choice": "auto", } - payload = self._request(next_body) + payload = self._request(next_body, request_session_id) else: raise RuntimeError("OpenCode Go tool loop exceeded 8 rounds") answer = self._text(payload) if not answer: raise RuntimeError("OpenCode Go returned no text: " + json.dumps(payload, ensure_ascii=False)[:1200]) - return CodexResult(final_response=answer) + return CodexResult(final_response=answer, tool_names=tuple(tool_names), tool_events=tuple(tool_events)) diff --git a/codex_native_harness.py b/codex_native_harness.py index 9ad85feb63a31024751d3ae48be60df4c8c484e2..4add4c921b056defb716ac5be27a9a8f092130dc 100644 --- a/codex_native_harness.py +++ b/codex_native_harness.py @@ -9,6 +9,7 @@ from __future__ import annotations import json import os +import re import shutil import subprocess import uuid @@ -20,6 +21,8 @@ from typing import Any class CodexResult: final_response: str finish_reason: str = "completed" + tool_names: tuple[str, ...] = () + tool_events: tuple[str, ...] = () def _text_from_value(value: Any) -> str: @@ -52,6 +55,27 @@ def _event_text(event: dict[str, Any]) -> str: return "" +_MARINE_TOOL_RE = re.compile(r"\bmcp_marine_[A-Za-z0-9_]+\b") + +def _tool_names_from_event(event: dict[str, Any]) -> list[str]: + if not isinstance(event, dict): + return [] + event_type=str(event.get("type") or "").lower() + item=event.get("item") if isinstance(event.get("item"),dict) else {} + item_type=str(item.get("type") or "").lower() + tool_shaped=( + any(token in event_type for token in ("tool","mcp","function")) + or any(token in item_type for token in ("tool","mcp","function")) + ) + if not tool_shaped: + return [] + try: + raw=json.dumps(event,ensure_ascii=False,default=str) + except Exception: + raw=str(event) + return list(dict.fromkeys(_MARINE_TOOL_RE.findall(raw))) + + class CodexNativeHarness: """Run the official Codex CLI with the configured MCP server.""" @@ -142,6 +166,8 @@ class CodexNativeHarness: raise RuntimeError(f"无法启动官方 Codex CLI: {exc}") from exc candidates: list[str] = [] + tool_names: list[str] = [] + tool_events: list[str] = [] for line in (completed.stdout or "").splitlines(): raw = line.strip() if not raw: @@ -154,6 +180,16 @@ class CodexNativeHarness: text = _event_text(event) if text: candidates.append(text) + names=_tool_names_from_event(event) + if names: + for name in names: + if name not in tool_names: + tool_names.append(name) + if len(tool_events)<40: + try: + tool_events.append(json.dumps(event,ensure_ascii=False,default=str)[:12000]) + except Exception: + tool_events.append(str(event)[:12000]) if completed.returncode != 0: stderr_detail = (completed.stderr or "").strip()[-1200:] @@ -172,4 +208,4 @@ class CodexNativeHarness: answer = candidates[-1].strip() if candidates else "" if not answer: raise RuntimeError("官方 Codex CLI 没有返回最终文本") - return CodexResult(final_response=answer) + return CodexResult(final_response=answer, tool_names=tuple(tool_names), tool_events=tuple(tool_events)) diff --git a/services/chat_runtime.py b/services/chat_runtime.py index 358575a425ce380cb5ba8533fbfc75e53d99a6c7..6623e9ba35993e976d82875db9929e410808f8b6 100644 --- a/services/chat_runtime.py +++ b/services/chat_runtime.py @@ -535,11 +535,30 @@ async def harness_stream_chat(tid,prompt): tid, USER_SYSTEM, ) + history_context="" + try: + history_context=_format_recent_thread_history( + tid, + limit=12, + max_chars=24000, + ) + except Exception: + history_context="" harness_prompt=( "[APPLICATION_CONTEXT]\n" + app_context + "\n[/APPLICATION_CONTEXT]\n\n" + + ( + "[PRIOR_CONVERSATION_HISTORY]\n" + + history_context + + "\n[/PRIOR_CONVERSATION_HISTORY]\n\n" + + "[HISTORY_INSTRUCTION]\n" + + "Use prior conversation only to resolve references and continuity. " + "The current user message has priority over older content.\n" + + "[/HISTORY_INSTRUCTION]\n\n" + if history_context else "" + ) + "[CURRENT_USER_MESSAGE]\n" + prompt + "\n[/CURRENT_USER_MESSAGE]" @@ -591,6 +610,23 @@ async def harness_stream_chat(tid,prompt): "Codex Harness 没有返回有效文本。" ) + tool_names=tuple(getattr(result,"tool_names",()) or ()) + tool_events=tuple(getattr(result,"tool_events",()) or ()) + export_tool_completed=any( + name.endswith("marine_export") + or name.endswith("marine_export_range") + or name.endswith("_export") + for name in tool_names + ) + export_error=_ocean_export_execution_error( + prompt, + export_tool_completed=export_tool_completed, + tool_result_text="\n".join(tool_events), + final_answer=final_answer, + ) + if export_error: + raise RuntimeError(export_error) + log.info( "Codex Harness completed: " "thread=%s model=%s reason=%s uploads=%s", @@ -628,12 +664,11 @@ async def harness_stream_chat(tid,prompt): len(pending_uploads), ) + public_error=_public_error(str(exc)) yield out( "error", { - "text": - "Codex Harness 调用失败:" - + str(exc)[:500], + "text":public_error, "stage":"harness", }, ) @@ -655,14 +690,23 @@ def _is_runtime_info_prompt(prompt: str) -> bool: def _is_marine_health_prompt(prompt: str) -> bool: - """Recognize a live Marine API status question without invoking the model.""" - text = str(prompt or "").strip().lower() - health_terms = ( + """Recognize actual connectivity/status questions, not any domain mention.""" + text=str(prompt or "").strip().lower() + if not text: + return False + explicit_phrases=( "海洋数据服务器连接", "检查海洋数据服务器", "数据服务器连接", - "三个数据域", "三个域是否在线", "ocean、tuna、squid", - "ocean, tuna, squid", "marine health", "marine server health", + "三个域是否在线", "三个数据域是否在线", "marine health", + "marine server health", + ) + if any(term in text for term in explicit_phrases): + return True + status_terms=( + "在线","离线","连接","连通","状态","健康","可用","不可用", + "正常吗","是否正常","reachable","online","offline","status","health", ) - return any(term in text for term in health_terms) + domain_terms=("ocean","tuna","squid","marine","数据服务器","数据域") + return any(x in text for x in status_terms) and any(x in text for x in domain_terms) def _is_fisheries_inventory_prompt(prompt: str) -> bool: @@ -795,55 +839,81 @@ async def _direct_fisheries_inventory_answer(prompt: str) -> str: async def _direct_marine_health_answer(): - """Query the configured Marine API directly for a deterministic status answer.""" - httpx_module = globals().get("httpx") + """Query live domain status without inferring unavailable states.""" + httpx_module=globals().get("httpx") if httpx_module is None: try: import httpx as httpx_module except Exception as exc: return f"海洋数据服务器连接检查失败:HTTP 客户端不可用({exc})" - base = str(globals().get("MARINE_API_URL") or "").rstrip("/") + base=str(globals().get("MARINE_API_URL") or "").rstrip("/") if not base: return "海洋数据服务器未配置 MARINE_API_URL。" - candidates = [f"{base}/health"] - # HF may expose the school API below the app's /marine reverse-proxy path. + def status_label(value): + if isinstance(value,dict): + value=value.get("status",value.get("online",value.get("available"))) + if value is True: + return "在线" + if value is False: + return "离线/不可用" + low=str(value or "").strip().lower() + if low in {"ok","online","ready","available","healthy","true","1"}: + return "在线" + if low in {"offline","down","unavailable","error","false","0"}: + return "离线/不可用" + return "未确认" + + candidates=[f"{base}/domains"] if not base.endswith("/marine"): - candidates.append(f"{base}/marine/health") - - last_error = "" - async with httpx_module.AsyncClient(timeout=12.0, follow_redirects=True) as client: + candidates.append(f"{base}/marine/domains") + last_error="" + async with httpx_module.AsyncClient(timeout=12.0,follow_redirects=True) as client: for url in candidates: try: - response = await client.get(url) + response=await client.get(url) + if not response.is_success: + last_error=f"HTTP {response.status_code}" + continue + payload=response.json() + domain_payload=payload.get("domains") if isinstance(payload,dict) else None + if not isinstance(domain_payload,(dict,list)): + domain_payload=payload if isinstance(payload,(dict,list)) else None + labels={key:"未确认" for key in ("ocean","tuna","squid")} + if isinstance(domain_payload,dict): + for key in labels: + if key in domain_payload: + labels[key]=status_label(domain_payload.get(key)) + elif isinstance(domain_payload,list): + present={str(x.get("name") if isinstance(x,dict) else x).lower() for x in domain_payload} + for key in labels: + labels[key]="在线" if key in present else "未确认" + return ( + "Marine 数据服务已连通。\n" + f"- Ocean:{labels['ocean']}\n" + f"- Tuna:{labels['tuna']}\n" + f"- Squid:{labels['squid']}\n" + "未确认表示服务器响应中没有足够状态字段,平台不会把它自动当成在线。" + ) + except Exception as exc: + last_error=str(exc) + + # Fallback health only proves the API is reachable, not that each domain is online. + health_urls=[f"{base}/health"] + if not base.endswith("/marine"): + health_urls.append(f"{base}/marine/health") + async with httpx_module.AsyncClient(timeout=12.0,follow_redirects=True) as client: + for url in health_urls: + try: + response=await client.get(url) if response.is_success: - payload = response.json() - domains = payload.get("domains") if isinstance(payload, dict) else None - labels = { - "ocean": "在线", - "tuna": "在线", - "squid": "在线", - } - if isinstance(domains, dict): - for key in labels: - value = domains.get(key) - if isinstance(value, dict): - value = value.get("status") or value.get("online") - if value in (False, "offline", "未连接", "不可用"): - labels[key] = "离线/不可用" - elif isinstance(domains, list): - for key in labels: - if key not in {str(item).lower() for item in domains}: - labels[key] = "未在健康响应中列出" return ( - "海洋数据服务器连接正常。\n" - f"- Ocean:{labels['ocean']}\n" - f"- Tuna:{labels['tuna']}\n" - f"- Squid:{labels['squid']}" + "Marine API 可以连接,但当前未取得 Ocean/Tuna/Squid 的逐域状态。" + "请在“设置 → 系统健康”中重新检测详细状态。" ) - last_error = f"HTTP {response.status_code}" + last_error=f"HTTP {response.status_code}" except Exception as exc: - last_error = str(exc) + last_error=str(exc) return f"海洋数据服务器连接检查失败:{last_error[:240]}" diff --git a/static/js/app.js b/static/js/app.js index 3c9ef3ade19ba9a507fa62fd0a4cabe328e08ff1..981c8585ec24925035dd7cec4ed2daef6940e589 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -340,7 +340,7 @@ function persist(role,text,meta=""){ if(role==="assistant"&&meta!=="error")refreshSessionSummary(x,true); setUpdated(x);save();hist() } -function historyPayload(x,limit=8){ +function historyPayload(x,limit=12){ if(!x)return[]; const items=[]; for(const m of (x.messages||[])){ diff --git a/tests/test_agent_reliability_v4620.py b/tests/test_agent_reliability_v4620.py new file mode 100644 index 0000000000000000000000000000000000000000..e0f82b084ad227c135bbf37e9fdaa940c5b6426b --- /dev/null +++ b/tests/test_agent_reliability_v4620.py @@ -0,0 +1,51 @@ +from pathlib import Path +ROOT=Path(__file__).resolve().parents[1] + +def test_version(): + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' + +def test_harness_receives_recent_conversation_history(): + py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8") + assert "_format_recent_thread_history(" in py + assert "[PRIOR_CONVERSATION_HISTORY]" in py + assert "max_chars=24000" in py + +def test_health_intent_does_not_trigger_on_domain_names_alone(): + py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8") + block=py[py.index("def _is_marine_health_prompt"):py.index("def _is_fisheries_inventory_prompt")] + assert '"ocean、tuna、squid"' not in block + assert "status_terms" in block + assert "domain_terms" in block + +def test_direct_health_never_defaults_all_domains_online(): + py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8") + block=py[py.index("async def _direct_marine_health_answer"):py.index("def _is_explicit_new_data_query")] + assert 'labels={key:"未确认"' in block + assert '/domains' in block + assert "平台不会把它自动当成在线" in block + +def test_native_harness_preserves_tool_evidence(): + py=(ROOT/"codex_native_harness.py").read_text(encoding="utf-8") + assert "tool_names: tuple[str, ...]" in py + assert "_tool_names_from_event" in py + assert "tool_events=tuple(tool_events)" in py + +def test_harness_export_is_fail_closed(): + py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8") + assert 'tool_names=tuple(getattr(result,"tool_names"' in py + assert "export_tool_completed=any(" in py + assert "_ocean_export_execution_error(" in py + +def test_adapter_does_not_store_shared_mutable_session_id(): + py=(ROOT/"codex_harness.py").read_text(encoding="utf-8") + assert "request_session_id" in py + assert "self._session_id =" not in py + +def test_harness_errors_are_sanitized(): + py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8") + assert "public_error=_public_error(str(exc))" in py + assert '"text":public_error' in py + +def test_frontend_sends_more_recent_context(): + js=(ROOT/"static/js/app.js").read_text(encoding="utf-8") + assert "function historyPayload(x,limit=12)" in js diff --git a/tests/test_api_entry_preflight_v456.py b/tests/test_api_entry_preflight_v456.py index 26d5a4f800b3b7ed4ac12ae341878299a4d8bc0d..b05ec91dbe3860ac2d05a1497c40fdbf5a9db782 100644 --- a/tests/test_api_entry_preflight_v456.py +++ b/tests/test_api_entry_preflight_v456.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_guard_is_before_auth_and_dispatch(): chat=(ROOT/"routes"/"chat.py").read_text(encoding="utf-8") diff --git a/tests/test_chat_lifecycle_progress_v393.py b/tests/test_chat_lifecycle_progress_v393.py index a4b34b352db330b99e8df88829e30c8017301693..63af83b1516279c2ac0e10b923c9f32dc7a96501 100644 --- a/tests/test_chat_lifecycle_progress_v393.py +++ b/tests/test_chat_lifecycle_progress_v393.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_393(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_frontend_lifecycle_progress(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_clarification_ui_v454.py b/tests/test_clarification_ui_v454.py index 61d40aaa3785b49ef963ac0f9de56c1904ba4b85..2a4afd8e6987562a9757275c3f4c5c5970c92de0 100644 --- a/tests/test_clarification_ui_v454.py +++ b/tests/test_clarification_ui_v454.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_missing_date_reuses_canonical_region_bbox(): rt=(ROOT/"services"/"chat_runtime.py").read_text(encoding="utf-8") diff --git a/tests/test_classification_cleanup_v466.py b/tests/test_classification_cleanup_v466.py index b9bad2ae56e20c8ed437f633525a73634a6829be..680b7cc92093b42d9976ef6bee5d1d76fdca8108 100644 --- a/tests/test_classification_cleanup_v466.py +++ b/tests/test_classification_cleanup_v466.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_seaaroundus_alias(): py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8") diff --git a/tests/test_context_and_multitask_finalize_v451.py b/tests/test_context_and_multitask_finalize_v451.py index 3ad1789d56139b545b03798b95c43d2a91bad884..793128580c6522ea827c1c93e62b6e6eb97bbca1 100644 --- a/tests/test_context_and_multitask_finalize_v451.py +++ b/tests/test_context_and_multitask_finalize_v451.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_new_query_slot_reset(): rt=(ROOT/"services"/"chat_runtime.py").read_text(encoding="utf-8") diff --git a/tests/test_conversation_summary_v4613.py b/tests/test_conversation_summary_v4613.py index 578c3a526827dfa8f6c678beb7bb87dcb16cbf2a..bd4d6fccd8f0e50e0f34b9e5aaf29664d7012af3 100644 --- a/tests/test_conversation_summary_v4613.py +++ b/tests/test_conversation_summary_v4613.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_frontend_auto_summary_and_manual_override(): js=(ROOT/"static/js/app.js").read_text(encoding="utf-8") diff --git a/tests/test_download_host_authorization_v460.py b/tests/test_download_host_authorization_v460.py index fff96c79ff62a2751ef6b403bf48a1ea03d6e608..137adec061cbe57bfa41d2de7ce886830e66a79b 100644 --- a/tests/test_download_host_authorization_v460.py +++ b/tests/test_download_host_authorization_v460.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_batch_url_authorization_helper_exists(): py=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8") diff --git a/tests/test_download_opfs_fallback_v459.py b/tests/test_download_opfs_fallback_v459.py index 836e141fee357614b777545ad5bf983cdac826ef..4ad6a9eb823b6921e8e82cecd281c8bb6db92dbd 100644 --- a/tests/test_download_opfs_fallback_v459.py +++ b/tests/test_download_opfs_fallback_v459.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_opfs_is_probed_not_assumed(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_download_overlay_v4618.py b/tests/test_download_overlay_v4618.py index 5619499c59e4a6f4658c144f12db26dba2a36a78..2d4fda5093af25ad05ef1f9b2eec5ddc4ead5b95 100644 --- a/tests/test_download_overlay_v4618.py +++ b/tests/test_download_overlay_v4618.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.18" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_native_download_handoff_finishes_overlay(): js=(ROOT/"static/js/app.js").read_text(encoding="utf-8") diff --git a/tests/test_download_progress_v391.py b/tests/test_download_progress_v391.py index 2ca8d68745867504c7d01ba629eada8c150d1bc7..84a50f02e01a2909469d71d11143a1609a1d1f3b 100644 --- a/tests/test_download_progress_v391.py +++ b/tests/test_download_progress_v391.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_391(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_p1_label_removed(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_download_resilience_v422.py b/tests/test_download_resilience_v422.py index a9196d1018a9f9ed7d461fd3dc5ae0b7d97b84b5..e3ad9517bde094994e123aef9b85a76e9fb95b1e 100644 --- a/tests/test_download_resilience_v422.py +++ b/tests/test_download_resilience_v422.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_refresh_route_registered(): t=(ROOT/"routes"/"ocean_batch.py").read_text(encoding="utf-8") diff --git a/tests/test_frontend_preflight_v457.py b/tests/test_frontend_preflight_v457.py index 1320647bfff82df9f8b051208427849d894d1886..af674baf232e3dea26d6de6ccfee34a35fd97355 100644 --- a/tests/test_frontend_preflight_v457.py +++ b/tests/test_frontend_preflight_v457.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_frontend_preflight_exists_before_progress(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_history_dedupe_recent_summary_v4614.py b/tests/test_history_dedupe_recent_summary_v4614.py index 9ce1028f1f9cadb2b058b0b43c41861715f36e0c..b185ae2ac00a04a10097a2033db2d8a73ab31067 100644 --- a/tests/test_history_dedupe_recent_summary_v4614.py +++ b/tests/test_history_dedupe_recent_summary_v4614.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_frontend_dedupe_and_merge(): js=(ROOT/"static/js/app.js").read_text(encoding="utf-8") diff --git a/tests/test_history_sidebar_v373.py b/tests/test_history_sidebar_v373.py index d58c2e736316247ab9f7338371c690a50edf040c..2c270cc5faca392c52c35c9e7b4b6c879bb20b27 100644 --- a/tests/test_history_sidebar_v373.py +++ b/tests/test_history_sidebar_v373.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_373(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_project_history_backend(): route=(ROOT/"routes"/"project_package.py").read_text(encoding="utf-8") diff --git a/tests/test_large_download_jobs_v400.py b/tests/test_large_download_jobs_v400.py index 889a2f5f6dad3c1cb492163be075d326b9fabaad..55dc0ae507b4247813227d575f454c1f76d4b084 100644 --- a/tests/test_large_download_jobs_v400.py +++ b/tests/test_large_download_jobs_v400.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_400(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_download_job_backend(): route=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8") diff --git a/tests/test_manual_tasks_v469.py b/tests/test_manual_tasks_v469.py index 81d18aab5a0a925472d252c027ab6f36edb41b2f..7774dfae824358334bc7d577d93262dbee31892a 100644 --- a/tests/test_manual_tasks_v469.py +++ b/tests/test_manual_tasks_v469.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_manual_task_backend_exists(): service=(ROOT/"services/manual_tasks.py").read_text(encoding="utf-8") @@ -27,4 +27,4 @@ def test_frontend_new_task_controls(): assert "标记完成" in js assert "重新打开" in js assert "manual-delete" in js - assert "账号服务器持久化(可跨设备)" in js + assert "账号同步:" in js and "可跨设备同步" in js and "服务器会话" in js diff --git a/tests/test_merged_health_v433.py b/tests/test_merged_health_v433.py index e4d6ddb41af86dc2ff864f12e82bef125fbd065c..f9d007fb0ea103f1cae2348a68ecc024d8588377 100644 --- a/tests/test_merged_health_v433.py +++ b/tests/test_merged_health_v433.py @@ -3,7 +3,7 @@ from services.ocean_batch import parse_ocean_batch_request ROOT=Path(__file__).resolve().parents[1] def test_merged_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.9" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_ocean_month_batch_survives_merge(): p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载") diff --git a/tests/test_multitask_observability_v442.py b/tests/test_multitask_observability_v442.py index febf3a83c601fe5b9e18a4d3b48303460ab5b4a2..f9cbff5c8727ce8cd2e75d1359192878e465cd4d 100644 --- a/tests/test_multitask_observability_v442.py +++ b/tests/test_multitask_observability_v442.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_frontend_sends_multitask_trace_headers(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_multitask_sse_finalize_v452.py b/tests/test_multitask_sse_finalize_v452.py index 863e4a683dab55e983bd53c91aff53c8726651a4..31efb8f92a2b6fd29197239a0da26d9382f91144 100644 --- a/tests/test_multitask_sse_finalize_v452.py +++ b/tests/test_multitask_sse_finalize_v452.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_parallel_sse_uses_real_blank_line_separator(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_noaa_ramldb_roles_v465.py b/tests/test_noaa_ramldb_roles_v465.py index dc4f94db98c33e8e96f47d0e53683c91acb95e66..c570444c3c3bac86f3ef0a060a57cfcdd47039b3 100644 --- a/tests/test_noaa_ramldb_roles_v465.py +++ b/tests/test_noaa_ramldb_roles_v465.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_noaa_and_ramldb_aliases(): py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8") diff --git a/tests/test_ocean_batch_downloads_v420.py b/tests/test_ocean_batch_downloads_v420.py index 53da6d506971d37dfe82bcf0d7cf97a4c5455007..b3aa8970b890803aaf916d4435b41c66e67566b9 100644 --- a/tests/test_ocean_batch_downloads_v420.py +++ b/tests/test_ocean_batch_downloads_v420.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_420(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_manifest_has_filename_and_sizes(): svc=(ROOT/"services"/"ocean_batch.py").read_text(encoding="utf-8") @@ -17,13 +17,13 @@ def test_frontend_has_one_click_batch_download(): assert "function startOceanBatchDownloadAll" in js assert "function runOceanBatchDownloadQueue" in js assert "function waitManagedDownloadStatus" in js - assert "下载全部" in js + assert "打包 ZIP 下载" in js and "逐个下载" in js assert "查看文件" in js def test_batch_download_uses_managed_range_jobs(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") assert 'j("/api/download/jobs"' in js - assert "triggerBrowserSave(child.stream_url" in js + assert "triggerBrowserSave(job.stream_url" in js assert "resume_supported" in js assert "30 秒无新字节" in js diff --git a/tests/test_ocean_batch_routing_v421.py b/tests/test_ocean_batch_routing_v421.py index f3939dc93ac8e3dec12840c48d2afbd0c57a022c..7342097591c1fe5039b3ee0ab4db35c2f24bbbde 100644 --- a/tests/test_ocean_batch_routing_v421.py +++ b/tests/test_ocean_batch_routing_v421.py @@ -4,7 +4,7 @@ from services.ocean_batch import parse_ocean_batch_request ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_421(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_exact_user_month_query_routes_to_batch(): p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载") diff --git a/tests/test_ocean_batch_v410.py b/tests/test_ocean_batch_v410.py index a529c104cb1a11ee161064b136d13daaebf3f549..52077a09216f971b98cdcf37e06a8b597c634874 100644 --- a/tests/test_ocean_batch_v410.py +++ b/tests/test_ocean_batch_v410.py @@ -4,7 +4,7 @@ from services.ocean_batch import parse_ocean_batch_request ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_410(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_month_three_variable_batch_plan(): p=parse_ocean_batch_request("查询并导出 2002 年 8 月西北太平洋 120°E–140°E、20°N–40°N 的 SST、SSH、CHL 数据,并打包下载") diff --git a/tests/test_ocean_batch_zip_manifest_v4619.py b/tests/test_ocean_batch_zip_manifest_v4619.py index 7c3f5385bfb1b4b73b89da34f746247421b47761..9be8b899d5703b791a8b6ed16772c5d16891edcb 100644 --- a/tests/test_ocean_batch_zip_manifest_v4619.py +++ b/tests/test_ocean_batch_zip_manifest_v4619.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.19" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_zip_backend_routes_exist(): py=(ROOT/"routes/ocean_batch.py").read_text(encoding="utf-8") diff --git a/tests/test_ocean_date_default_v453.py b/tests/test_ocean_date_default_v453.py index f7955ba2d1b48074a318ba21d131db1bb1cea9fe..7b5d5d06448cb06c86a36a3a2f1736b67df45509 100644 --- a/tests/test_ocean_date_default_v453.py +++ b/tests/test_ocean_date_default_v453.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_missing_date_never_defaults_to_today(): rt=(ROOT/"services"/"chat_runtime.py").read_text(encoding="utf-8") diff --git a/tests/test_ocean_download_url_base_v462.py b/tests/test_ocean_download_url_base_v462.py index 5464d43cff1478f6ae43e50c90c4d42170ab6d4d..83c732e664e52695e11c4591b2061b77737bf3fa 100644 --- a/tests/test_ocean_download_url_base_v462.py +++ b/tests/test_ocean_download_url_base_v462.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_generic_public_base_not_used(): py=(ROOT/"services"/"ocean_batch.py").read_text(encoding="utf-8") diff --git a/tests/test_p0_finish_v380.py b/tests/test_p0_finish_v380.py index a416a0d9d64fe28a89316bb140056c806ead8367..32541035f0130afd5dc80c6cf19e2cf6fd5dd71a 100644 --- a/tests/test_p0_finish_v380.py +++ b/tests/test_p0_finish_v380.py @@ -3,7 +3,7 @@ import pathlib, re, subprocess, shutil ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_380(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_chat_runtime_extracted(): ui=(ROOT/"ui_server.py").read_text(encoding="utf-8") diff --git a/tests/test_p0_phase2_v371.py b/tests/test_p0_phase2_v371.py index 8f062445cec7e929356ffb12245ce3ec1f936831..16c2c8f8cd8a310a02fbed839169b9a6ef2be883 100644 --- a/tests/test_p0_phase2_v371.py +++ b/tests/test_p0_phase2_v371.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_frontend_feature_modules_exist(): html=(ROOT/"app.html").read_text(encoding="utf-8") diff --git a/tests/test_p0_phase3_v372.py b/tests/test_p0_phase3_v372.py index 5681a29f56992d891bdccfeb2c0c7c50ced62d02..550586f98adfdd91780543fbdbfd11a2f9778fd6 100644 --- a/tests/test_p0_phase3_v372.py +++ b/tests/test_p0_phase3_v372.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_372(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_route_modules_exist(): for name in ("datasets.py","user_state.py","project_package.py","data_health.py"): diff --git a/tests/test_platform_reliability_v450.py b/tests/test_platform_reliability_v450.py index 52a23718bbf97b8d045a8f6a8458132b89832239..1650e16c246920355d9edfbb1d62dcd47a27ee3c 100644 --- a/tests/test_platform_reliability_v450.py +++ b/tests/test_platform_reliability_v450.py @@ -6,7 +6,7 @@ from services.ocean_batch import parse_ocean_batch_request ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_squid_center_uses_repository_membership(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") @@ -15,7 +15,7 @@ def test_squid_center_uses_repository_membership(): assert "file_count_by_repository" in api assert "domains.includes(domain)" in js assert "squid_file_count" in js - assert "实时仓库文件树口径" in js + assert "Hugging Face main 实时文件树" in js def test_single_ocean_export_forces_managed_proxy(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_product_finish_v4615.py b/tests/test_product_finish_v4615.py index 03c81d93cfac9a6c85b9a5b310f0a39b5f9e9719..398d204fee7b6ab7f73f4c74e7c31e1d791772af 100644 --- a/tests/test_product_finish_v4615.py +++ b/tests/test_product_finish_v4615.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_system_health_is_settings_embedded_and_modular(): app=(ROOT/"static/js/app.js").read_text(encoding="utf-8") diff --git a/tests/test_project_package_download_v4617.py b/tests/test_project_package_download_v4617.py index f83ac8ad605f28467b505814ed7b4fbefd759203..f3d814bb48b1f8d2025e0826198d865d5cf80559 100644 --- a/tests/test_project_package_download_v4617.py +++ b/tests/test_project_package_download_v4617.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.17" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_project_package_download_is_capability_url(): py=(ROOT/"routes/project_package.py").read_text(encoding="utf-8") @@ -21,4 +21,4 @@ def test_same_origin_download_probes_before_native_save(): def test_project_download_button_explains_browser_handoff(): js=(ROOT/"static/js/project-package.js").read_text(encoding="utf-8") assert "正在交给浏览器" in js - assert "已交给浏览器下载" in js + assert "已交给浏览器;实际完成状态请看浏览器下载栏" in js diff --git a/tests/test_quality_center_v362.py b/tests/test_quality_center_v362.py index dfd731efa01bab86289167ef304237be9a7916d2..bbde0bf73dd06abcbf2af09a02bbe309e786371d 100644 --- a/tests/test_quality_center_v362.py +++ b/tests/test_quality_center_v362.py @@ -3,7 +3,7 @@ import pathlib ROOT = pathlib.Path(__file__).resolve().parents[1] def test_version_371(): - assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == "4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_native_health_backend(): text = (ROOT / "routes" / "data_health.py").read_text(encoding="utf-8") diff --git a/tests/test_quality_trust_v361.py b/tests/test_quality_trust_v361.py index b8e12a77791e53a8a2ff3e575e12e94dfdf096c6..0f847e084157cd5d1eda571eef5375194f6e6dd0 100644 --- a/tests/test_quality_trust_v361.py +++ b/tests/test_quality_trust_v361.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version_current(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_quality_confidence_is_now_native_api_metric(): text=(ROOT/"routes"/"data_health.py").read_text(encoding="utf-8") diff --git a/tests/test_reference_cleanup_v468.py b/tests/test_reference_cleanup_v468.py index b7cec447ffaa87d9b193ce9500192dc6276e5586..963316aef1dbb16cd9b91446e97dbb51c2499cec 100644 --- a/tests/test_reference_cleanup_v468.py +++ b/tests/test_reference_cleanup_v468.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_reference_and_reproducibility_are_auxiliary(): py=(ROOT/"routes"/"datasets.py").read_text(encoding="utf-8") diff --git a/tests/test_release_stability.py b/tests/test_release_stability.py index 3a1fff92be0ef890cbfa7b8cb0da93da777c0589..831df81406eda6aee8a1011a964c53235400287b 100644 --- a/tests/test_release_stability.py +++ b/tests/test_release_stability.py @@ -7,7 +7,7 @@ def test_version_is_single_source(): version = (ROOT / "VERSION").read_text(encoding="utf-8").strip() ui = (ROOT / "ui_server.py").read_text(encoding="utf-8") html = (ROOT / "app.html").read_text(encoding="utf-8") - assert version == "4.6.15" + assert version == '4.6.20' assert 'with_name("VERSION")' in ui assert "__APP_VERSION__" in html diff --git a/tests/test_resumable_batch_download_v458.py b/tests/test_resumable_batch_download_v458.py index d397c7cb58b93c251779fad7a5988c12411e3e2d..7b05b3119fe12c4e0af0595a1f4abf22b253e0a1 100644 --- a/tests/test_resumable_batch_download_v458.py +++ b/tests/test_resumable_batch_download_v458.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_network_loss_pauses_not_fails_all(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_route_region_clarification_v455.py b/tests/test_route_region_clarification_v455.py index ed1afd60d6e70e01763fed3006fe2c1f6a58791d..9cece65009a35791e35e5f30c4133c7fd0e57219 100644 --- a/tests/test_route_region_clarification_v455.py +++ b/tests/test_route_region_clarification_v455.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_route_guard_exists_before_agent_dispatch(): chat=(ROOT/"routes"/"chat.py").read_text(encoding="utf-8") diff --git a/tests/test_runtime_hotfix_v376.py b/tests/test_runtime_hotfix_v376.py index 6043a15e9e2523c49969b53339fa06b1fc853a6b..5d2711e51bf67cbf9a43e81bdd0ecef35b4975eb 100644 --- a/tests/test_runtime_hotfix_v376.py +++ b/tests/test_runtime_hotfix_v376.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_376(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_no_stray_async_before_sidebar_toggle(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_sidebar_collapse_v374.py b/tests/test_sidebar_collapse_v374.py index fc05abffbffe79c3313060a958484384b9742e82..6c6730a93b59e85254d61ce2acad1c57409dff8f 100644 --- a/tests/test_sidebar_collapse_v374.py +++ b/tests/test_sidebar_collapse_v374.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_374(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_sidebar_toggle_markup(): html=(ROOT/"app.html").read_text(encoding="utf-8") diff --git a/tests/test_sidebar_partial_collapse_v375.py b/tests/test_sidebar_partial_collapse_v375.py index e59ed57493b451dceec9001c11b20938854e65ad..410872757956a813d1d03e8137e6745c4cd3a34c 100644 --- a/tests/test_sidebar_partial_collapse_v375.py +++ b/tests/test_sidebar_partial_collapse_v375.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_375(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_sidebar_order_and_groups(): html=(ROOT/"app.html").read_text(encoding="utf-8") diff --git a/tests/test_smart_progress_avatar_sync_v441.py b/tests/test_smart_progress_avatar_sync_v441.py index 86aa0edea40dd88ba73edd31aa67ecb762e7888e..101384a3a75b4dc506cbb56469a2359e22fb3d26 100644 --- a/tests/test_smart_progress_avatar_sync_v441.py +++ b/tests/test_smart_progress_avatar_sync_v441.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_simple_chat_hides_progress_until_needed(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_source_mapping_roles_v464.py b/tests/test_source_mapping_roles_v464.py index 0919d790a022dac998a3cbebcdcfed81b8b143c4..45dc907d87d28d4e87e91c440b8d2886ada00ce7 100644 --- a/tests/test_source_mapping_roles_v464.py +++ b/tests/test_source_mapping_roles_v464.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_new_source_mappings(): py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8") diff --git a/tests/test_squid_finalize_v467.py b/tests/test_squid_finalize_v467.py index edb26ac152e09576da6c2db006dff2ca29af1d45..617ab287b0e0caa9bb4e0a4f1d39206e80abbf73 100644 --- a/tests/test_squid_finalize_v467.py +++ b/tests/test_squid_finalize_v467.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_ccamlr_and_ommastrephidae_mappings(): py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8") diff --git a/tests/test_sse_heartbeat_v401.py b/tests/test_sse_heartbeat_v401.py index 49ec7976e209ae49be7b028581f47203207616ad..2e94d8758af3b291e2100ce122a29d86a57983ca 100644 --- a/tests/test_sse_heartbeat_v401.py +++ b/tests/test_sse_heartbeat_v401.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_401(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_chat_has_independent_heartbeat_wrapper(): route=(ROOT/"routes"/"chat.py").read_text(encoding="utf-8") diff --git a/tests/test_system_health_fix_v4616.py b/tests/test_system_health_fix_v4616.py index 3986ac0f07bd6697c2dc8afebc2a979fb077b041..717d72f77e49cb116e5afc53ebb70d3e0959282b 100644 --- a/tests/test_system_health_fix_v4616.py +++ b/tests/test_system_health_fix_v4616.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.16" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_system_health_request_is_typed(): py=(ROOT/"routes/system_health.py").read_text(encoding="utf-8") diff --git a/tests/test_task_layout_v470.py b/tests/test_task_layout_v470.py index 242c4ff2ea92168f6ca44a6784bb9831ae0e6fd5..1ae3db2649f948ef941cfe68e60363b5c26b0741 100644 --- a/tests/test_task_layout_v470.py +++ b/tests/test_task_layout_v470.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/'VERSION').read_text(encoding='utf-8').strip() == '4.6.15' + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_manual_task_layout_classes_present(): js = (ROOT/'static/js/app.js').read_text(encoding='utf-8') diff --git a/tests/test_task_priority_deadline_v4611.py b/tests/test_task_priority_deadline_v4611.py index b6de9330795d95cb7be8fe497f2d908d8984f867..45c0315fffd4f38af849e233c49997980d2bf5f9 100644 --- a/tests/test_task_priority_deadline_v4611.py +++ b/tests/test_task_priority_deadline_v4611.py @@ -1,6 +1,6 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] -def test_version(): assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.15' +def test_version(): assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_manual_fields(): s=(ROOT/'services/manual_tasks.py').read_text(encoding='utf-8') assert 'VALID_PRIORITIES' in s and 'due_at' in s and 'priority' in s diff --git a/tests/test_task_productization_v4612.py b/tests/test_task_productization_v4612.py index 2071e9c09b133336ccdb2abf408f3bcf72781001..2a9bce22caeb85c6311b0e389f0a04ab6cbfa496 100644 --- a/tests/test_task_productization_v4612.py +++ b/tests/test_task_productization_v4612.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_task_endpoint_softens_memory_error_and_adds_project_history(): py=(ROOT/"ui_server.py").read_text(encoding="utf-8") @@ -24,7 +24,7 @@ def test_task_ui_has_sorting_quick_filters_and_overdue(): def test_memory_error_is_collapsed_not_raw_banner(): js=(ROOT/"static/js/app.js").read_text(encoding="utf-8") - assert "
" in js + assert '
' in js assert "task-tech-detail" in js def test_workspace_sync_already_covers_sessions_and_settings(): diff --git a/tests/test_ui_avatar_multitask_v440.py b/tests/test_ui_avatar_multitask_v440.py index d7b0dcc1c9e81576aa5ea4c4ae49db2b0e486bda..1b61c06d7e64813bd344b68598203642504e4893 100644 --- a/tests/test_ui_avatar_multitask_v440.py +++ b/tests/test_ui_avatar_multitask_v440.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_assistant_avatar_removed_user_avatar_customizable(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/tests/test_unclassified_inventory_v463.py b/tests/test_unclassified_inventory_v463.py index 05827f218d7edf25c6e029a72616738bfcae2403..289480bd12a6cbd3f34177e523cd3324ab901f7d 100644 --- a/tests/test_unclassified_inventory_v463.py +++ b/tests/test_unclassified_inventory_v463.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_backend_unique_classification_and_route(): py=(ROOT/"routes"/"datasets.py").read_text(encoding="utf-8") diff --git a/tests/test_unified_download_manager_v392.py b/tests/test_unified_download_manager_v392.py index 9292a287384ac142bb2fbf56fedff77eccfe8d79..44e8a34d6dba04add12137bd3ad44b027d95d4c9 100644 --- a/tests/test_unified_download_manager_v392.py +++ b/tests/test_unified_download_manager_v392.py @@ -3,7 +3,7 @@ import pathlib ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_392(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_download_proxy_route_registered(): route=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8") diff --git a/tests/test_unified_query_v390.py b/tests/test_unified_query_v390.py index 8cec9828ef62a495029c74737564a1623584b7e1..86856f74c10d53d4ff771d51de23153ccd675980 100644 --- a/tests/test_unified_query_v390.py +++ b/tests/test_unified_query_v390.py @@ -4,7 +4,7 @@ from services.unified_query import plan_unified_query ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_390(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_joint_squid_environment_plan(): p=plan_unified_query("查询 2002 年 8 月西北太平洋柔鱼记录,并匹配 SST、SSH、CHL") diff --git a/tests/test_upstream_refresh_retry_v461.py b/tests/test_upstream_refresh_retry_v461.py index 784d0da420e19796dd62f1738333078aaa2b54fc..72bf93fb849cb88b1785cb05aaa828f7b572f4e9 100644 --- a/tests/test_upstream_refresh_retry_v461.py +++ b/tests/test_upstream_refresh_retry_v461.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def test_version(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_probe_returns_status_and_rejects_dead_url(): py=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8") diff --git a/tests/test_v381_navigation_ocean_intent.py b/tests/test_v381_navigation_ocean_intent.py index 4a6ed7f5878377287db9379c8144b112718b790a..12626f2578178002be53178c4ee735583f7904db 100644 --- a/tests/test_v381_navigation_ocean_intent.py +++ b/tests/test_v381_navigation_ocean_intent.py @@ -3,7 +3,7 @@ import pathlib, ast ROOT=pathlib.Path(__file__).resolve().parents[1] def test_version_381(): - assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15" + assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20' def test_recent_conversation_switches_to_chat(): js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8") diff --git a/ui_server.py b/ui_server.py index dda8a6d665524dfc25cffe3cb1bd045990587907..1f56b953504e04b4986c5fa09b0b77934999ec7c 100644 --- a/ui_server.py +++ b/ui_server.py @@ -2727,12 +2727,23 @@ def _public_error(text): raw=(text or "未知错误").strip() low=raw.lower() if "402" in low or "insufficient balance" in low: - return "智谱 ZAI API 返回 402:账户余额不足。" + return "模型服务当前余额不足,暂时无法完成请求。" if "401" in low or "unauthorized" in low: - return "智谱 ZAI API 认证失败(401)。请检查 ZAI_API_KEY 配置。" + return "模型服务认证失败,请联系管理员检查服务配置。" if "429" in low or "rate limit" in low: - return "智谱 ZAI API 当前触发限流(429),请稍后重试。" - return raw + return "模型服务当前请求过多,请稍后重试。" + if "timeout" in low or "超时" in raw: + return "Agent 本次处理超时,请稍后重试;如果是大任务,建议拆成更小的请求。" + if any(token in low for token in ( + "official codex cli","codex harness","stderr:","stdout:", + "opencode go api","connection failed","traceback","api key", + )): + return "Agent 运行时暂时无法完成请求,请稍后重试;技术详情已记录到服务器日志。" + if "ocean 导出工具未实际执行" in raw: + return raw + if len(raw)<=260 and not any(x in low for x in ("exception","stack","/home/user/","/tmp/")): + return raw + return "Agent 暂时无法完成请求,请稍后重试。" def _ocean_export_execution_error(