Zin299 commited on
Commit
c175ac0
·
1 Parent(s): 5e9972d

Deploy v4.6.20 agent reliability fixes

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +1 -1
  2. VERSION +1 -1
  3. codex_harness.py +18 -6
  4. codex_native_harness.py +37 -1
  5. services/chat_runtime.py +113 -43
  6. static/js/app.js +1 -1
  7. tests/test_agent_reliability_v4620.py +51 -0
  8. tests/test_api_entry_preflight_v456.py +1 -1
  9. tests/test_chat_lifecycle_progress_v393.py +1 -1
  10. tests/test_clarification_ui_v454.py +1 -1
  11. tests/test_classification_cleanup_v466.py +1 -1
  12. tests/test_context_and_multitask_finalize_v451.py +1 -1
  13. tests/test_conversation_summary_v4613.py +1 -1
  14. tests/test_download_host_authorization_v460.py +1 -1
  15. tests/test_download_opfs_fallback_v459.py +1 -1
  16. tests/test_download_overlay_v4618.py +1 -1
  17. tests/test_download_progress_v391.py +1 -1
  18. tests/test_download_resilience_v422.py +1 -1
  19. tests/test_frontend_preflight_v457.py +1 -1
  20. tests/test_history_dedupe_recent_summary_v4614.py +1 -1
  21. tests/test_history_sidebar_v373.py +1 -1
  22. tests/test_large_download_jobs_v400.py +1 -1
  23. tests/test_manual_tasks_v469.py +2 -2
  24. tests/test_merged_health_v433.py +1 -1
  25. tests/test_multitask_observability_v442.py +1 -1
  26. tests/test_multitask_sse_finalize_v452.py +1 -1
  27. tests/test_noaa_ramldb_roles_v465.py +1 -1
  28. tests/test_ocean_batch_downloads_v420.py +3 -3
  29. tests/test_ocean_batch_routing_v421.py +1 -1
  30. tests/test_ocean_batch_v410.py +1 -1
  31. tests/test_ocean_batch_zip_manifest_v4619.py +1 -1
  32. tests/test_ocean_date_default_v453.py +1 -1
  33. tests/test_ocean_download_url_base_v462.py +1 -1
  34. tests/test_p0_finish_v380.py +1 -1
  35. tests/test_p0_phase2_v371.py +1 -1
  36. tests/test_p0_phase3_v372.py +1 -1
  37. tests/test_platform_reliability_v450.py +2 -2
  38. tests/test_product_finish_v4615.py +1 -1
  39. tests/test_project_package_download_v4617.py +2 -2
  40. tests/test_quality_center_v362.py +1 -1
  41. tests/test_quality_trust_v361.py +1 -1
  42. tests/test_reference_cleanup_v468.py +1 -1
  43. tests/test_release_stability.py +1 -1
  44. tests/test_resumable_batch_download_v458.py +1 -1
  45. tests/test_route_region_clarification_v455.py +1 -1
  46. tests/test_runtime_hotfix_v376.py +1 -1
  47. tests/test_sidebar_collapse_v374.py +1 -1
  48. tests/test_sidebar_partial_collapse_v375.py +1 -1
  49. tests/test_smart_progress_avatar_sync_v441.py +1 -1
  50. tests/test_source_mapping_roles_v464.py +1 -1
README.md CHANGED
@@ -10,7 +10,7 @@ pinned: false
10
 
11
  # Global Marine Foundation Data Agent
12
 
13
- Current UI release: **v4.6.19**.
14
 
15
 
16
  ## v3.4.0 稳定性重构(第一阶段)
 
10
 
11
  # Global Marine Foundation Data Agent
12
 
13
+ Current UI release: **v4.6.20**.
14
 
15
 
16
  ## v3.4.0 稳定性重构(第一阶段)
VERSION CHANGED
@@ -1 +1 @@
1
- 4.6.19
 
1
+ 4.6.20
codex_harness.py CHANGED
@@ -12,6 +12,8 @@ from dataclasses import dataclass
12
  class CodexResult:
13
  final_response: str
14
  finish_reason: str = "completed"
 
 
15
 
16
 
17
  class CodexHarness:
@@ -171,7 +173,7 @@ class CodexHarness:
171
  result = getattr(marine_mcp, short)(**args)
172
  return CodexHarness._jsonable(result)
173
 
174
- def _request(self, body: dict) -> dict:
175
  request = urllib.request.Request(
176
  self.base_url + "/responses",
177
  data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
@@ -181,7 +183,7 @@ class CodexHarness:
181
  "Content-Type": "application/json",
182
  "Accept": "application/json",
183
  "User-Agent": "Global-Marine-Foundation/4.5",
184
- "x-opencode-session": str(getattr(self, "_session_id", uuid.uuid4())),
185
  },
186
  )
187
  try:
@@ -222,7 +224,7 @@ class CodexHarness:
222
  def run(self, prompt, session_id=None):
223
  if not self.api_key:
224
  raise RuntimeError("OPENCODE_GO_API_KEY is not configured")
225
- self._session_id = str(session_id or uuid.uuid4())
226
  full_prompt = (self.system_prompt + "\n\n" + prompt).strip()
227
  tools = self._tools()
228
  payload = self._request({
@@ -230,7 +232,9 @@ class CodexHarness:
230
  "input": full_prompt,
231
  "tools": tools,
232
  "tool_choice": "auto",
233
- })
 
 
234
 
235
  # Responses can contain multiple tool calls. Execute them locally,
236
  # then continue the same Responses conversation until text is final.
