All notable changes to GengoWatcher are documented in this file. This project follows Keep a Changelog.
- A native Ratatui interface covering live watcher status, available jobs, workflow, history, analytics, service health, and API events.
- Authenticated loopback API polling with reconnect and backoff handling.
- Ratatui actions for immediate checks, pause/resume, accepting or ignoring a selected job, and cancelling active work. Destructive actions require confirmation.
- Keyboard and mouse workspace navigation, compact terminal layouts, and a Kanagawa Dragon-inspired colour scheme.
- Deterministic demo/render modes and SVG previews for interface development.
- Cargo build, test, run, and install commands in the Makefile.
- A loopback-only Gengo sandbox with captured job, RSS, WebSocket, acceptance, workbench, CAT-service, and versioned persistence primitives for deterministic testing.
- Opt-in real-browser coverage that exercises the production browser-worker acceptance flow against the local sandbox.
- Repository guidance in
CLAUDE.mdcovering development commands, runtime topology, state/event flow, browser boundaries, and persistence invariants.
- Ratatui is now the preferred terminal interface when a
gengowatcher-tuibinary is available. The Python runtime starts the authenticated loopback API and launches it automatically; Python-only installs fall back to Textual.gengowatcher --tui ratatuimay compile through Cargo when no binary exists. - The existing Textual interface remains available through
--tui textual. - API status responses now expose the watcher's paused state.
- Browser-worker command validation now supports an explicitly configured, exact-match loopback sandbox origin while preserving production Gengo URL restrictions.
- Browser-worker acceptance tracking captures and normalizes accepted workbench payloads for the watcher telemetry path.
- Python dependencies are locked in
uv.lockfor reproducible environments. make coveragesetsPYTHONPATH=src.make lintandmake formatincludeprototypes/.
- Web-only mode now starts the shared watcher thread. Combined TUI/API execution still avoids a second monitor loop.
- API startup no longer crashes with
UnboundLocalErrorwhen generating a missing[WebServer] auth_tokenfor a shared-watcher runtime. - The repo
bin/gengowatcherlauncher setsPYTHONPATHtosrcwhen using.venv, so the package is importable without an editable install. - RSS jobs that leave the live feed are marked
goneinstead of remaining available forever. The Ratatui client no longer treatsgone,unavailable, ormissedjobs as open listings. - Manual accept through
/api/jobs/{id}/acceptnow inspectsAcceptResult.successinstead of treating the result object as success. Gone or otherwise unavailable jobs are rejected with a 409 and a reason the TUI can display. - The watcher now forwards
[BrowserWorker].sandbox_originintoBrowserWorkerClient, so sandbox job URLs are canonicalized by the coordinator instead of rejected as unsupported hosts. - Ratatui accept/cancel requests use a 20s timeout so they can outlive the default 12s acceptance attempt. Snapshot GETs stay on the 4s timeout.
- A dropped API connection clears a pending accept/cancel lock so
a/xcan be retried after reconnect. - Dismissing the overview alert no longer suppresses banners for later newly available jobs.
- Automatic Ratatui selection no longer treats a source
Cargo.tomlpluscargoon PATH as “available”; that path is reserved for--tui ratatui. - Sandbox
GET /t/workbench/{id}no longer serves__GENGO_WORKBENCH_DATA__for still-available collections.
- The Python launcher passes the API token through
GENGOWATCHER_API_TOKEN, not through process arguments. - The native client accepts plain HTTP API connections only on loopback addresses.
- Sandbox servers reject non-loopback binding unless explicitly acknowledged; browser connections enforce a matching loopback Origin.
BrowserRuntimeConfig.sandbox_originis validated and canonicalized at construction: only HTTP(S) loopback origins without credentials, path, query, or fragment are accepted.- Sandbox
AtomicJSONStoreserializes load and write with an instance lock and suppressesOSErrorduring temp-file cleanup so the original write failure is re-raised.
- Added Python coverage for backend selection, Ratatui process lifecycle, loopback API startup, token forwarding, failures, and paused status.
- Added Rust coverage for API parsing, live-state updates, input handling, rendering, confirmation flows, and terminal-size variants.
- Added sandbox route, lifecycle, CAT-service, persistence, origin-validation, browser-worker protocol, and opt-in Chromium acceptance coverage.
- Added RSS feed-reconcile coverage, accept-result and gone-job API tests, sandbox-origin construction tests, and notify-send error-path tests.
- BrowserJobs monitor is now event-driven (
src/gengowatcher/watcher.py). The old 1.5s fixed polling loop and the randomGENGO_BROWSER_BROWSE_URLSnavigation have been removed. The monitor now wakes from_browser_jobs_refresh_eventon:job.visible/job.details/job.discoveredAPI events (emitted by the native browser listener and the WS / RSS / webhook paths);- explicit
trigger_browser_jobs_refresh()calls from TUI commands or the WS monitor's sync-fallback path; - a long idle-cap keepalive (default 30 min, configurable via
[BrowserJobs].idle_cap_sec) that runs a passive eval only — no navigation, no interaction.
config.toml.exampleadds a[BrowserJobs]block withallow_navigation = falseandidle_cap_sec = 1800. The random browse feature is still reachable for operators who explicitly setallow_navigation = true, but they no longer get unsolicited navigation of the live Firefox tab.- New public API:
GengoWatcher.trigger_browser_jobs_refresh(reason=...)for external producers that want to request a refresh.
tests/test_browser_jobs_event_driven.py— 4 new regression tests: no scrape without trigger inside idle_cap window, scrape after explicit trigger, keepalive scrape is passive (no browse_url, no force_refresh), triggered refresh respectsallow_navigation=false.
_event_loopteardown race inWebAPIwebsocket handler: refcount loop pointer so concurrent WS connections don't clear each other's broadcast loop on disconnect (src/gengowatcher/web.py).- Webhook executor was a module-level ThreadPoolExecutor that survived until
interpreter shutdown; now registers
atexit.shutdown(wait=False)so the daemon pool is released cleanly (src/gengowatcher/webhooks.py). state.py_track_job_unlockedusedsetdefaultfor the new lookup index, leaving stale pointers when a brand-new job shared an identifier tuple (e.g. matchingorder_id) with a prior job. Now last-write-wins (src/gengowatcher/state.py)._calculate_accepted_seconds_leftdropped expire values <= 1e9 (Unix-epoch seconds) on the floor. Restored the> 10_000_000_000ms-vs-sec cutoff only; everything positive is now treated as a Unix epoch second (src/gengowatcher/state.py).
- Anti-detection hardening for the Gengo WebSocket handshake:
build_browser_aligned_websocket_headersno longer emitsPragma,Cache-Control: no-cache, orAccept-Encoding(none of which appear on a real Chrome WS upgrade); distinct cookie values formyG_myGSession_andmyG_rdsessID(src/gengowatcher/browser_session.py).- Adds
Sec-CH-UA,Sec-CH-UA-Mobile,Sec-CH-UA-Platform,Sec-Fetch-Mode,Sec-Fetch-SiteClient Hints when the configured UA is modern Chrome (src/gengowatcher/browser_session.py). - Standalone gateway (
websocket_server.py) now sends auth as{userId, sessionToken, userKey?}— the same camelCase shape the in-process monitor and Gengo's web frontend use.
- Playwright Chromium launches with
--disable-blink-features=AutomationControlledand an init script that overwritesnavigator.webdriveron every navigation, removing__webdriver_*internals, and stubbingwindow.chrome.runtime(src/gengowatcher/browser_worker/runtime.py). - WebSocket monitor heartbeat is jittered ±5s around the configured interval
(
src/gengowatcher/websocket_monitor.py). - Native Firefox RDP listener poll interval is jittered ±150ms
(
src/gengowatcher/native_browser_listener.py). - WebSocket monitor fails closed with a clear log when
user_keyis missing or set toREPLACE_WITH_YOUR_USER_KEY(src/gengowatcher/websocket_monitor.py).
/api/jobs/discoverednow requires bearer auth; the webhook alias/api/webhooks/jobs/discoveredis forced torequire_signature=True(src/gengowatcher/web.py)._read_limited_request_bodyis bounded by anasyncio.timeout(10.0)and a chunk-count cap to prevent slow-loris on the unauthenticated webhook path (src/gengowatcher/web.py).
- Cookie snapshot includes
rd_session_idseparately; BiDi and CDP fetchers extract bothmyG_myGSession_andmyG_rdsessIDfrom the browser cookie store (src/gengowatcher/browser_session.py). - Workbench JS scan now measures serialized size and returns
{payload: null, oversized: true}above 768 KB to avoid silent truncation through the RDP/BiDi eval channel (src/gengowatcher/native_browser_listener.py). - Rebase repair:
_run_browser_worker_event_listenerindentation restored to compile cleanly (src/gengowatcher/watcher.py).
- API-driven browser job telemetry: webhook-backed API event plumbing,
audit logging, TUI API controls, native Firefox RDP listener, event bus,
state projector, workbench payload normalization
(PR #116,
codex/api-browser-job-telemetry). - Countdown tracking and notifications for elapsed, halfway, and low-time thresholds.
- New TUI tabs for browser jobs, audit log, and telemetry.
- Prometheus scrape config + alert rules for system / service monitoring
(
ops/prometheus/).
- User-facing webhook terminology replaced with API/event terminology in the TUI, health snapshot, and public routes.
- Public API event routes added at
/api/jobs/discoveredand/api/events/audit; old webhook routes retained as hidden aliases. - Dashboard status row fits at 80 columns and shows
APIplus browser status first.
See git history for changes prior to the API/event rewrite.