From ded9b7e1c43edb57db88a52bfa8755586841fa4b Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Wed, 29 Apr 2026 21:06:30 -0700 Subject: [PATCH] release: v0.50.243 (#1302) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit release: v0.50.243 Batch release of 2 PRs. - #1301 — fix: remove PRIMARY chip badge + add Claude Opus 4.7 label Drops the chip-projected configured-model badge added in #1287 (chip width 235px → 164px). Adds Claude Opus 4.7 label entries so the picker no longer renders "Claude Opus 4 7" (missing dot). Independently reviewed and approved by nesquena (commit c0bbd23). - #1297 (@franksong2702) — fix: preserve cron output response snippets Fixes #1295. /api/crons/output now preserves the ## Response section when a large skill dump appears in the prompt section; falls back to file tail when no marker exists. Tests: 3254 passed, 2 skipped, 3 xpassed. Independently reviewed and approved by nesquena (commit b262e4d). --- CHANGELOG.md | 7 ++++++ api/config.py | 3 +++ api/routes.py | 38 ++++++++++++++++++++++++++++++- static/index.html | 1 - static/style.css | 4 ---- static/ui.js | 12 ---------- tests/test_model_picker_badges.py | 23 +++++++++---------- tests/test_sprint10.py | 34 +++++++++++++++++++++++++++ 8 files changed, 92 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4507f822..93373894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ ### Fixed +## [v0.50.243] — 2026-04-30 + +### Fixed +- **Chip composer model badge — removed the `PRIMARY` projection** — The chip-projected configured-model badge added in #1287 was eating ≈30% of chip width (235px → 164px) without adding signal, since the model name is already right next to it. The dropdown rows still show `Primary` / `Fallback N` badges where they actually help distinguish picker entries. Backend `_build_configured_model_badges()` and the `configured_model_badges` payload on `/api/models` are preserved for the dropdown to consume. (`static/index.html`, `static/ui.js`, `static/style.css`, `tests/test_model_picker_badges.py`) — PR #1301 +- **Claude Opus 4.7 label rendering** — Adds explicit label entries for `anthropic/claude-opus-4.7`, `claude-opus-4.7`, and `claude-opus-4-7` so the picker no longer renders "Claude Opus 4 **7**" (missing dot) when the dashed-form model ID falls through to the generic dash-replace formatter. (`api/config.py`) — PR #1301 +- **Cron output snippet preserves the `## Response` section** — `/api/crons/output` returned `txt[:8000]` which could drop the useful response section when a large skill dump appeared in the prompt context. Now: if `## Response` exists, preserves a short header plus the response section; if no marker exists, returns the file tail rather than the head. (`api/routes.py`, `tests/test_sprint10.py`) @franksong2702 — PR #1297, fixes #1295 + ## [v0.50.242] — 2026-04-30 ### Reverted diff --git a/api/config.py b/api/config.py index 21dde7d6..406261e2 100644 --- a/api/config.py +++ b/api/config.py @@ -503,6 +503,7 @@ _FALLBACK_MODELS = [ {"provider": "OpenAI", "id": "openai/gpt-5.4-mini", "label": "GPT-5.4 Mini"}, {"provider": "OpenAI", "id": "openai/gpt-5.4", "label": "GPT-5.4"}, # Anthropic — 4.6 flagship + 4.5 generation + {"provider": "Anthropic", "id": "anthropic/claude-opus-4.7", "label": "Claude Opus 4.7"}, {"provider": "Anthropic", "id": "anthropic/claude-opus-4.6", "label": "Claude Opus 4.6"}, {"provider": "Anthropic", "id": "anthropic/claude-sonnet-4.6", "label": "Claude Sonnet 4.6"}, {"provider": "Anthropic", "id": "anthropic/claude-sonnet-4-5", "label": "Claude Sonnet 4.5"}, @@ -641,6 +642,7 @@ def _resolve_provider_alias(name: str) -> str: # Well-known models per provider (used to populate dropdown for direct API providers) _PROVIDER_MODELS = { "anthropic": [ + {"id": "claude-opus-4.7", "label": "Claude Opus 4.7"}, {"id": "claude-opus-4.6", "label": "Claude Opus 4.6"}, {"id": "claude-sonnet-4.6", "label": "Claude Sonnet 4.6"}, {"id": "claude-sonnet-4-5", "label": "Claude Sonnet 4.5"}, @@ -738,6 +740,7 @@ _PROVIDER_MODELS = { {"id": "gpt-5", "label": "GPT-5"}, {"id": "gpt-5-codex", "label": "GPT-5 Codex"}, {"id": "gpt-5-nano", "label": "GPT-5 Nano"}, + {"id": "claude-opus-4-7", "label": "Claude Opus 4.7"}, {"id": "claude-opus-4-6", "label": "Claude Opus 4.6"}, {"id": "claude-opus-4-5", "label": "Claude Opus 4.5"}, {"id": "claude-opus-4-1", "label": "Claude Opus 4.1"}, diff --git a/api/routes.py b/api/routes.py index 7f4ad069..8524c3e2 100644 --- a/api/routes.py +++ b/api/routes.py @@ -35,6 +35,8 @@ _CLIENT_DISCONNECT_ERRORS = ( # Track job IDs currently being executed so the frontend can poll status. _RUNNING_CRON_JOBS: dict[str, float] = {} # job_id → start_timestamp _RUNNING_CRON_LOCK = threading.Lock() +_CRON_OUTPUT_CONTENT_LIMIT = 8000 +_CRON_OUTPUT_HEADER_CONTEXT = 200 def _mark_cron_running(job_id: str): @@ -56,6 +58,40 @@ def _is_cron_running(job_id: str) -> tuple[bool, float]: return True, time.time() - t +def _cron_response_marker_index(text: str) -> int: + """Return the start index of a markdown Response heading, if present.""" + candidates = [] + for heading in ("## Response", "# Response"): + if text.startswith(heading): + candidates.append(0) + idx = text.find(f"\n{heading}") + if idx >= 0: + candidates.append(idx + 1) + return min(candidates) if candidates else -1 + + +def _cron_output_content_window(text: str, limit: int = _CRON_OUTPUT_CONTENT_LIMIT) -> str: + """Return a bounded cron output window that preserves useful response text. + + Cron output files can contain large skill dumps in the Prompt section. The + UI already extracts ``## Response`` when present, so keep that section in + the API payload instead of blindly returning the first ``limit`` chars. + """ + if limit <= 0: + return "" + if len(text) <= limit: + return text + + response_idx = _cron_response_marker_index(text) + if response_idx >= 0: + header = text[:min(_CRON_OUTPUT_HEADER_CONTEXT, response_idx)].rstrip() + response = text[response_idx:].lstrip("\n") + content = f"{header}\n...\n{response}" if header else response + return content[:limit] + + return text[-limit:] + + def _run_cron_tracked(job): """Wrapper that tracks running state around cron.scheduler.run_job.""" try: @@ -2932,7 +2968,7 @@ def _handle_cron_output(handler, parsed): for f in files: try: txt = f.read_text(encoding="utf-8", errors="replace") - outputs.append({"filename": f.name, "content": txt[:8000]}) + outputs.append({"filename": f.name, "content": _cron_output_content_window(txt)}) except Exception: logger.debug("Failed to read cron output file %s", f) return j(handler, {"job_id": job_id, "outputs": outputs}) diff --git a/static/index.html b/static/index.html index 34dd1515..50d5a59e 100644 --- a/static/index.html +++ b/static/index.html @@ -401,7 +401,6 @@