@@ -240,10 +244,18 @@ class CodexHarness:
240
  break
241
  outputs = []
242
  for call in calls:
 
 
243
  try:
244
  value = self._run_tool(call["name"], call["arguments"])
245
  except Exception as exc:
246
  value = {"status": "error", "detail": str(exc)[:1000]}
 
 
 
 
 
 
247
  outputs.append({
248
  "type": "function_call_output",
249
  "call_id": call["call_id"],
@@ -256,11 +268,11 @@ class CodexHarness:
256
  "tools": tools,
257
  "tool_choice": "auto",
258
  }
259
- payload = self._request(next_body)
260
  else:
261
  raise RuntimeError("OpenCode Go tool loop exceeded 8 rounds")
262
 
263
  answer = self._text(payload)
264
  if not answer:
265
  raise RuntimeError("OpenCode Go returned no text: " + json.dumps(payload, ensure_ascii=False)[:1200])
266
- return CodexResult(final_response=answer)
 
12
  class CodexResult:
13
  final_response: str
14
  finish_reason: str = "completed"
15
+ tool_names: tuple[str, ...] = ()
16
+ tool_events: tuple[str, ...] = ()
17
 
18
 
19
  class CodexHarness:
 
173
  result = getattr(marine_mcp, short)(**args)
174
  return CodexHarness._jsonable(result)
175
 
176
+ def _request(self, body: dict, session_id: str) -> dict:
177
  request = urllib.request.Request(
178
  self.base_url + "/responses",
179
  data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
 
183
  "Content-Type": "application/json",
184
  "Accept": "application/json",
185
  "User-Agent": "Global-Marine-Foundation/4.5",
186
+ "x-opencode-session": str(session_id),
187
  },
188
  )
189
  try:
 
224
  def run(self, prompt, session_id=None):
225
  if not self.api_key:
226
  raise RuntimeError("OPENCODE_GO_API_KEY is not configured")
227
+ request_session_id = str(session_id or uuid.uuid4())
228
  full_prompt = (self.system_prompt + "\n\n" + prompt).strip()
229
  tools = self._tools()
230
  payload = self._request({
 
232
  "input": full_prompt,
233
  "tools": tools,
234
  "tool_choice": "auto",
235
+ }, request_session_id)
236
+ tool_names = []
237
+ tool_events = []
238
 
239
  # Responses can contain multiple tool calls. Execute them locally,
240
  # then continue the same Responses conversation until text is final.
 
244
  break
245
  outputs = []
246
  for call in calls:
247
+ if call["name"] not in tool_names:
248
+ tool_names.append(call["name"])
249
  try:
250
  value = self._run_tool(call["name"], call["arguments"])
251
  except Exception as exc:
252
  value = {"status": "error", "detail": str(exc)[:1000]}
