6 Commits

10 changed files with 2778 additions and 5103 deletions

View File

@@ -1,5 +1,36 @@
# Changelog # Changelog
## v3.10.11 (2026-05-26)
**Hybrid Endpoint Fallback — Redundant Antigravity Endpoints**
### New Features
- Hybrid endpoint fallback: tries `cloudcode-pa.googleapis.com` then `daily-cloudcode-pa.googleapis.com` on 429
- `daily-cloudcode-pa.googleapis.com` is the same production endpoint agy-core uses (separate rate limit bucket)
- 429 errors now log full response body for debugging
- SERVICE_DISABLED (403) still falls through to next endpoint
- Rate-limit marking only happens after ALL endpoints fail
### Bug Fixes
- Fixed 429 on one endpoint immediately failing — now tries fallback before giving up
- Restored SERVICE_DISABLED fallthrough (was accidentally removed)
## v3.10.10 (2026-05-25)
**Context Normalizer Fix — Compaction Summary Preservation**
### Bug Fixes
- Fixed normalizer stripping ALL context on resumed sessions after compaction
- Normalizer no longer auto-resets when compaction summary is present
- Compaction summaries ("Auto-compacted: N earlier turns") are always preserved
- Deduplicates consecutive identical `<goal_context>` messages (10→1)
- Emergency reset now preserves compaction summaries
- Previous behavior: after compaction reduced 1925→185 items, normalizer saw `n_tool_outputs == 0` and stripped to just `system + latest_user`, losing all context — model responded with "I don't have context"
### hashlib Fix (v3.10.9 hotfix)
- `_antigravity_normalize_context` crashed with `NameError: hashlib` on resumed sessions
- Replaced SHA256 duplicate detection with string comparison
## v3.10.9 (2026-05-25) ## v3.10.9 (2026-05-25)
**Antigravity Overhaul — Context Normalizer, Claude Thinking Fix, Endpoint Lockdown** **Antigravity Overhaul — Context Normalizer, Claude Thinking Fix, Endpoint Lockdown**

View File

@@ -1856,7 +1856,7 @@ class LauncherWin(Gtk.Window):
# header row # header row
hdr = Gtk.Box(spacing=8) hdr = Gtk.Box(spacing=8)
vbox.pack_start(hdr, False, False, 0) vbox.pack_start(hdr, False, False, 0)
lbl = Gtk.Label(label="<b>Codex Launcher v3.10.7</b>") lbl = Gtk.Label(label="<b>Codex Launcher v3.10.9</b>")
lbl.set_use_markup(True) lbl.set_use_markup(True)
hdr.pack_start(lbl, False, False, 0) hdr.pack_start(lbl, False, False, 0)
changelog_btn = Gtk.Button(label="Changelog") changelog_btn = Gtk.Button(label="Changelog")

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -3,11 +3,11 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [ -f "$SCRIPT_DIR/codex-launcher_3.10.9_all.deb" ]; then if [ -f "$SCRIPT_DIR/codex-launcher_3.10.11_all.deb" ]; then
echo "Installing codex-launcher_3.10.9_all.deb ..." echo "Installing codex-launcher_3.10.11_all.deb ..."
sudo dpkg -i "$SCRIPT_DIR/codex-launcher_3.10.9_all.deb" sudo dpkg -i "$SCRIPT_DIR/codex-launcher_3.10.11_all.deb"
echo "" echo ""
echo "Installed v3.10.9 via .deb package." echo "Installed v3.10.11 via .deb package."
echo " translate-proxy.py -> /usr/bin/translate-proxy.py" echo " translate-proxy.py -> /usr/bin/translate-proxy.py"
echo " codex-launcher-gui -> /usr/bin/codex-launcher-gui" echo " codex-launcher-gui -> /usr/bin/codex-launcher-gui"
echo " cleanup-codex-stale -> /usr/bin/cleanup-codex-stale.sh" echo " cleanup-codex-stale -> /usr/bin/cleanup-codex-stale.sh"

