github nesquena/hermes-webui v0.50.257

latest releases: v0.50.259, v0.50.258
4 hours ago

v0.50.257 — Codex OAuth, cron history, per-session toolsets, custom-provider routing fix

Three new features and a routing bug fix, plus five Opus pre-release advisor follow-ups including one CRITICAL finding that would have shipped a non-functional feature.

Added

  • Cron run history + full-output viewer (#468) — new GET /api/crons/history?job_id=X&offset=N&limit=M endpoint lists output files with metadata (filename + size + mtime) without loading content. New GET /api/crons/run?job_id=X&filename=Y returns full content + a snippet extracted from the ## Response section. Tasks panel renders a per-job run history with click-to-expand. (#1402, @bergeouss)

  • Per-session toolset overrides (#493) — new Session.enabled_toolsets: list[str] | None field threaded through _run_agent_streaming. New POST /api/session/toolsets endpoint validates input shape (non-empty list of non-empty strings, or null to clear). Settings panel adds a per-session toolset chip with global/custom modes. (#1402, @bergeouss)

  • Codex OAuth in-app device-code flow — new api/oauth.py (stdlib only — no external HTTP libs). Two endpoints: GET /api/oauth/codex/start (initiates Codex device-code flow, returns user_code + verification_uri) and GET /api/oauth/codex/poll?device_code=X (SSE for polling token endpoint). Successful poll writes credentials to ~/.hermes/auth.json under credential_pool.openai-codex. Onboarding wizard adds a "Sign in with ChatGPT" path. Idempotent: existing OAuth credential entries are updated in place; new ones use uuid.uuid4().hex[:8] with retry-on-collision. (#1402, @bergeouss)

Fixed

  • Named custom provider routing in model picker — @custom:NAME:model form preserved (follow-up to #1390) — when the model picker iterated custom_providers entries with a name field (e.g. [{name: "sub2api", base_url, models: [...]}]), the option IDs were stored as bare model strings. On chat start, the backend resolved those bare strings through the active/default provider, silently routing the request to the wrong endpoint (e.g. DeepSeek instead of the user's selected sub2api proxy). Now the picker prefixes IDs with @<slug>:<model> whenever the active provider differs from the named slug, so _resolve_compatible_session_model_state (added by #1390) routes through the correct named provider. The frontend _findModelInDropdown already strips @provider: prefixes during normalization, so legacy localStorage["hermes-webui-model"] values with bare IDs continue to resolve. (#1415, @Thanatos-Z)

Pre-release hardening (Opus advisor)

  • CRITICAL: per-session toolset override (#493) was non-functional_run_agent_streaming called _session_meta.get('enabled_toolsets') on the result of Session.load_metadata_only(), which returns a Session instance (not a dict). The AttributeError was swallowed by the surrounding except Exception: block, so the user's toolset chip silently no-op'd every time and the agent always ran with global toolsets. CI was green, all five of #1402's targeted tests passed — would have shipped non-functional. Fix uses getattr(_session_meta, 'enabled_toolsets', None). Source-level negative-pattern test prevents the dict-access shape from returning.

  • api/oauth.py::_write_auth_json chmod 0600 BEFORE renametmp.replace() preserves the temp file's umask-derived mode (commonly 0644 or 0664). auth.json contains OAuth access/refresh tokens; on shared systems those tokens landed world-readable through the temp-file→rename window. Fix sets tmp.chmod(0o600) before the atomic rename, with a try/except OSError that logs but doesn't abort if chmod fails on filesystems that don't support POSIX modes.

  • _handle_cron_history and _handle_cron_run_detail regex-validate job_id — the _checkpoint_root() / ws_hash / checkpoint path-traversal vector caught in v0.50.255 (#1405) had a sibling here: CRON_OUT / job_id / *.md. Path() / "../escape" does NOT normalize. New regex ^[A-Za-z0-9_-][A-Za-z0-9_.-]{0,63}$ with explicit ./.. rejection at the parameter boundary. Mirrors the rollback fix shape.

  • _handle_cron_history clamps offset and limit — raw int(qs.get("offset", ["0"])[0]) raised ValueError on ?offset=foo and surfaced as a generic 500. No upper bound on limit either. Now wrapped in try/except (ValueError, TypeError) returning a 400 on bad input, and limit clamped to [1, 500].

Tests

3604 passed, 2 skipped, 3 xpassed. Browser tests + Phase 2 API sanity all green. Opus advisor reviewed the combined stage diff and caught the CRITICAL toolset finding empirically (CI alone would have shipped the broken feature). Independent end-to-end review by @nesquena.

Contributors

@bergeouss · @Thanatos-Z · @nesquena (review)

Full Changelog: v0.50.256...v0.50.257

Don't miss a new hermes-webui release

NewReleases is sending notifications on new releases.