253
+ if len(tool_events)<40:
254
+ tool_events.append(json.dumps({
255
+ "name":call["name"],
256
+ "arguments":call["arguments"],
257
+ "result":value,
258
+ },ensure_ascii=False,default=str)[:12000])
259
  outputs.append({
260
  "type": "function_call_output",
261
  "call_id": call["call_id"],
 
268
  "tools": tools,
269
  "tool_choice": "auto",
270
  }
271
+ payload = self._request(next_body, request_session_id)
272
  else:
273
  raise RuntimeError("OpenCode Go tool loop exceeded 8 rounds")
274
 
275
  answer = self._text(payload)
276
  if not answer:
277
  raise RuntimeError("OpenCode Go returned no text: " + json.dumps(payload, ensure_ascii=False)[:1200])
278
+ return CodexResult(final_response=answer, tool_names=tuple(tool_names), tool_events=tuple(tool_events))
codex_native_harness.py CHANGED
@@ -9,6 +9,7 @@ from __future__ import annotations
9
 
10
  import json
11
  import os
 
12
  import shutil
13
  import subprocess
14
  import uuid
@@ -20,6 +21,8 @@ from typing import Any
20
  class CodexResult:
21
  final_response: str
22
  finish_reason: str = "completed"
 
 
23
 
24
 
25
  def _text_from_value(value: Any) -> str:
@@ -52,6 +55,27 @@ def _event_text(event: dict[str, Any]) -> str:
52
  return ""
53
 
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  class CodexNativeHarness:
56
  """Run the official Codex CLI with the configured MCP server."""
57
 
@@ -142,6 +166,8 @@ class CodexNativeHarness:
142
  raise RuntimeError(f"无法启动官方 Codex CLI: {exc}") from exc
143
 
144
  candidates: list[str] = []
 
 
145
  for line in (completed.stdout or "").splitlines():
146
  raw = line.strip()
147
  if not raw:
@@ -154,6 +180,16 @@ class CodexNativeHarness:
154
  text = _event_text(event)
155
  if text:
156
  candidates.append(text)
 
 
 
 
 
 
 
 
 
 
157
 
158
  if completed.returncode != 0:
159
  stderr_detail = (completed.stderr or "").strip()[-1200:]
@@ -172,4 +208,4 @@ class CodexNativeHarness:
172
  answer = candidates[-1].strip() if candidates else ""
173
  if not answer:
174
  raise RuntimeError("官方 Codex CLI 没有返回最终文本")
175
- return CodexResult(final_response=answer)
 
9
 
10
  import json
11
  import os
12
+ import re
13
  import shutil
14
  import subprocess
15
  import uuid
 
21
  class CodexResult:
22
  final_response: str
23
  finish_reason: str = "completed"
24
+ tool_names: tuple[str, ...] = ()
25
+ tool_events: tuple[str, ...] = ()
26
 
27
 
28
  def _text_from_value(value: Any) -> str:
 
55
  return ""
56
 
57
 
58
+ _MARINE_TOOL_RE = re.compile(r"\bmcp_marine_[A-Za-z0-9_]+\b")
59
+
60
+ def _tool_names_from_event(event: dict[str, Any]) -> list[str]:
61
+ if not isinstance(event, dict):
62
+ return []
63
+ event_type=str(event.get("type") or "").lower()
64
+ item=event.get("item") if isinstance(event.get("item"),dict) else {}
65
+ item_type=str(item.get("type") or "").lower()
66
+ tool_shaped=(
67
+ any(token in event_type for token in ("tool","mcp","function"))
68
+ or any(token in item_type for token in ("tool","mcp","function"))
69
+ )
70
+ if not tool_shaped:
71
+ return []
72
+ try:
73
+ raw=json.dumps(event,ensure_ascii=False,default=str)
74
+ except Exception:
75
+ raw=str(event)
76
+ return list(dict.fromkeys(_MARINE_TOOL_RE.findall(raw)))
77
+
78
+
79
  class CodexNativeHarness:
80
  """Run the official Codex CLI with the configured MCP server."""
81
 
 
166
  raise RuntimeError(f"无法启动官方 Codex CLI: {exc}") from exc
167
 
168
  candidates: list[str] = []
169
+ tool_names: list[str] = []
170
+ tool_events: list[str] = []
171
  for line in (completed.stdout or "").splitlines():
172
  raw = line.strip()
173
  if not raw:
 
180
  text = _event_text(event)
181
  if text:
182
  candidates.append(text)
183
+ names=_tool_names_from_event(event)
184
+ if names:
185
+ for name in names:
186
+ if name not in tool_names:
187
+ tool_names.append(name)
188
+ if len(tool_events)<40:
189
+ try:
190
+ tool_events.append(json.dumps(event,ensure_ascii=False,default=str)[:12000])
191
+ except Exception:
192
+ tool_events.append(str(event)[:12000])
193
 
194
  if completed.returncode != 0:
195
  stderr_detail = (completed.stderr or "").strip()[-1200:]
 
208
  answer = candidates[-1].strip() if candidates else ""
209
  if not answer:
210
  raise RuntimeError("官方 Codex CLI 没有返回最终文本")
211
+ return CodexResult(final_response=answer, tool_names=tuple(tool_names), tool_events=tuple(tool_events))
services/chat_runtime.py CHANGED
@@ -535,11 +535,30 @@ async def harness_stream_chat(tid,prompt):
535
  tid,
536
  USER_SYSTEM,
537
  )
 
 
 
 
 
 
 
 
 
538
 
539
  harness_prompt=(
540
  "[APPLICATION_CONTEXT]\n"
541
  + app_context
542
  + "\n[/APPLICATION_CONTEXT]\n\n"
 
 
 
 
 
 
 
 
 
 
543
  + "[CURRENT_USER_MESSAGE]\n"
544
  + prompt
545
  + "\n[/CURRENT_USER_MESSAGE]"
@@ -591,6 +610,23 @@ async def harness_stream_chat(tid,prompt):
591
  "Codex Harness 没有返回有效文本。"
592
  )
593
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
  log.info(
595
  "Codex Harness completed: "
596
  "thread=%s model=%s reason=%s uploads=%s",
@@ -628,12 +664,11 @@ async def harness_stream_chat(tid,prompt):
628
  len(pending_uploads),
629
  )
630
 
 
631
  yield out(
632
  "error",
633
  {
634
- "text":
635
- "Codex Harness 调用失败:"
636
- + str(exc)[:500],
637
  "stage":"harness",
638
  },
639
  )
@@ -655,14 +690,23 @@ def _is_runtime_info_prompt(prompt: str) -> bool:
655
 
656
 
657
  def _is_marine_health_prompt(prompt: str) -> bool:
658
- """Recognize a live Marine API status question without invoking the model."""
659
- text = str(prompt or "").strip().lower()
660
- health_terms = (
 
 
661
  "海洋数据服务器连接", "检查海洋数据服务器", "数据服务器连接",
662
- "三个数据域", "三个域是否在线", "ocean、tuna、squid",
663
- "ocean, tuna, squid", "marine health", "marine server health",
 
 
 
 
 
 
664
  )
665
- return any(term in text for term in health_terms)
 
666
 
667
 
668
  def _is_fisheries_inventory_prompt(prompt: str) -> bool:
@@ -795,55 +839,81 @@ async def _direct_fisheries_inventory_answer(prompt: str) -> str:
795
 
796
 
797
  async def _direct_marine_health_answer():
798
- """Query the configured Marine API directly for a deterministic status answer."""
799
- httpx_module = globals().get("httpx")
800
  if httpx_module is None:
801
  try:
802
  import httpx as httpx_module
803
  except Exception as exc:
804
  return f"海洋数据服务器连接检查失败:HTTP 客户端不可用({exc})"
805
- base = str(globals().get("MARINE_API_URL") or "").rstrip("/")
806
  if not base:
807
  return "海洋数据服务器未配置 MARINE_API_URL。"
808
 
809
- candidates = [f"{base}/health"]
810
- # HF may expose the school API below the app's /marine reverse-proxy path.
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  if not base.endswith("/marine"):
812
- candidates.append(f"{base}/marine/health")
813
-
814
- last_error = ""
815
- async with httpx_module.AsyncClient(timeout=12.0, follow_redirects=True) as client:
816
  for url in candidates:
