Replace the per-decode "launch browser + login + open catalog" model with a
persistent warm Vinpin seat that eliminates cold-start, brand-switch cost and the
seat livelock. Additive + fail-safe: every warm operation degrades to the proven
cold per-decode path, so behaviour never regresses.
Warm daemon (VinpinDaemonService, worker-process singleton):
- Scheduler warms the seat at 08:00 and tears it down at 21:00 Europe/Istanbul
(proper TZ via Intl, no hardcoded offset); warms on worker start if inside hours;
reconcile() every 60s with a reentrancy guard.
- Keepalive nudges the RDS session (mouse.move) every ~75s while warm+idle; it
SKIPS during any active seat op (busy flag) and never takes a lock that blocks a
decode.
- decode(vin): inside hours ensure warm (warm-on-demand once) then delegate to the
driver; off-hours delegate straight to the cold path. Never throws.
Driver warm path (VinpinDriverService):
- warmUp() launches+logs in ONCE and opens Fiat ePER + Renault Rpartstore + Renault
Dialogys windows without closing each other, then OCR-binds each taskbar button
(order read via OCR, not hardcoded; raise self-heals by probing slots + OCR
verify). isWarm()/teardownWarm()/keepalivePing() added.
- warmDecode(): taskbar-raise the brand window (Fiat→ePER, Renault→Rpartstore w/
Dialogys fallback), run the EXISTING in-catalog decode on the warm window, OCR
the modal, parse, then Escape to ready the field for the next VIN. Runs under the
wall-clock budget; a hang still aborts.
- Health-recovery: a dropped seat (Disconnected/no-free-sessions OCR marker) →
teardown + re-warm ONCE, then retry the decode once.
- Refactored runDialogys into openDialogysSubmenu + runDialogysSearch so the warm
path searches without a window-closing reopen; cold Dialogys flow unchanged.
Safety nets retained: VINPIN_DECODE_BUDGET_MS + sessionPoisoned breaker (warm
budget abort → teardown+poison+null), single-seat serialization (runExclusive),
decode() never throws. Gated by VINPIN_ENABLED; VINPIN_WARM_DAEMON=false forces the
legacy cold path (kill-switch). Widened VINPIN_MODAL_REGION to ~900px.
Wiring: processor calls getVinpinDaemon().decode(); worker starts the daemon on
boot and stops it (releasing the seat) on shutdown. BullMQ concurrency 1 +
attempts:1 unchanged.
Tests: business-hours warm/teardown scheduling (injected clock/TZ), brand→taskbar
routing, taskbar OCR-order binding, keepalive-skips-during-decode, and
session-drop→re-warm→retry recovery. All existing vinpin/queue tests stay green.
NOTE: un-dev-testable (prod holds the single seat) — pixel/taskbar coords are
OCR-verified + marked TUNE and need live prod validation; warm falls back to cold
until confirmed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>