File diff suppressed because it is too large Load Diff

View File

@@ -83,6 +83,21 @@ model_catalog_json = ""
""" """
CHANGELOG = [ CHANGELOG = [
("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", [ ("3.10.9", "2026-05-25", [
"Antigravity: production-only endpoints (cloudcode-pa.googleapis.com), sandbox blocked unless ALLOW_ANTIGRAVITY_STAGING=1", "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)", "Antigravity: 403 SERVICE_DISABLED falls through, 429 returns to client (no sandbox fallback)",

View File

@@ -4310,13 +4310,19 @@ def _antigravity_normalize_context(input_data):
if os.environ.get("ANTIGRAVITY_AUTO_RESET_POLLUTED_CONTEXT", "1") != "1": if os.environ.get("ANTIGRAVITY_AUTO_RESET_POLLUTED_CONTEXT", "1") != "1":
auto_reset = False auto_reset = False
if is_simple and (auto_reset or n_tool_outputs == 0): has_compaction_summary = any(
isinstance(it, dict) and it.get("type") == "message" and it.get("role") == "user"
and ("Auto-compacted" in str(it.get("content", "")) or "auto-compacted" in str(it.get("content", "")).lower())
for it in input_data
)
if is_simple and auto_reset and not has_compaction_summary:
system_items = [it for it in input_data if isinstance(it, dict) and it.get("type") == "message" and it.get("role") in ("developer", "system")] system_items = [it for it in input_data if isinstance(it, dict) and it.get("type") == "message" and it.get("role") in ("developer", "system")]
user_item = input_data[latest_user_idx] user_item = input_data[latest_user_idx]
result = system_items + [user_item] if system_items else [user_item] result = system_items + [user_item] if system_items else [user_item]
print(f"[antigravity-context] raw_items={n_raw} compacted_items={n_raw} final_items={len(result)}", file=sys.stderr) print(f"[antigravity-context] raw_items={n_raw} compacted_items={n_raw} final_items={len(result)}", file=sys.stderr)
print(f"[antigravity-context] raw_tool_outputs={n_tool_outputs} kept_tool_outputs=0", file=sys.stderr) print(f"[antigravity-context] raw_tool_outputs={n_tool_outputs} kept_tool_outputs=0", file=sys.stderr)
print(f"[antigravity-context] simple_latest_user=true auto_reset={auto_reset}", file=sys.stderr) print(f"[antigravity-context] simple_latest_user=true auto_reset={auto_reset} has_compaction={has_compaction_summary}", file=sys.stderr)
return result return result
dev_messages = [] dev_messages = []
@@ -4357,6 +4363,22 @@ def _antigravity_normalize_context(input_data):
tail_start = max(0, len(recent_items) - 6) tail_start = max(0, len(recent_items) - 6)
recent_tail = recent_items[tail_start:] recent_tail = recent_items[tail_start:]
deduped_tail = []
seen_goal_context = False
for idx, msg_item in recent_tail:
content_str = ""
c = msg_item.get("content", "")
if isinstance(c, str):
content_str = c
elif isinstance(c, list):
content_str = " ".join(p.get("text", p.get("input_text", "")) for p in c if isinstance(p, dict))
if "<goal_context>" in content_str:
if seen_goal_context:
continue
seen_goal_context = True
deduped_tail.append((idx, msg_item))
recent_tail = deduped_tail if deduped_tail else recent_tail
tool_call_ids = set() tool_call_ids = set()
for _, t_item in kept_tools: for _, t_item in kept_tools:
cid = t_item.get("call_id", t_item.get("id", "")) cid = t_item.get("call_id", t_item.get("id", ""))
@@ -4371,6 +4393,15 @@ def _antigravity_normalize_context(input_data):
result = list(dev_messages) result = list(dev_messages)
compaction_summaries = []
for idx, msg_item in recent_items:
if msg_item is input_data[latest_user_idx]:
continue
c = msg_item.get("content", "")
content_str = c if isinstance(c, str) else " ".join(p.get("text", p.get("input_text", "")) for p in c if isinstance(p, dict)) if isinstance(c, list) else ""
if "Auto-compacted" in content_str or "auto-compacted" in content_str.lower():
compaction_summaries.append(msg_item)
if n_summarized > 0: if n_summarized > 0:
summary_text = f"[Tool history summary: {n_summarized} older tool outputs omitted. {n_tool_calls} prior function calls were made for file inspection/editing.]" summary_text = f"[Tool history summary: {n_summarized} older tool outputs omitted. {n_tool_calls} prior function calls were made for file inspection/editing.]"
result.append({"type": "message", "role": "user", "content": [{"type": "input_text", "text": summary_text}]}) result.append({"type": "message", "role": "user", "content": [{"type": "input_text", "text": summary_text}]})
@@ -4381,23 +4412,26 @@ def _antigravity_normalize_context(input_data):
for _, tool_item in kept_tools: for _, tool_item in kept_tools:
result.append(tool_item) result.append(tool_item)
for cs_item in compaction_summaries:
result.append(cs_item)
for _, msg_item in recent_tail: for _, msg_item in recent_tail:
if msg_item is not input_data[latest_user_idx]: if msg_item is not input_data[latest_user_idx]:
result.append(msg_item) result.append(msg_item)
latest_hash = hashlib.sha256(" ".join(latest_user.strip().split()).encode()).hexdigest() latest_norm = " ".join(latest_user.strip().split())[:200].lower()
already_present = False already_present = False
for r in result: for r in result:
if isinstance(r, dict) and r.get("type") == "message" and r.get("role") == "user": if isinstance(r, dict) and r.get("type") == "message" and r.get("role") == "user":
c = r.get("content", "") c = r.get("content", "")
if isinstance(c, str): if isinstance(c, str):
rh = hashlib.sha256(" ".join(c.strip().split()).encode()).hexdigest() rn = " ".join(c.strip().split())[:200].lower()
elif isinstance(c, list): elif isinstance(c, list):
combined = " ".join(p.get("text", p.get("input_text", "")) for p in c if isinstance(p, dict)) combined = " ".join(p.get("text", p.get("input_text", "")) for p in c if isinstance(p, dict))
rh = hashlib.sha256(" ".join(combined.strip().split()).encode()).hexdigest() rn = " ".join(combined.strip().split())[:200].lower()
else: else:
rh = "" rn = ""
if rh == latest_hash: if rn == latest_norm:
already_present = True already_present = True
break break
@@ -4408,7 +4442,10 @@ def _antigravity_normalize_context(input_data):
if total_chars > _ANTIGRAVITY_EMERGENCY_CHARS: if total_chars > _ANTIGRAVITY_EMERGENCY_CHARS:
print(f"[antigravity-context] EMERGENCY: {total_chars} chars exceeds limit, resetting to minimal", file=sys.stderr) print(f"[antigravity-context] EMERGENCY: {total_chars} chars exceeds limit, resetting to minimal", file=sys.stderr)
result = list(dev_messages) + [input_data[latest_user_idx]] result = list(dev_messages)
if compaction_summaries:
result.extend(compaction_summaries)
result.append(input_data[latest_user_idx])
total_chars = sum(len(json.dumps(it, ensure_ascii=False)) for it in result) total_chars = sum(len(json.dumps(it, ensure_ascii=False)) for it in result)
while len(result) > _ANTIGRAVITY_MAX_CONTENTS and total_chars > _ANTIGRAVITY_SOFT_CHARS: while len(result) > _ANTIGRAVITY_MAX_CONTENTS and total_chars > _ANTIGRAVITY_SOFT_CHARS:
@@ -5034,7 +5071,10 @@ class Handler(http.server.BaseHTTPRequestHandler):
_allow_staging = os.environ.get("ALLOW_ANTIGRAVITY_STAGING", "0") == "1" _allow_staging = os.environ.get("ALLOW_ANTIGRAVITY_STAGING", "0") == "1"
if OAUTH_PROVIDER == "google-antigravity": if OAUTH_PROVIDER == "google-antigravity":
_antigravity_endpoints = ["https://cloudcode-pa.googleapis.com"] _antigravity_endpoints = [
"https://cloudcode-pa.googleapis.com",
"https://daily-cloudcode-pa.googleapis.com",
]
if _allow_staging: if _allow_staging:
_antigravity_endpoints.extend([ _antigravity_endpoints.extend([
"https://daily-cloudcode-pa.sandbox.googleapis.com", "https://daily-cloudcode-pa.sandbox.googleapis.com",
@@ -5091,14 +5131,16 @@ class Handler(http.server.BaseHTTPRequestHandler):
if e.code == 403 and "SERVICE_DISABLED" in err_body[:500] and ep != endpoints[-1]: if e.code == 403 and "SERVICE_DISABLED" in err_body[:500] and ep != endpoints[-1]:
print(f"[{self._session_id}] {ep} SERVICE_DISABLED, trying next endpoint", file=sys.stderr) print(f"[{self._session_id}] {ep} SERVICE_DISABLED, trying next endpoint", file=sys.stderr)
continue continue
if e.code == 429 and ep != endpoints[-1] and _allow_staging: if e.code == 429 and OAUTH_PROVIDER.startswith("google"):
print(f"[{self._session_id}] {ep} HTTP 429, trying next endpoint", file=sys.stderr) print(f"[{self._session_id}] 429 from {ep}, body: {err_body[:300]}", file=sys.stderr)
if ep != endpoints[-1]:
print(f"[{self._session_id}] {ep} HTTP 429, trying fallback endpoint", file=sys.stderr)
continue continue
if e.code == 429:
pool = _google_antigravity_pool if OAUTH_PROVIDER == "google-antigravity" else _google_cli_pool pool = _google_antigravity_pool if OAUTH_PROVIDER == "google-antigravity" else _google_cli_pool
_, acct = _get_google_account(OAUTH_PROVIDER) _, acct = _get_google_account(OAUTH_PROVIDER)
if acct: if acct:
pool.mark_rate_limited(acct, 60) pool.mark_rate_limited(acct, 60)
print(f"[{self._session_id}] all endpoints returned 429", file=sys.stderr)
return self.send_json(e.code, {"error": {"type": "upstream_error", "message": _sanitize_err_body(err_body)}}) return self.send_json(e.code, {"error": {"type": "upstream_error", "message": _sanitize_err_body(err_body)}})
except Exception as e: except Exception as e:
if ep == endpoints[-1]: if ep == endpoints[-1]:

View File

@@ -4385,19 +4385,19 @@ def _antigravity_normalize_context(input_data):
if msg_item is not input_data[latest_user_idx]: if msg_item is not input_data[latest_user_idx]:
result.append(msg_item) result.append(msg_item)
latest_hash = hashlib.sha256(" ".join(latest_user.strip().split()).encode()).hexdigest() latest_norm = " ".join(latest_user.strip().split())[:200].lower()
already_present = False already_present = False
for r in result: for r in result:
if isinstance(r, dict) and r.get("type") == "message" and r.get("role") == "user": if isinstance(r, dict) and r.get("type") == "message" and r.get("role") == "user":
c = r.get("content", "") c = r.get("content", "")
if isinstance(c, str): if isinstance(c, str):
rh = hashlib.sha256(" ".join(c.strip().split()).encode()).hexdigest() rn = " ".join(c.strip().split())[:200].lower()
elif isinstance(c, list): elif isinstance(c, list):
combined = " ".join(p.get("text", p.get("input_text", "")) for p in c if isinstance(p, dict)) combined = " ".join(p.get("text", p.get("input_text", "")) for p in c if isinstance(p, dict))
rh = hashlib.sha256(" ".join(combined.strip().split()).encode()).hexdigest() rn = " ".join(combined.strip().split())[:200].lower()
else: else:
rh = "" rn = ""
if rh == latest_hash: if rn == latest_norm:
already_present = True already_present = True
break break