817
  try:
818
- response = await client.get(url)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
819
  if response.is_success:
820
- payload = response.json()
821
- domains = payload.get("domains") if isinstance(payload, dict) else None
822
- labels = {
823
- "ocean": "在线",
824
- "tuna": "在线",
825
- "squid": "在线",
826
- }
827
- if isinstance(domains, dict):
828
- for key in labels:
829
- value = domains.get(key)
830
- if isinstance(value, dict):
831
- value = value.get("status") or value.get("online")
832
- if value in (False, "offline", "未连接", "不可用"):
833
- labels[key] = "离线/不可用"
834
- elif isinstance(domains, list):
835
- for key in labels:
836
- if key not in {str(item).lower() for item in domains}:
837
- labels[key] = "未在健康响应中列出"
838
  return (
839
- "海洋数据服务器连接正常\n"
840
- f"- Ocean:{labels['ocean']}\n"
841
- f"- Tuna:{labels['tuna']}\n"
842
- f"- Squid:{labels['squid']}"
843
  )
844
- last_error = f"HTTP {response.status_code}"
845
  except Exception as exc:
846
- last_error = str(exc)
847
  return f"海洋数据服务器连接检查失败:{last_error[:240]}"
848
 
849
 
 
535
  tid,
536
  USER_SYSTEM,
537
  )
538
+ history_context=""
539
+ try:
540
+ history_context=_format_recent_thread_history(
541
+ tid,
542
+ limit=12,
543
+ max_chars=24000,
544
+ )
545
+ except Exception:
546
+ history_context=""
547
 
548
  harness_prompt=(
549
  "[APPLICATION_CONTEXT]\n"
550
  + app_context
551
  + "\n[/APPLICATION_CONTEXT]\n\n"
552
+ + (
553
+ "[PRIOR_CONVERSATION_HISTORY]\n"
554
+ + history_context
555
+ + "\n[/PRIOR_CONVERSATION_HISTORY]\n\n"
556
+ + "[HISTORY_INSTRUCTION]\n"
557
+ + "Use prior conversation only to resolve references and continuity. "
558
+ "The current user message has priority over older content.\n"
559
+ + "[/HISTORY_INSTRUCTION]\n\n"
560
+ if history_context else ""
561
+ )
562
  + "[CURRENT_USER_MESSAGE]\n"
563
  + prompt
564
  + "\n[/CURRENT_USER_MESSAGE]"
 
610
  "Codex Harness 没有返回有效文本。"
611
  )
612
 
613
+ tool_names=tuple(getattr(result,"tool_names",()) or ())
614
+ tool_events=tuple(getattr(result,"tool_events",()) or ())
615
+ export_tool_completed=any(
616
+ name.endswith("marine_export")
617
+ or name.endswith("marine_export_range")
618
+ or name.endswith("_export")
619
+ for name in tool_names
620
+ )
621
+ export_error=_ocean_export_execution_error(
622
+ prompt,
623
+ export_tool_completed=export_tool_completed,
624
+ tool_result_text="\n".join(tool_events),
625
+ final_answer=final_answer,
626
+ )
627
+ if export_error:
628
+ raise RuntimeError(export_error)
629
+
630
  log.info(
631
  "Codex Harness completed: "
632
  "thread=%s model=%s reason=%s uploads=%s",
 
664
  len(pending_uploads),
665
  )
666
 
667
+ public_error=_public_error(str(exc))
668
  yield out(
669
  "error",
670
  {
671
+ "text":public_error,
 
 
672
  "stage":"harness",
673
  },
674
  )
 
690
 
691
 
692
  def _is_marine_health_prompt(prompt: str) -> bool:
693
+ """Recognize actual connectivity/status questions, not any domain mention."""
694
+ text=str(prompt or "").strip().lower()
695
+ if not text:
696
+ return False
697
+ explicit_phrases=(
698
  "海洋数据服务器连接", "检查海洋数据服务器", "数据服务器连接",
699
+ "三个域是否在线", "三个数据域是否在线", "marine health",
700
+ "marine server health",
701
+ )
702
+ if any(term in text for term in explicit_phrases):
703
+ return True
704
+ status_terms=(
705
+ "在线","离线","连接","连通","状态","健康","可用","不可用",
706
+ "正常吗","是否正常","reachable","online","offline","status","health",
707
  )
708
+ domain_terms=("ocean","tuna","squid","marine","数据服务器","数据域")
709
+ return any(x in text for x in status_terms) and any(x in text for x in domain_terms)
710
 
711
 
712
  def _is_fisheries_inventory_prompt(prompt: str) -> bool:
 
839
 
840
 
841
  async def _direct_marine_health_answer():
842
+ """Query live domain status without inferring unavailable states."""
843
+ httpx_module=globals().get("httpx")
844
  if httpx_module is None:
845
  try:
846
  import httpx as httpx_module
847
  except Exception as exc:
848
  return f"海洋数据服务器连接检查失败:HTTP 客户端不可用({exc})"
849
+ base=str(globals().get("MARINE_API_URL") or "").rstrip("/")
850
  if not base:
851
  return "海洋数据服务器未配置 MARINE_API_URL。"
852
 
