From 27b22f4fd8456d6d10156e55e6c552ee93bb84d5 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 20 May 2026 14:32:36 +0400 Subject: [PATCH] v2.3.0: adaptive Crof self-healing system - Per-model success/failure tracking with dynamic item limits - Proactive compaction when above learned limit - Auto-retry on finish_reason=length with aggressive re-compaction - Tested: kimi-k2.6 (27 items) and mimo-v2.5-pro both completed - All previous fixes included: _ts crash, connection reset, timeout, orphaned fco --- CHANGELOG.md | 15 +++ codex-launcher_2.2.1_all.deb | Bin 24534 -> 0 bytes codex-launcher_2.3.0_all.deb | Bin 0 -> 25790 bytes src/codex-launcher-gui | 9 +- src/translate-proxy.py | 229 ++++++++++++++++++++++++++++++++--- 5 files changed, 238 insertions(+), 15 deletions(-) delete mode 100644 codex-launcher_2.2.1_all.deb create mode 100644 codex-launcher_2.3.0_all.deb diff --git a/CHANGELOG.md b/CHANGELOG.md index eefa18d..17ffab3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## v2.3.0 (2026-05-20) + +- **Adaptive Crof self-healing system** + - Tracks per-model success/failure history with item counts + - Dynamically learns max item limit per model (starts at 30, adjusts down on failures) + - Proactively compacts input when above learned limit before sending to upstream + - Auto-retry on `finish_reason=length` with aggressive re-compaction and resend + - Prevents `stream disconnected` and `incomplete` errors on long conversations + - All tracking logged to stderr: `[crof-adaptive] model=X items=N OK/FAIL -> limit=N` +- Fixed `NameError: _ts` crash in debug logging +- Fixed `ConnectionResetError` crash on client disconnect during streaming +- Added 180s upstream timeout to prevent hanging connections +- Compaction now preserves function_call/function_call_output pairs (no orphaned tool outputs) +- Fixed reasoning control: `reasoning_effort=none` always sends both params + ## v2.2.1 (2026-05-20) - **Fixed compaction orphaning function_call_output items** — root cause of Crof `incomplete` responses diff --git a/codex-launcher_2.2.1_all.deb b/codex-launcher_2.2.1_all.deb deleted file mode 100644 index cd9970c9b92b3fce96053cc899c6e720e1d6c59a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24534 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 diff --git a/codex-launcher_2.3.0_all.deb b/codex-launcher_2.3.0_all.deb new file mode 100644 index 0000000000000000000000000000000000000000..f6334193e1697543bdd9f8bd9bcfb5b06748c341 GIT binary patch literal 25790 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 UDI>)Ua}(>^7GJOd%j#TP3h3cHA^-pY literal 0 HcmV?d00001 diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui index 1757f72..a11a5f1 100755 --- a/src/codex-launcher-gui +++ b/src/codex-launcher-gui @@ -24,6 +24,13 @@ model_catalog_json = "" """ CHANGELOG = [ + ("2.3.0", "2026-05-20", [ + "Adaptive Crof self-healing system — auto-adjusts to Crof model limits", + "Tracks per-model success/failure history, learns item count limits dynamically", + "Proactively compacts input when above learned limit before sending to Crof", + "Auto-retries on finish_reason=length — aggressively compacts and resends", + "Prevents 'stream disconnected' and 'incomplete' errors on long conversations", + ]), ("2.2.1", "2026-05-20", [ "Fixed compaction orphaning function_call_output items — root cause of Crof incomplete responses", "Compaction now respects function_call/function_call_output pairs — no more dangling tool results", @@ -548,7 +555,7 @@ class LauncherWin(Gtk.Window): # header row hdr = Gtk.Box(spacing=8) vbox.pack_start(hdr, False, False, 0) - lbl = Gtk.Label(label="Codex Launcher v2.2.1") + lbl = Gtk.Label(label="Codex Launcher v2.3.0") lbl.set_use_markup(True) hdr.pack_start(lbl, False, False, 0) changelog_btn = Gtk.Button(label="Changelog") diff --git a/src/translate-proxy.py b/src/translate-proxy.py index f3c5c44..f47aa9e 100755 --- a/src/translate-proxy.py +++ b/src/translate-proxy.py @@ -171,6 +171,87 @@ _MAX_INPUT_ITEMS = 30 _MAX_TOOL_OUTPUT_CHARS = 8000 _COMPACT_KEEP_RECENT = 10 +_CROF_ADAPTIVE = { + "fail_history": [], + "model_limits": {}, + "global_item_limit": 30, + "min_keep_recent": 4, +} + +def _crof_record(model, n_items, success): + if not isinstance(n_items, int) or n_items < 1: + return + entry = {"model": model, "items": n_items, "ok": success} + hist = _CROF_ADAPTIVE["fail_history"] + hist.append(entry) + if len(hist) > 200: + _CROF_ADAPTIVE["fail_history"] = hist[-100:] + + ml = _CROF_ADAPTIVE["model_limits"].setdefault(model, {"ok_max": 30, "fail_min": 0, "limit": 30}) + if success and n_items > ml["ok_max"]: + ml["ok_max"] = n_items + if not success and (ml["fail_min"] == 0 or n_items < ml["fail_min"]): + ml["fail_min"] = n_items + + if ml["fail_min"] > 0 and ml["ok_max"] >= ml["fail_min"]: + ml["limit"] = ml["fail_min"] - 1 + elif ml["fail_min"] > 0: + ml["limit"] = max(ml["fail_min"] - 2, _CROF_ADAPTIVE["min_keep_recent"] + 2) + + global_limit = 30 + for m, v in _CROF_ADAPTIVE["model_limits"].items(): + if v.get("limit", 30) < global_limit: + global_limit = v["limit"] + _CROF_ADAPTIVE["global_item_limit"] = global_limit + + print(f"[crof-adaptive] model={model} items={n_items} {'OK' if success else 'FAIL'} -> limit={ml.get('limit',30)} global={global_limit}", file=sys.stderr) + +def _crof_item_limit(model): + ml = _CROF_ADAPTIVE["model_limits"].get(model, {}) + per_model = ml.get("limit", 30) + return min(per_model, _CROF_ADAPTIVE["global_item_limit"]) + +def _crof_compact_for_retry(input_data, model): + limit = _crof_item_limit(model) + if not isinstance(input_data, list) or len(input_data) <= limit: + return input_data + + keep = max(_CROF_ADAPTIVE["min_keep_recent"], limit // 3) + head_end = 0 + for i, item in enumerate(input_data): + t = item.get("type") + if t == "message" and item.get("role") in ("developer", "system"): + head_end = i + 1 + elif t == "message" and item.get("role") == "user" and head_end == i: + head_end = i + 1 + else: + break + + head = input_data[:head_end] + tail_start = max(head_end, len(input_data) - keep) + while tail_start > head_end: + t = input_data[tail_start].get("type") + r = input_data[tail_start].get("role", "") + if t in ("function_call_output", "function_call"): + tail_start -= 1 + elif t == "message" and r == "assistant": + tail_start -= 1 + else: + break + tail = input_data[tail_start:] + body = input_data[head_end:tail_start] + + if not body: + return head + tail + + summary_lines = [f"[Auto-compacted: {len(body)} turns removed (adaptive limit={limit})]"] + for item in body[-5:]: + summary_lines.append(_item_summary(item, max_len=120)) + + summary_msg = {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "\n".join(summary_lines)}]} + print(f"[crof-adaptive] RETRY compact: {len(input_data)} -> {len(head)+1+len(tail)} (limit={limit}, keep={len(tail)})", file=sys.stderr) + return head + [summary_msg] + tail + def _item_summary(item, max_len=200): t = item.get("type") if t == "message": @@ -888,6 +969,15 @@ class Handler(http.server.BaseHTTPRequestHandler): def _handle_openai_compat(self, body, model, stream): input_data = body.get("input", "") + + # Adaptive: proactively compact if above learned Crof limit + crof_limit = _crof_item_limit(model) + if isinstance(input_data, list) and len(input_data) > crof_limit: + print(f"[crof-adaptive] proactive compact: {len(input_data)} items > limit {crof_limit}", file=sys.stderr) + input_data = _crof_compact_for_retry(input_data, model) + body = dict(body) + body["input"] = input_data + messages = oa_input_to_messages(input_data) instructions = body.get("instructions", "").strip() if instructions: @@ -914,25 +1004,136 @@ class Handler(http.server.BaseHTTPRequestHandler): "Content-Type": "application/json", "Authorization": f"Bearer {API_KEY}", }, browser_ua=True) - print(f"[translate-proxy] POST {target} model={model} stream={stream} ua={fwd.get('User-Agent','')[:50]}", file=sys.stderr) - _crof_debug_path = os.path.join(_LOG_DIR, "crof-upstream.jsonl") - with open(_crof_debug_path, "a") as _cdf: - _cdf.write(json.dumps({ - "model": model, "max_tokens": chat_body.get("max_tokens"), - "reasoning_effort": chat_body.get("reasoning_effort"), - "enable_thinking": chat_body.get("enable_thinking", "NOT_SENT"), - "n_messages": len(chat_body.get("messages", [])), - "has_tools": bool(chat_body.get("tools")), - }) + "\n") + print(f"[translate-proxy] POST {target} model={model} stream={stream} items={len(input_data) if isinstance(input_data,list) else 1} ua={fwd.get('User-Agent','')[:50]}", file=sys.stderr) + req = urllib.request.Request( target, data=json.dumps(chat_body).encode(), headers=fwd, ) - self._forward(req, stream, model, - lambda r: oa_resp_to_responses(json.loads(r.read()), model), - lambda s: oa_stream_to_sse(s, model, body.get("request_id") or body.get("id")), - input_data=body.get("input", "")) + self._forward_oa_compat(req, stream, model, chat_body, body, input_data, fwd, target, tools) + + def _forward_oa_compat(self, req, stream, model, chat_body, body, input_data, fwd, target, tools): + try: + upstream = urllib.request.urlopen(req, timeout=180) + except urllib.error.HTTPError as e: + err = e.read().decode() + return self.send_json(e.code, {"error": {"type": "upstream_error", "message": err}}) + except Exception as e: + return self.send_json(500, {"error": {"type": "proxy_error", "message": str(e)}}) + + n_items = len(input_data) if isinstance(input_data, list) else 1 + + if stream: + self.send_response(200) + self.send_header("Content-Type", "text/event-stream") + self.send_header("Cache-Control", "no-cache") + self.send_header("Connection", "keep-alive") + self.end_headers() + + collected_events = [] + last_resp_id = None + last_output = None + last_status = None + finish_reason = None + has_content = False + + try: + for event in oa_stream_to_sse(upstream, model, body.get("request_id") or body.get("id")): + self.wfile.write(event.encode("utf-8")) + self.wfile.flush() + collected_events.append(event) + for line in event.strip().split("\n"): + if line.startswith("data: "): + try: + d = json.loads(line[6:]) + if d.get("type") == "response.completed": + last_resp_id = d.get("response", {}).get("id") + last_output = d.get("response", {}).get("output", []) + last_status = d.get("response", {}).get("status") + fr_map = {"completed": "stop", "incomplete": "length"} + finish_reason = "length" if last_status == "incomplete" else "stop" + has_content = any(o.get("type") == "message" for o in (last_output or [])) + except: pass + except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError): + print("[translate-proxy] client disconnected during stream", file=sys.stderr) + _crof_record(model, n_items, False) + _log_resp(last_resp_id, "client_disconnect", last_output) + return + + # Record outcome + success = (finish_reason != "length") + _crof_record(model, n_items, success) + _log_resp(last_resp_id, last_status, last_output) + if last_resp_id and input_data is not None: + store_response(last_resp_id, input_data, last_output) + + # Auto-retry on finish_reason=length with no content + if finish_reason == "length" and not has_content and isinstance(input_data, list) and len(input_data) > 5: + print(f"[crof-adaptive] RETRY: finish_reason=length with no content, compacting {n_items} items", file=sys.stderr) + new_input = _crof_compact_for_retry(input_data, model) + if len(new_input) < len(input_data): + new_body = dict(body) + new_body["input"] = new_input + new_messages = oa_input_to_messages(new_input) + instructions = body.get("instructions", "").strip() + if instructions: + new_messages.insert(0, {"role": "system", "content": instructions}) + new_chat_body = dict(chat_body) + new_chat_body["messages"] = new_messages + new_req = urllib.request.Request( + target, + data=json.dumps(new_chat_body).encode(), + headers=fwd, + ) + self._forward_oa_compat_retry(new_req, model, new_chat_body, body, new_input) + else: + result = oa_resp_to_responses(json.loads(upstream.read()), model) + success = result.get("status") != "incomplete" + _crof_record(model, n_items, success) + self.send_json(200, result) + rid = result.get("id") + _log_resp(rid, result.get("status"), result.get("output", [])) + if rid and input_data is not None: + store_response(rid, input_data, result.get("output", [])) + + def _forward_oa_compat_retry(self, req, model, chat_body, body, input_data): + try: + upstream = urllib.request.urlopen(req, timeout=180) + except Exception as e: + print(f"[crof-adaptive] retry failed: {e}", file=sys.stderr) + return + + self.send_response(200) + self.send_header("Content-Type", "text/event-stream") + self.send_header("Cache-Control", "no-cache") + self.send_header("Connection", "keep-alive") + self.end_headers() + + last_resp_id = None + last_output = None + last_status = None + try: + for event in oa_stream_to_sse(upstream, model, body.get("request_id") or body.get("id")): + self.wfile.write(event.encode("utf-8")) + self.wfile.flush() + for line in event.strip().split("\n"): + if line.startswith("data: "): + try: + d = json.loads(line[6:]) + if d.get("type") == "response.completed": + last_resp_id = d.get("response", {}).get("id") + last_output = d.get("response", {}).get("output", []) + last_status = d.get("response", {}).get("status") + except: pass + except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError): + print("[translate-proxy] client disconnected during retry stream", file=sys.stderr) + + n_items = len(input_data) if isinstance(input_data, list) else 1 + _crof_record(model, n_items, last_status == "completed") + _log_resp(last_resp_id, last_status or "retry_disconnect", last_output) + if last_resp_id and input_data is not None: + store_response(last_resp_id, input_data, last_output) def _handle_anthropic(self, body, model, stream): input_data = body.get("input", "")