v3.13.0: Desktop Updater, profile system fix, Antigravity E2E, conservative compaction
- Codex Desktop Updater: check/install/rollback/service management + manual rebuild - Fix Codex CLI 0.134.0 profiles: separate <slug>.config.toml files - Fix Antigravity: prod endpoint first, model resolution, OAUTH_PROVIDER - Fix compaction: max_input_items 60->200 for 1M-token models - Antigravity E2E test suite: test-antigravity.sh - Windows GUI: UpdateDesktopWindow + profile slug fix - Updated CHANGELOG.md and README.md
This commit is contained in:
@@ -83,6 +83,109 @@ model_catalog_json = ""
|
||||
"""
|
||||
|
||||
CHANGELOG = [
|
||||
("3.13.0", "2026-05-27", [
|
||||
"Codex Desktop Updater: auto-update from ilysenko/codex-desktop-linux",
|
||||
"Fix Antigravity: prod endpoint first, model resolution, OAUTH_PROVIDER derivation",
|
||||
"Fix Codex CLI 0.134.0 profile system: separate .config.toml files",
|
||||
"Fix compaction: max_input_items 60->200 for 1M-token Antigravity models",
|
||||
"Antigravity E2E test suite: bash test-antigravity.sh",
|
||||
]),
|
||||
("3.12.1", "2026-05-27", [
|
||||
"Fix Antigravity adapter (PR #15): simplify model resolution",
|
||||
"Removed broken schema sanitization, restored correct headers",
|
||||
"Expanded model alias map for all Antigravity variants",
|
||||
"Re-enabled gRPC fallback by default",
|
||||
]),
|
||||
("3.12.0", "2026-05-27", [
|
||||
"gRPC auto-fallback for Antigravity provider (PR #13)",
|
||||
"New antigravity_grpc module with protobuf client",
|
||||
"REST 404 triggers gRPC fallback using display names",
|
||||
"gRPC supports streaming and unary generate",
|
||||
"Dynamic version fetch with probe validation",
|
||||
"Antigravity v2 handler rewrite (anti-api approach)",
|
||||
"Safety settings, stopSequences, sessionId, requestType: agent",
|
||||
]),
|
||||
("3.11.11", "2026-05-26", [
|
||||
"Final trimming only removes plain messages, never function_call_output",
|
||||
]),
|
||||
("3.11.10", "2026-05-26", [
|
||||
"Fix Antigravity: interleave function_call/output pairs in correct sequence (PR #11)",
|
||||
"Fix Gemini sanitizer: trim leading/trailing non-user turns for Google API compliance",
|
||||
"Stricter function call/response isolation — no merging across role boundaries",
|
||||
]),
|
||||
("3.11.9", "2026-05-26", [
|
||||
"Fix Antigravity: preserve functionCall/functionResponse in Gemini sanitizer (PR #10)",
|
||||
"Prevents tool responses from being merged/dropped in multi-turn Antigravity sessions",
|
||||
]),
|
||||
("3.11.8", "2026-05-26", [
|
||||
"Vision description cache persisted across requests (no redundant API calls for same image)",
|
||||
"Merge PR #8: fix vision cache persistence across requests",
|
||||
]),
|
||||
("3.11.7", "2026-05-26", [
|
||||
"Vision auto-detect: uses provider's own vision model (e.g. 0G-Qwen-VL) as fallback for image description",
|
||||
"Vision preprocessing replaces image stripping: images described via API instead of just removed",
|
||||
"Fix AttributeError in image_url handling when value is string not dict",
|
||||
"Merge PR #6: vision/OCR preprocessing for text-only models",
|
||||
"Merge PR #7: 177 unit tests for translate-proxy.py",
|
||||
"Auth os error 2 fix: GUI shows config-missing message instead of raw error",
|
||||
]),
|
||||
("3.11.6", "2026-05-26", [
|
||||
"Antigravity loop breakers: per-session tracking, edit-intent nudge (first turn only)",
|
||||
"Loop breaker: same tool+args repeated 5+ times triggers force finalization",
|
||||
"Latest user instruction appended exactly once per request",
|
||||
"Detailed [antigravity-loop] logging for all tracking fields",
|
||||
"has_content fix: function_call now counts as valid output (no more infinite loops)",
|
||||
"Antigravity-only changes, no touch to other providers",
|
||||
]),
|
||||
("3.11.5", "2026-05-26", [
|
||||
"Token-aware compaction: fixes context_length_exceeded on small-context models (25 items x 1600 tokens)",
|
||||
"Proactive compaction triggers on token count (>80% model limit), not just item count",
|
||||
"Universal adaptive compaction: removed crof.ai-only gates, all providers get compaction",
|
||||
"Vision model detection: strips images for non-vision models, keeps for vision-capable ones",
|
||||
"Per-model token limit learning from context_length_exceeded error messages",
|
||||
"Compaction aggression levels: normal vs extreme when tokens > 1.5x model limit",
|
||||
"Smart-continue text-tool detection: triggers on tool-call text patterns, not just function_call_output",
|
||||
"Active endpoint sync: GUI auto-removes stale endpoint references on startup",
|
||||
]),
|
||||
("3.11.0", "2026-05-26", [
|
||||
"Merge cobra PR: concurrency semaphore (max 3), auto-continue for truncated text",
|
||||
"SO_REUSEADDR on sticky port, proxy-stderr.log, stream diagnostics logging",
|
||||
"Timeout/OSError handler sends response.failed SSE instead of silent drop",
|
||||
"Restart Proxy button: only restarts proxy without killing Codex Desktop",
|
||||
"Tool call argument normalizer: fixes Arguments->arguments, strips markdown wrapping",
|
||||
"Smart-continue loop (2x retries): escalating nudges when model stops text-only mid-task",
|
||||
"XML tool call extraction: parses patterns from text, injects as real calls",
|
||||
"Auto-continue + smart-continue ordered with skip guard to avoid double-firing",
|
||||
"API key hot-reload with mtime tracking + /admin/reload + /admin/verify-key endpoints",
|
||||
"GUI hot-reload: auto-refreshes proxy key on endpoint edit, verifies with upstream",
|
||||
"Synthetic tool-results disabled: was causing deepseek-v4-pro truncation on opencode.ai",
|
||||
]),
|
||||
("3.10.12", "2026-05-26", [
|
||||
"Sticky endpoint: caches last working endpoint, sequential fallback on failure",
|
||||
"Endpoint order: cloudcode-pa first (matches agy CLI), daily-cloudcode-pa fallback",
|
||||
"Anti-stall engine: kills stale proxy processes + clears pycache on startup",
|
||||
"Smart error classification: quota vs capacity vs banned vs validation vs auth",
|
||||
"Rate limit reset parsing: extracts cooldown from error body for accuracy",
|
||||
"Missing headers: X-Client-Name, X-Client-Version, x-goog-api-client, sessionId",
|
||||
"Guardrail skip: simple messages (hi) skip agent guardrail, no more tool-call loops",
|
||||
"Claude fixes: preserve all tools, skip compaction/normalizer/sanitization for Claude",
|
||||
"Normalizer model param: distinguishes Claude vs Gemini for correct behavior",
|
||||
]),
|
||||
("3.10.11", "2026-05-26", [
|
||||
"Hybrid endpoint fallback: cloudcode-pa then daily-cloudcode-pa on 429",
|
||||
"daily-cloudcode-pa.googleapis.com (same endpoint agy-core uses)",
|
||||
"429 errors log full response body for debugging",
|
||||
"Rate-limit marking only after ALL endpoints fail",
|
||||
"Restored SERVICE_DISABLED (403) fallthrough",
|
||||
]),
|
||||
("3.10.10", "2026-05-25", [
|
||||
"Fix normalizer stripping ALL context after compaction on resumed sessions",
|
||||
"No auto-reset when compaction summary present (preserves 1925+ turn history)",
|
||||
"Always preserve compaction summaries in normalizer output",
|
||||
"Deduplicate consecutive identical goal_context messages",
|
||||
"Emergency reset preserves compaction summaries",
|
||||
"Fix hashlib NameError in _antigravity_normalize_context (string comparison instead)",
|
||||
]),
|
||||
("3.10.9", "2026-05-25", [
|
||||
"Antigravity: production-only endpoints (cloudcode-pa.googleapis.com), sandbox blocked unless ALLOW_ANTIGRAVITY_STAGING=1",
|
||||
"Antigravity: 403 SERVICE_DISABLED falls through, 429 returns to client (no sandbox fallback)",
|
||||
@@ -618,6 +721,9 @@ def safe_name(name):
|
||||
digest = hashlib.sha1(name.encode("utf-8")).hexdigest()[:8]
|
||||
return f"{base}-{digest}"
|
||||
|
||||
def _profile_slug(name):
|
||||
return "".join(ch if ch.isalnum() else "-" for ch in name).strip("-") or "default"
|
||||
|
||||
|
||||
def label_for_backend(backend_type):
|
||||
return {
|
||||
@@ -1008,10 +1114,9 @@ def write_config_for_native(endpoint, selected_model):
|
||||
mc_path = PROXY_CONFIG_DIR / f"models-{safe_name(endpoint['name'])}.json"
|
||||
mc_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
mc_path.write_text(json.dumps(model_catalog, indent=2))
|
||||
|
||||
mc_str = str(mc_path).replace("\\", "/")
|
||||
new_config = [
|
||||
f'profile = "{_toml_safe(endpoint["name"])}"\n',
|
||||
|
||||
main_config = [
|
||||
f'model = "{_toml_safe(selected_model)}"\n',
|
||||
f'model_provider = "{_toml_safe(endpoint["name"])}"\n',
|
||||
f'model_catalog_json = "{mc_str}"\n',
|
||||
@@ -1019,16 +1124,21 @@ def write_config_for_native(endpoint, selected_model):
|
||||
f'name = "{_toml_safe(endpoint["name"])}"\n',
|
||||
f'base_url = "{_toml_safe(endpoint["base_url"])}"\n',
|
||||
f'experimental_bearer_token = "{_toml_safe(_resolve_secret(endpoint["api_key"]))}"\n',
|
||||
f'\n[profiles."{endpoint["name"]}"]\n',
|
||||
f'model_provider = "{_toml_safe(endpoint["name"])}"\n',
|
||||
]
|
||||
existing = CONFIG.read_text(encoding="utf-8") if CONFIG.exists() else ""
|
||||
merged = _merge_toml(existing, "".join(main_config))
|
||||
write_secure_text(CONFIG, merged)
|
||||
|
||||
profile_slug = _profile_slug(endpoint["name"])
|
||||
profile_path = CONFIG.parent / f"{profile_slug}.config.toml"
|
||||
profile_lines = [
|
||||
f'model = "{_toml_safe(selected_model)}"\n',
|
||||
f'model_provider = "{_toml_safe(endpoint["name"])}"\n',
|
||||
f'model_catalog_json = "{mc_str}"\n',
|
||||
f'service_tier = "default"\n',
|
||||
f'approvals_reviewer = "user"\n',
|
||||
]
|
||||
existing = CONFIG.read_text(encoding="utf-8") if CONFIG.exists() else ""
|
||||
merged = _merge_toml(existing, "".join(new_config))
|
||||
write_secure_text(CONFIG, merged)
|
||||
write_secure_text(profile_path, "".join(profile_lines))
|
||||
|
||||
|
||||
def write_config_for_translated(endpoint, selected_model, proxy_port=8080):
|
||||
@@ -1037,10 +1147,9 @@ def write_config_for_translated(endpoint, selected_model, proxy_port=8080):
|
||||
mc_path = PROXY_CONFIG_DIR / f"models-{safe_name(endpoint['name'])}.json"
|
||||
mc_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
mc_path.write_text(json.dumps(model_catalog, indent=2))
|
||||
|
||||
mc_str = str(mc_path).replace("\\", "/")
|
||||
new_config = [
|
||||
f'profile = "{_toml_safe(endpoint["name"])}"\n',
|
||||
|
||||
main_config = [
|
||||
f'model = "{_toml_safe(selected_model)}"\n',
|
||||
f'model_provider = "{_toml_safe(endpoint["name"])}"\n',
|
||||
f'model_catalog_json = "{mc_str}"\n',
|
||||
@@ -1048,16 +1157,21 @@ def write_config_for_translated(endpoint, selected_model, proxy_port=8080):
|
||||
f'name = "{_toml_safe(endpoint["name"])}"\n',
|
||||
f'base_url = "http://127.0.0.1:{proxy_port}"\n',
|
||||
f'experimental_bearer_token = "codex-launcher-local"\n',
|
||||
f'\n[profiles."{endpoint["name"]}"]\n',
|
||||
f'model_provider = "{_toml_safe(endpoint["name"])}"\n',
|
||||
]
|
||||
existing = CONFIG.read_text(encoding="utf-8") if CONFIG.exists() else ""
|
||||
merged = _merge_toml(existing, "".join(main_config))
|
||||
write_secure_text(CONFIG, merged)
|
||||
|
||||
profile_slug = _profile_slug(endpoint["name"])
|
||||
profile_path = CONFIG.parent / f"{profile_slug}.config.toml"
|
||||
profile_lines = [
|
||||
f'model = "{_toml_safe(selected_model)}"\n',
|
||||
f'model_provider = "{_toml_safe(endpoint["name"])}"\n',
|
||||
f'model_catalog_json = "{mc_str}"\n',
|
||||
f'service_tier = "fast"\n',
|
||||
f'approvals_reviewer = "user"\n',
|
||||
]
|
||||
existing = CONFIG.read_text(encoding="utf-8") if CONFIG.exists() else ""
|
||||
merged = _merge_toml(existing, "".join(new_config))
|
||||
write_secure_text(CONFIG, merged)
|
||||
write_secure_text(profile_path, "".join(profile_lines))
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Model fetching
|
||||
@@ -1442,6 +1556,7 @@ def _pick_free_port():
|
||||
try:
|
||||
saved = int(_PROXY_PORT_FILE.read_text().strip())
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(("127.0.0.1", saved))
|
||||
return saved
|
||||
except (ValueError, OSError, FileNotFoundError):
|
||||
@@ -1533,11 +1648,19 @@ def _start_proxy_with_config(pcfg_path, port, logfn):
|
||||
)
|
||||
_register_pgid_entry("proxy", _proxy_proc.pid)
|
||||
|
||||
_proxy_log_path = PROXY_CONFIG_DIR / "proxy-stderr.log"
|
||||
_proxy_log_file = open(_proxy_log_path, "a", encoding="utf-8")
|
||||
|
||||
def _pipe_stderr():
|
||||
if not _proxy_proc.stderr:
|
||||
return
|
||||
for line in _proxy_proc.stderr:
|
||||
logfn(f"[proxy] {line.rstrip()}")
|
||||
try:
|
||||
_proxy_log_file.write(line)
|
||||
_proxy_log_file.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
threading.Thread(target=_pipe_stderr, daemon=True).start()
|
||||
|
||||
@@ -1655,6 +1778,10 @@ def check_codex_auth():
|
||||
return ("unknown", "No output from codex login status")
|
||||
except FileNotFoundError:
|
||||
return ("not_installed", "codex not found")
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
return ("not_configured", "Config not found — launch Codex once to create it")
|
||||
return ("error", str(e))
|
||||
except Exception as e:
|
||||
return ("error", str(e))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user