853
+ def status_label(value):
854
+ if isinstance(value,dict):
855
+ value=value.get("status",value.get("online",value.get("available")))
856
+ if value is True:
857
+ return "在线"
858
+ if value is False:
859
+ return "离线/不可用"
860
+ low=str(value or "").strip().lower()
861
+ if low in {"ok","online","ready","available","healthy","true","1"}:
862
+ return "在线"
863
+ if low in {"offline","down","unavailable","error","false","0"}:
864
+ return "离线/不可用"
865
+ return "未确认"
866
+
867
+ candidates=[f"{base}/domains"]
868
  if not base.endswith("/marine"):
869
+ candidates.append(f"{base}/marine/domains")
870
+ last_error=""
871
+ async with httpx_module.AsyncClient(timeout=12.0,follow_redirects=True) as client:
 
872
  for url in candidates:
873
  try:
874
+ response=await client.get(url)
875
+ if not response.is_success:
876
+ last_error=f"HTTP {response.status_code}"
877
+ continue
878
+ payload=response.json()
879
+ domain_payload=payload.get("domains") if isinstance(payload,dict) else None
880
+ if not isinstance(domain_payload,(dict,list)):
881
+ domain_payload=payload if isinstance(payload,(dict,list)) else None
882
+ labels={key:"未确认" for key in ("ocean","tuna","squid")}
883
+ if isinstance(domain_payload,dict):
884
+ for key in labels:
885
+ if key in domain_payload:
886
+ labels[key]=status_label(domain_payload.get(key))
887
+ elif isinstance(domain_payload,list):
888
+ present={str(x.get("name") if isinstance(x,dict) else x).lower() for x in domain_payload}
889
+ for key in labels:
890
+ labels[key]="在线" if key in present else "未确认"
891
+ return (
892
+ "Marine 数据服务已连通。\n"
893
+ f"- Ocean:{labels['ocean']}\n"
894
+ f"- Tuna:{labels['tuna']}\n"
895
+ f"- Squid:{labels['squid']}\n"
896
+ "未确认表示服务器响应中没有足够状态字段,平台不会把它自动当成在线。"
897
+ )
898
+ except Exception as exc:
899
+ last_error=str(exc)
900
+
901
+ # Fallback health only proves the API is reachable, not that each domain is online.
902
+ health_urls=[f"{base}/health"]
903
+ if not base.endswith("/marine"):
904
+ health_urls.append(f"{base}/marine/health")
905
+ async with httpx_module.AsyncClient(timeout=12.0,follow_redirects=True) as client:
906
+ for url in health_urls:
907
+ try:
908
+ response=await client.get(url)
909
  if response.is_success:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
910
  return (
911
+ "Marine API 可以连接,但当前未取得 Ocean/Tuna/Squid 的逐域状态。"
912
+ "请在“设置 → 系统健康”中重新检测详细状态。"
 
 
913
  )
914
+ last_error=f"HTTP {response.status_code}"
915
  except Exception as exc:
916
+ last_error=str(exc)
917
  return f"海洋数据服务器连接检查失败:{last_error[:240]}"
918
 
919
 
static/js/app.js CHANGED
@@ -340,7 +340,7 @@ function persist(role,text,meta=""){
340
  if(role==="assistant"&&meta!=="error")refreshSessionSummary(x,true);
341
  setUpdated(x);save();hist()
342
  }
343
- function historyPayload(x,limit=8){
344
  if(!x)return[];
345
  const items=[];
346
  for(const m of (x.messages||[])){
 
340
  if(role==="assistant"&&meta!=="error")refreshSessionSummary(x,true);
341
  setUpdated(x);save();hist()
342
  }
343
+ function historyPayload(x,limit=12){
344
  if(!x)return[];
345
  const items=[];
346
  for(const m of (x.messages||[])){
tests/test_agent_reliability_v4620.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ ROOT=Path(__file__).resolve().parents[1]
3
+
4
+ def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
+
7
+ def test_harness_receives_recent_conversation_history():
8
+ py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8")
9
+ assert "_format_recent_thread_history(" in py
10
+ assert "[PRIOR_CONVERSATION_HISTORY]" in py
11
+ assert "max_chars=24000" in py
12
+
13
+ def test_health_intent_does_not_trigger_on_domain_names_alone():
14
+ py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8")
15
+ block=py[py.index("def _is_marine_health_prompt"):py.index("def _is_fisheries_inventory_prompt")]
16
+ assert '"ocean、tuna、squid"' not in block
17
+ assert "status_terms" in block
18
+ assert "domain_terms" in block
19
+
20
+ def test_direct_health_never_defaults_all_domains_online():
21
+ py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8")
22
+ block=py[py.index("async def _direct_marine_health_answer"):py.index("def _is_explicit_new_data_query")]
23
+ assert 'labels={key:"未确认"' in block
24
+ assert '/domains' in block
25
+ assert "平台不会把它自动当成在线" in block
26
+
27
+ def test_native_harness_preserves_tool_evidence():
28
+ py=(ROOT/"codex_native_harness.py").read_text(encoding="utf-8")
29
+ assert "tool_names: tuple[str, ...]" in py
30
+ assert "_tool_names_from_event" in py
31
+ assert "tool_events=tuple(tool_events)" in py
32
+
33
+ def test_harness_export_is_fail_closed():
34
+ py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8")
35
+ assert 'tool_names=tuple(getattr(result,"tool_names"' in py
36
+ assert "export_tool_completed=any(" in py
37
+ assert "_ocean_export_execution_error(" in py
38
+
39
+ def test_adapter_does_not_store_shared_mutable_session_id():
40
+ py=(ROOT/"codex_harness.py").read_text(encoding="utf-8")
41
+ assert "request_session_id" in py
42
+ assert "self._session_id =" not in py
43
+
44
+ def test_harness_errors_are_sanitized():
45
+ py=(ROOT/"services/chat_runtime.py").read_text(encoding="utf-8")
46
+ assert "public_error=_public_error(str(exc))" in py
47
+ assert '"text":public_error' in py
48
+
49
+ def test_frontend_sends_more_recent_context():
50
+ js=(ROOT/"static/js/app.js").read_text(encoding="utf-8")
51
+ assert "function historyPayload(x,limit=12)" in js
tests/test_api_entry_preflight_v456.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_guard_is_before_auth_and_dispatch():
8
  chat=(ROOT/"routes"/"chat.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_guard_is_before_auth_and_dispatch():
8
  chat=(ROOT/"routes"/"chat.py").read_text(encoding="utf-8")
tests/test_chat_lifecycle_progress_v393.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_393():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_frontend_lifecycle_progress():
9
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_393():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_frontend_lifecycle_progress():
9
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_clarification_ui_v454.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_missing_date_reuses_canonical_region_bbox():
8
  rt=(ROOT/"services"/"chat_runtime.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_missing_date_reuses_canonical_region_bbox():
8
  rt=(ROOT/"services"/"chat_runtime.py").read_text(encoding="utf-8")
tests/test_classification_cleanup_v466.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_seaaroundus_alias():
8
  py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_seaaroundus_alias():
8
  py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8")
tests/test_context_and_multitask_finalize_v451.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_new_query_slot_reset():
8
  rt=(ROOT/"services"/"chat_runtime.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_new_query_slot_reset():
8
  rt=(ROOT/"services"/"chat_runtime.py").read_text(encoding="utf-8")
tests/test_conversation_summary_v4613.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_frontend_auto_summary_and_manual_override():
8
  js=(ROOT/"static/js/app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_frontend_auto_summary_and_manual_override():
8
  js=(ROOT/"static/js/app.js").read_text(encoding="utf-8")
tests/test_download_host_authorization_v460.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_batch_url_authorization_helper_exists():
8
  py=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_batch_url_authorization_helper_exists():
8
  py=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8")
tests/test_download_opfs_fallback_v459.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_opfs_is_probed_not_assumed():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_opfs_is_probed_not_assumed():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_download_overlay_v4618.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.18"
6
 
7
  def test_native_download_handoff_finishes_overlay():
8
  js=(ROOT/"static/js/app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_native_download_handoff_finishes_overlay():
8
  js=(ROOT/"static/js/app.js").read_text(encoding="utf-8")
tests/test_download_progress_v391.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_391():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_p1_label_removed():
9
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_391():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_p1_label_removed():
9
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_download_resilience_v422.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_refresh_route_registered():
9
  t=(ROOT/"routes"/"ocean_batch.py").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_refresh_route_registered():
9
  t=(ROOT/"routes"/"ocean_batch.py").read_text(encoding="utf-8")
tests/test_frontend_preflight_v457.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_frontend_preflight_exists_before_progress():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_frontend_preflight_exists_before_progress():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_history_dedupe_recent_summary_v4614.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_frontend_dedupe_and_merge():
8
  js=(ROOT/"static/js/app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_frontend_dedupe_and_merge():
8
  js=(ROOT/"static/js/app.js").read_text(encoding="utf-8")
tests/test_history_sidebar_v373.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_373():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_project_history_backend():
9
  route=(ROOT/"routes"/"project_package.py").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_373():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_project_history_backend():
9
  route=(ROOT/"routes"/"project_package.py").read_text(encoding="utf-8")
tests/test_large_download_jobs_v400.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_400():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_download_job_backend():
9
  route=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_400():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_download_job_backend():
9
  route=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8")
tests/test_manual_tasks_v469.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_manual_task_backend_exists():
8
  service=(ROOT/"services/manual_tasks.py").read_text(encoding="utf-8")
@@ -27,4 +27,4 @@ def test_frontend_new_task_controls():
27
  assert "标记完成" in js
28
  assert "重新打开" in js
29
  assert "manual-delete" in js
30
- assert "账号服务器持久化(可跨设备" in js
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_manual_task_backend_exists():
8
  service=(ROOT/"services/manual_tasks.py").read_text(encoding="utf-8")
 
27
  assert "标记完成" in js
28
  assert "重新打开" in js
29
  assert "manual-delete" in js
30
+ assert "账号同步:" in js and "可跨设备同步" in js and "服务器会话" in js
tests/test_merged_health_v433.py CHANGED
@@ -3,7 +3,7 @@ from services.ocean_batch import parse_ocean_batch_request
3
  ROOT=Path(__file__).resolve().parents[1]
4
 
5
  def test_merged_version():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.9"
7
 
8
  def test_ocean_month_batch_survives_merge():
9
  p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载")
 
3
  ROOT=Path(__file__).resolve().parents[1]
4
 
5
  def test_merged_version():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_ocean_month_batch_survives_merge():
9
  p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载")
tests/test_multitask_observability_v442.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_frontend_sends_multitask_trace_headers():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_frontend_sends_multitask_trace_headers():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_multitask_sse_finalize_v452.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_parallel_sse_uses_real_blank_line_separator():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_parallel_sse_uses_real_blank_line_separator():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_noaa_ramldb_roles_v465.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_noaa_and_ramldb_aliases():
8
  py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_noaa_and_ramldb_aliases():
8
  py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8")
tests/test_ocean_batch_downloads_v420.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_420():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_manifest_has_filename_and_sizes():
9
  svc=(ROOT/"services"/"ocean_batch.py").read_text(encoding="utf-8")
@@ -17,13 +17,13 @@ def test_frontend_has_one_click_batch_download():
17
  assert "function startOceanBatchDownloadAll" in js
18
  assert "function runOceanBatchDownloadQueue" in js
19
  assert "function waitManagedDownloadStatus" in js
20
- assert "下载全部" in js
21
  assert "查看文件" in js
22
 
23
  def test_batch_download_uses_managed_range_jobs():
24
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
25
  assert 'j("/api/download/jobs"' in js
26
- assert "triggerBrowserSave(child.stream_url" in js
27
  assert "resume_supported" in js
28
  assert "30 秒无新字节" in js
29
 
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_420():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_manifest_has_filename_and_sizes():
9
  svc=(ROOT/"services"/"ocean_batch.py").read_text(encoding="utf-8")
 
17
  assert "function startOceanBatchDownloadAll" in js
18
  assert "function runOceanBatchDownloadQueue" in js
19
  assert "function waitManagedDownloadStatus" in js
20
+ assert "打包 ZIP 下载" in js and "逐个下载" in js
21
  assert "查看文件" in js
22
 
23
  def test_batch_download_uses_managed_range_jobs():
24
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
25
  assert 'j("/api/download/jobs"' in js
26
+ assert "triggerBrowserSave(job.stream_url" in js
27
  assert "resume_supported" in js
28
  assert "30 秒无新字节" in js
29
 
tests/test_ocean_batch_routing_v421.py CHANGED
@@ -4,7 +4,7 @@ from services.ocean_batch import parse_ocean_batch_request
4
  ROOT=pathlib.Path(__file__).resolve().parents[1]
5
 
6
  def test_version_421():
7
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
8
 
9
  def test_exact_user_month_query_routes_to_batch():
10
  p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载")
 
4
  ROOT=pathlib.Path(__file__).resolve().parents[1]
5
 
6
  def test_version_421():
7
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
8
 
9
  def test_exact_user_month_query_routes_to_batch():
10
  p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载")
tests/test_ocean_batch_v410.py CHANGED
@@ -4,7 +4,7 @@ from services.ocean_batch import parse_ocean_batch_request
4
  ROOT=pathlib.Path(__file__).resolve().parents[1]
5
 
6
  def test_version_410():
7
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
8
 
9
  def test_month_three_variable_batch_plan():
10
  p=parse_ocean_batch_request("查询并导出 2002 年 8 月西北太平洋 120°E–140°E、20°N–40°N 的 SST、SSH、CHL 数据,并打包下载")
 
4
  ROOT=pathlib.Path(__file__).resolve().parents[1]
5
 
6
  def test_version_410():
7
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
8
 
9
  def test_month_three_variable_batch_plan():
10
  p=parse_ocean_batch_request("查询并导出 2002 年 8 月西北太平洋 120°E–140°E、20°N–40°N 的 SST、SSH、CHL 数据,并打包下载")
tests/test_ocean_batch_zip_manifest_v4619.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.19"
6
 
7
  def test_zip_backend_routes_exist():
8
  py=(ROOT/"routes/ocean_batch.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_zip_backend_routes_exist():
8
  py=(ROOT/"routes/ocean_batch.py").read_text(encoding="utf-8")
tests/test_ocean_date_default_v453.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_missing_date_never_defaults_to_today():
8
  rt=(ROOT/"services"/"chat_runtime.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_missing_date_never_defaults_to_today():
8
  rt=(ROOT/"services"/"chat_runtime.py").read_text(encoding="utf-8")
tests/test_ocean_download_url_base_v462.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_generic_public_base_not_used():
8
  py=(ROOT/"services"/"ocean_batch.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_generic_public_base_not_used():
8
  py=(ROOT/"services"/"ocean_batch.py").read_text(encoding="utf-8")
tests/test_p0_finish_v380.py CHANGED
@@ -3,7 +3,7 @@ import pathlib, re, subprocess, shutil
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_380():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_chat_runtime_extracted():
9
  ui=(ROOT/"ui_server.py").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_380():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_chat_runtime_extracted():
9
  ui=(ROOT/"ui_server.py").read_text(encoding="utf-8")
tests/test_p0_phase2_v371.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_frontend_feature_modules_exist():
9
  html=(ROOT/"app.html").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_frontend_feature_modules_exist():
9
  html=(ROOT/"app.html").read_text(encoding="utf-8")
tests/test_p0_phase3_v372.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_372():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_route_modules_exist():
9
  for name in ("datasets.py","user_state.py","project_package.py","data_health.py"):
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_372():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_route_modules_exist():
9
  for name in ("datasets.py","user_state.py","project_package.py","data_health.py"):
tests/test_platform_reliability_v450.py CHANGED
@@ -6,7 +6,7 @@ from services.ocean_batch import parse_ocean_batch_request
6
  ROOT=Path(__file__).resolve().parents[1]
7
 
8
  def test_version():
9
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
10
 
11
  def test_squid_center_uses_repository_membership():
12
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
@@ -15,7 +15,7 @@ def test_squid_center_uses_repository_membership():
15
  assert "file_count_by_repository" in api
16
  assert "domains.includes(domain)" in js
17
  assert "squid_file_count" in js
18
- assert "实时仓库文件树口径" in js
19
 
20
  def test_single_ocean_export_forces_managed_proxy():
21
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
6
  ROOT=Path(__file__).resolve().parents[1]
7
 
8
  def test_version():
9
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
10
 
11
  def test_squid_center_uses_repository_membership():
12
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
15
  assert "file_count_by_repository" in api
16
  assert "domains.includes(domain)" in js
17
  assert "squid_file_count" in js
18
+ assert "Hugging Face main 实时文件树" in js
19
 
20
  def test_single_ocean_export_forces_managed_proxy():
21
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_product_finish_v4615.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_system_health_is_settings_embedded_and_modular():
8
  app=(ROOT/"static/js/app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_system_health_is_settings_embedded_and_modular():
8
  app=(ROOT/"static/js/app.js").read_text(encoding="utf-8")
tests/test_project_package_download_v4617.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.17"
6
 
7
  def test_project_package_download_is_capability_url():
8
  py=(ROOT/"routes/project_package.py").read_text(encoding="utf-8")
@@ -21,4 +21,4 @@ def test_same_origin_download_probes_before_native_save():
21
  def test_project_download_button_explains_browser_handoff():
22
  js=(ROOT/"static/js/project-package.js").read_text(encoding="utf-8")
23
  assert "正在交给浏览器" in js
24
- assert "已交给浏览器下载" in js
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_project_package_download_is_capability_url():
8
  py=(ROOT/"routes/project_package.py").read_text(encoding="utf-8")
 
21
  def test_project_download_button_explains_browser_handoff():
22
  js=(ROOT/"static/js/project-package.js").read_text(encoding="utf-8")
23
  assert "正在交给浏览器" in js
24
+ assert "已交给浏览器;实际完成状态请看浏览器下载" in js
tests/test_quality_center_v362.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT = pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_371():
6
- assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == "4.6.15"
7
 
8
  def test_native_health_backend():
9
  text = (ROOT / "routes" / "data_health.py").read_text(encoding="utf-8")
 
3
  ROOT = pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_371():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_native_health_backend():
9
  text = (ROOT / "routes" / "data_health.py").read_text(encoding="utf-8")
tests/test_quality_trust_v361.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version_current():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_quality_confidence_is_now_native_api_metric():
8
  text=(ROOT/"routes"/"data_health.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version_current():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_quality_confidence_is_now_native_api_metric():
8
  text=(ROOT/"routes"/"data_health.py").read_text(encoding="utf-8")
tests/test_reference_cleanup_v468.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_reference_and_reproducibility_are_auxiliary():
8
  py=(ROOT/"routes"/"datasets.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_reference_and_reproducibility_are_auxiliary():
8
  py=(ROOT/"routes"/"datasets.py").read_text(encoding="utf-8")
tests/test_release_stability.py CHANGED
@@ -7,7 +7,7 @@ def test_version_is_single_source():
7
  version = (ROOT / "VERSION").read_text(encoding="utf-8").strip()
8
  ui = (ROOT / "ui_server.py").read_text(encoding="utf-8")
9
  html = (ROOT / "app.html").read_text(encoding="utf-8")
10
- assert version == "4.6.15"
11
  assert 'with_name("VERSION")' in ui
12
  assert "__APP_VERSION__" in html
13
 
 
7
  version = (ROOT / "VERSION").read_text(encoding="utf-8").strip()
8
  ui = (ROOT / "ui_server.py").read_text(encoding="utf-8")
9
  html = (ROOT / "app.html").read_text(encoding="utf-8")
10
+ assert version == '4.6.20'
11
  assert 'with_name("VERSION")' in ui
12
  assert "__APP_VERSION__" in html
13
 
tests/test_resumable_batch_download_v458.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_network_loss_pauses_not_fails_all():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_network_loss_pauses_not_fails_all():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_route_region_clarification_v455.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_route_guard_exists_before_agent_dispatch():
8
  chat=(ROOT/"routes"/"chat.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_route_guard_exists_before_agent_dispatch():
8
  chat=(ROOT/"routes"/"chat.py").read_text(encoding="utf-8")
tests/test_runtime_hotfix_v376.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_376():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_no_stray_async_before_sidebar_toggle():
9
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_376():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_no_stray_async_before_sidebar_toggle():
9
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_sidebar_collapse_v374.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_374():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_sidebar_toggle_markup():
9
  html=(ROOT/"app.html").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_374():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_sidebar_toggle_markup():
9
  html=(ROOT/"app.html").read_text(encoding="utf-8")
tests/test_sidebar_partial_collapse_v375.py CHANGED
@@ -3,7 +3,7 @@ import pathlib
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_375():
6
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
7
 
8
  def test_sidebar_order_and_groups():
9
  html=(ROOT/"app.html").read_text(encoding="utf-8")
 
3
  ROOT=pathlib.Path(__file__).resolve().parents[1]
4
 
5
  def test_version_375():
6
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
7
 
8
  def test_sidebar_order_and_groups():
9
  html=(ROOT/"app.html").read_text(encoding="utf-8")
tests/test_smart_progress_avatar_sync_v441.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_simple_chat_hides_progress_until_needed():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_simple_chat_hides_progress_until_needed():
8
  js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
tests/test_source_mapping_roles_v464.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
- assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.6.15"
6
 
7
  def test_new_source_mappings():
8
  py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8")
 
2
  ROOT=Path(__file__).resolve().parents[1]
3
 
4
  def test_version():
5
+ assert (ROOT/'VERSION').read_text(encoding='utf-8').strip()=='4.6.20'
6
 
7
  def test_new_source_mappings():
8
  py=(ROOT/"sidebar_catalog.py").read_text(encoding="utf-8")