chore(deps): bump cryptography from 49.0.0 to 50.0.0 in the pip group across 1 directory - #17
Merged
Merged
Conversation
Public history was squashed to a single commit today, so the entries grandfathering docs/INSTALL_SAAS.md and store-listings/ in the old history no longer apply to anything — there is no pre-squash history left in the public repo to grandfather. Left as empty containers (not deleted) so a future genuinely-accepted history leak has somewhere to go without re-adding the machinery.
Reworded the title and body to read as a heads-up rather than a legal
consent notice ("Toplantı modu — onay" -> "Toplantı modu hakkında" /
"Meeting mode — consent" -> "About Meeting mode"), matching the tone
of the rest of the onboarding copy.
The loopback listener used for the browser-relay Google/email sign-in reflected any request's Origin header back verbatim in Access-Control-Allow-Origin, letting any site open in the user's browser make cross-origin requests to it during the listener's lifetime. Only the exact voxislive.com origin is allowed now.
… majors Clears the Node.js 20 deprecation warning GitHub Actions annotates on every run — the pinned v4/v5 releases predate each action's Node 24 runtime upgrade.
The repo moved from the personal DavutAkca account to the VoxisLive org; update hardcoded clone URLs, badges, and issue/advisory links across the READMEs and .github/ templates accordingly.
Weekly automated version-bump PRs for Python dependencies and the Actions used in CI, instead of relying on manual bumps.
Bumps [onnxruntime](https://github.com/microsoft/onnxruntime) from 1.27.0 to 1.28.0. - [Release notes](https://github.com/microsoft/onnxruntime/releases) - [Changelog](https://github.com/microsoft/onnxruntime/blob/main/docs/ReleaseManagement.md) - [Commits](microsoft/onnxruntime@v1.27.0...v1.28.0) --- updated-dependencies: - dependency-name: onnxruntime dependency-version: 1.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [google-genai](https://github.com/googleapis/python-genai) from 2.8.0 to 2.14.0. - [Release notes](https://github.com/googleapis/python-genai/releases) - [Changelog](https://github.com/googleapis/python-genai/blob/main/CHANGELOG.md) - [Commits](googleapis/python-genai@v2.8.0...v2.14.0) --- updated-dependencies: - dependency-name: google-genai dependency-version: 2.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [scipy](https://github.com/scipy/scipy) from 1.17.1 to 1.18.0. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](scipy/scipy@v1.17.1...v1.18.0) --- updated-dependencies: - dependency-name: scipy dependency-version: 1.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Applied by hand — the Dependabot PR (#6) hit a merge conflict against requirements.txt after the other three same-day dependency PRs landed first.
requirements.txt already had google-genai 2.14.0, numpy 2.5.1, onnxruntime 1.28.0, and scipy 1.18.0 from the recent Dependabot merges, but CI installs from requirements.lock (--require-hashes), which was still pinned to the old versions — so the "passing" CI on those PRs never actually exercised the new dependency code paths. Regenerated with pip-compile and verified locally: full pytest suite (401 tests) and ruff (app tests scripts) both pass against the real new versions.
numpy 2.5.1 and scipy 1.18.0 both require Python >=3.12, so pip could not even install them under the 3.11 test job (confirmed on CI: "ERROR: Could not find a version that satisfies the requirement numpy==2.5.1"). Held both back to their prior versions and left a comment explaining why, so a future Dependabot PR for either doesn't get merged blind again. google-genai and onnxruntime bumps are unaffected and stay.
… expires /auth/quota only reads auth-core's token cache (5-minute TTL, not extended on read) — only /auth/verify repopulates it — so the endpoint 401s once per TTL by construction. get_quota() mapped that 401 to None, and the UI keeps its previous QUOTA whenever a poll yields nothing, so the counter silently froze at a stale number instead of counting down. A user watching a 150-minute film read "60 min left" while the balance actually reached zero and the session was cut mid-film, with the one signal that would have warned him never moving. get_quota now does what report_usage has always done with its own identical 401: re-verify once. /auth/verify answers with the same snapshot shape, so its body is the refreshed quota. When it declines to answer — 402, a paid tier that is genuinely exhausted — resolveEntry has still re-cached the entry on the way to that 402, so one retry of the read yields the exhausted snapshot, which is exactly the number the counter must show. Only a FAILED re-verify is throttled (30s): a successful one warms the cache for a full TTL and cannot recur per-beat, whereas a failing one (PocketBase down) would otherwise turn a 6-second poll into three requests every six seconds against an already-sick server.
base_translator.py did not import logging at all. Every rotation, reconnect, backoff and watchdog trip existed only as an on-screen status line pushed through on_status — nothing reached voxis.log and nothing reached the saved transcript. A field report of "it reconnected once mid-meeting and the audio cut" therefore had no evidence anywhere: a 63-minute session runs roughly four 13-minute rotations and not one of them left a trace. Log once per occurrence, off the audio path, at the points that decide what the session does next: a successful connect (with target + model), a planned rotation, each transient failure with its counter and backoff, the terminal classification that ends the reconnect loop, both watchdog trips (stall and no-output) with the measured idle time, and _give_up — including whether a substitute engine took over, which is the difference between a degraded session and a dead one.
Every response.*.done cleared _out_acc. The next response's first delta then extended an EMPTY accumulator, so _delta returned its text bare and webui's `_cur_line += text` glued it straight onto the previous sentence. A measured session rendered "Bir dakika bekleyelim.Bu arada,Eğer giderseniz" — 211 occurrences across 131 of 525 turns (25%), and they land in the exports too, where one of them produced a 46-character unbreakable token in an SRT cue. _delta already handled the other reset path correctly: the "unrelated text" branch prepends a space. What was missing is that an EXPLICIT reset is the same kind of boundary. _boundary_pending records it, and the next increment that actually carries text leads with a separator (never doubling one Qwen already sent). A no-growth event leaves the boundary pending rather than spending it. A rotation counts as a boundary too: _reset_session_state clears the accumulators but not the caption line downstream, so the new session's first increment continues an open sentence. Harmless on the very first session — a leading space is stripped when the turn finalizes. Tests drive the real event shape (delta…delta, done, delta…) and assert the exact string the field transcript got wrong; also pins the no-growth, double-space and rotation cases, plus the lifecycle logging added previously.
render_srt/render_vtt wrote the cue body exactly as stored. A simultaneous engine that never pauses long enough to split a turn produces very long ones, so 482 of 525 cues in a measured session exceeded the conventional 42-character line and the longest was a single 548-character line no player can display. _wrap folds on word boundaries and never truncates — an over-long word is left intact rather than broken mid-token. Re-rendering that session's record gives 834 over-long lines -> 1 (a merged token the previous commit removes at source), max 548 -> 46 characters, and zero words lost. Only the cue path wraps; the TXT export keeps its long lines, where they are not a problem. build_record also gains an optional `events` list — the engine lifecycle now logged by base_translator, so a saved transcript can answer "did it drop at 16:31?" instead of the answer scrolling past in the UI. Schema-additive: the key is omitted entirely when nothing happened, so an uneventful session serializes byte-identically to every record written before this existed. Turns gain an optional `leg` (which side of a meeting spoke) and exports prefix a two-way record with the localized side name on every turn — the sides alternate constantly, so an omitted tag would read as "same side as before" exactly when it is not. Video/Game records carry no leg and render unchanged.
ModeController._build handed BOTH meeting pipelines the same on_text, and the Bridge kept one set of transcript buffers. The lock around _on_text kept those buffers consistent, but it could not keep the two CONVERSATIONS apart: the other party's translation and the user's own interleaved inside a single caption line and landed in the record indistinguishable from each other. Never seen in Video/Game, which only ever runs one direction. _text_sink binds a side to each pipeline and _LegState gives each its own caption line, source queue, turn start and re-speak guard. The turn list stays shared — the record is one chronological timeline — and is re-sorted by offset at flush so a side that was mid-utterance at stop still lands in order. Turns carry `leg`, rendered as a "Them:"/"Me:" prefix; the UI opens a new bubble on every side change. Speaker labels stay incoming-only (the tracker hears only the capture), and the overlay and OBS file — single-line surfaces — keep showing the other party rather than the user's own words. Billing is deliberately untouched. The server bills meeting 1:1 on purpose (MeetingBillingMultiplier) because two sessions cost ~1.0-1.2x, not 2x: output audio dominates and total speech is conserved across turn-taking. That multiplier keys off the exact `source` string, so surfacing the second leg there would move money; session_live carries `two_way` instead. Also in the caption path, from the same session audit: * The transcript timeline now starts at the session, not at the first translated token. A session that began before anyone spoke shifted every exported cue against its own WAV — 7m46s in the measured case. Sharing the anchor with the session-folder stamp also makes the JSON's `started` match the folder it sits in. * LINE_GAP alone never fires on a continuously-streaming engine, so one turn absorbed 20+ seconds of speech. Past a length or time budget the stream splits at the next sentence end, with a hard ceiling for a run-on that never punctuates. * The re-speak guard matched exact text, so a regenerated echo with a leading connective landed as a second turn that was never spoken aloud. It now matches on a normalized form, in the streaming path and the stop-time flush alike. Short repeats still survive — "Evet." twice is dialogue. Video/Game output is unchanged: no leg key, no prefix, byte-identical records.
`l` reads as `1` in most fonts, which is why ruff rejects it (E741). Caught by CI's static-analysis step, not by the local test run.
Smart mode already stops streaming after roughly 1.5 seconds of silence, so a quiet room sends nothing upstream. Accrual, though, runs on wall clock: a session joined before the meeting starts bills the wait. In a measured 63-minute session that was 7m46s of digital silence — about 12% of the session — during which nothing was sent and nothing was translated. The gate now records when it last decided a frame was speech, and the heartbeat skips accrual once every capture source has been quiet for a minute. Meeting needs BOTH sides quiet, so the minimum idle wins. The threshold sits far above any conversational pause; only an empty room, a call on hold or a paused video reaches it. Resumption is immediate on the next speech frame and nothing is deferred — _consume_minutes already advances the watermark when accrue is false, so the silent stretch is simply never billed. The stop() tail applies the same rule, or a session ended after sitting quiet would bill its last stretch anyway. Fails OPEN: a pipeline whose idle time cannot be read is never treated as idle, so an unreadable source keeps billing rather than silently giving the session away. One status line per transition, so a paused counter reads as intentional rather than broken.
Qwen accepts a term list (corpus.phrases) and Voxis has always passed one: parse_hotwords feeds every standard-routed Qwen session, beta opt-in or not. Only the UI was missing — cfg["beta"]["hotwords"] could be edited nowhere but config.json, so in practice the feature did not exist. The field audit measured what that costs. Across one meeting the speaker said "Antler" 33 times and Voxis spelled it correctly 4 times: the rest came out as Anter, Enter, Anthos, Atler, and once "enterözis". "MENAP" was said 14 times and never once written correctly. For a user whose meetings are full of product and company names, that is the difference between a usable transcript and one they have to fix by hand. Settings > General now takes one term per line, with term=replacement when it should come out differently. The count on screen is produced by mirroring the ENGINE's own parse rule (blank and #comment lines skipped) so it cannot drift from what is actually sent, and it warns past the 50-pair server limit instead of letting the engine truncate silently. set_hotwords writes through a copy of the beta dict: clone, source_lang and vad_ms live beside it and are still config-file-only, so a whole-dict overwrite would quietly reset them. Saved on blur rather than per keystroke, because the term list rides the session handshake and therefore restarts a running session.
A turn's `src` is not data, it is a PAIRING: the source text that had arrived by roughly SRC_LAG_S ago, attached to the turn finalizing now. That estimate holds for Gemini, which transcribes and translates on one path. Qwen runs its source ASR as a separate, slower service, so the translation lands first and the matching source arrives seconds later — an audit of one real session measured the resulting column shift at a median -8s across 84% of turns. Only the RESULT of the pairing has ever reached disk. The arrival times that produced it were discarded, so the error could not be measured after the fact and no corrected rule could be checked against a session that had already happened. The record now carries the raw material alongside: what source text arrived, and when, independent of which turn it was attached to. Consecutive increments are merged inside a one-second window, since the useful resolution is "when did this stretch arrive", not one row per token, and the list is bounded so a long session cannot grow the file without limit. Schema-additive and omitted when empty, so every existing record and every session that captures no source serializes exactly as before. This deliberately does not change the pairing itself. The obvious repair — shift each cut back by one turn — fits the audited session because its turns happened to run about as long as the lag; on shorter turns it under-corrects and on longer ones it introduces a new error, and with nothing but the paired result on disk there was no way to tell those cases apart. One real Qwen session recorded with this track is enough to derive the rule and prove it, which is a far better trade than shipping a guess into the one artifact users keep.
Owner reported the translated voice skipping. Transcribing the translated WAV of a measured session confirmed it: 968 spoken words against 1067 captioned. The asymmetry is the tell — 97.6% of the audio appears in the captions, but only 88.6% of the captions appear in the audio, so text was captioned and never spoken, in small fragments rather than whole sentences. Nothing in the record could localize it. The caption stream was persisted; the audio stream was not. The playback counters were clean throughout — backlog 0-2s, trimmed_total 0.0, dup_audio 0 — which rules out the pacing layer as the place the words went, and leaves the engine's own output as the remaining suspect, with no evidence either way. This is the output-side counterpart of the source track: cumulative seconds of translated speech the engine produced, sampled while captions flow. A stretch where the turn timeline advances and this does not is text that was never spoken, and now it can be pointed at rather than described. Counted at the top of the sink, before the preview-mute and cascade branches, because the question is what the ENGINE produced — not what reached the speakers. Summed across pipelines so a meeting accounts for both legs. Sampling rides the caption path rather than a timer: no new thread, and the samples land exactly where they matter. A controller without the counter simply yields no samples; instrumentation must never be able to break a session. Schema-additive and omitted when empty, so existing records are unchanged.
The cross-turn guard compares whole turns, so it never saw this shape:
A. <connective>, A. <tail>
where the engine emits a full clause twice inside ONE caption line. Measured
across three recorded sessions: 12 turns, 174 words, 2.2% of all caption text —
4.2% and 9.0% in two podcast sessions, 1.1% in a conference talk. Identical in
both runs of the same video, so it is engine behaviour rather than a race.
This is not lost audio. The translated speech says the clause once and is
correct; only the caption carries it twice. That distinction matters beyond the
transcript: the duplicate is a large part of what made caption text look "never
spoken" and sent the audio-gap investigation after a dropout that, for those
stretches, was never there.
The repair deletes text, so it is deliberately hard to trigger: at least five
words, non-overlapping copies, a 0.9 match, and the trailing words must match
exactly. That last rule is load-bearing — a 0.9 threshold tolerates one mismatch
in ten, which lets the match run a word past the real repeat and eat the first
word of the tail, and that word is something the speaker actually said. Short
echoes ("Evet." twice) stay: that is dialogue.
Replayed over the 8,006 recorded words of three sessions, the output is always a
subsequence of the input — deletions only, never a reorder, never an invention.
Applied at both finalize points so the re-speak guard, the record and the
exports all read the same text. Known limit: it lands on the record and the
exports, not on the caption bubble already rendered on screen.
Two recorded sessions captioned more than they spoke — 968 words against 1067, then 578 against 652. Part of that was the caption repeating itself, fixed separately. The rest is text the engine described and never voiced, and it does not reproduce across two runs of the same video, so it is timing rather than content. What was missing was a name for it. The two streams were only ever counted in aggregate: audio_track gives a session-level production total, and per-turn attribution from it does not work because audio trails text by a variable amount (replayed against a real session, four of five turns it called silent had in fact been spoken). So "some text was never voiced" could be measured but never pointed at. The response id is the one place the two streams share an identity. Booking produced characters and produced audio bytes against it makes a response with captions and zero audio nameable, which is the whole point. Judged at session end, never at its own .done: audio and text for one response interleave in no guaranteed order, so an immediate verdict would report silence that had merely not arrived yet. The id rides the *.done events for certain and is best-effort on the deltas, so an event without one books against the most recent response rather than being dropped. _on_thread_exit is added to the base class for this: a rotation-time flush only ever sees the sessions BEFORE the last one, so without it the final session — usually the longest — would never be judged. Default no-op, best-effort by contract, so a broken hook cannot take the teardown with it. Log-only, bounded, and off the audio path.
With a virtual cable installed, both meeting directions shared it: the outgoing leg writes the translated voice into CABLE Input and the vbcable capture path reads CABLE Output, so the translation just sent into the call came back in as if the other party had spoken it and was translated again. Meeting now forces the driverless capture path, whose process-exclude loopback cannot hear our own output.
The v1.0.49 test file imported `types` without using it, so `ruff check` failed the Quality workflow while pytest and the i18n parity check passed. Test-only change; the frozen 1.0.49 artifact is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
run_session.py --prod took the server-issued key but dropped the model that came with it, so make_translator fell back through resolve_model to the undated client alias. On DashScope the dated snapshot the server issues and that alias are separate models with separate free quotas, and the alias's was spent: every --prod Qwen run died with "1007 free quota has been exhausted" long after a healthy session.created, because the rejection only lands once audio is sent. That reads like a dead account rather than a wrong model id. Production was never affected — pipeline passes model=_model correctly. --prod is also routing-aware now. The bare session-key call is the server's backward-compat path and always answers Gemini, so benching any other engine has to request routing by target; the returned engine is checked rather than assumed, since silently benching Gemini under a 'qwen' label is the failure this harness exists to prevent. Adds --clone off|once|always. beta_active has to be passed explicitly or engines.make_translator reads clone as "off" and both arms of the A/B come out identical. audio_total_s joins audio_s because the overrun that drives playback backlog is a property of the whole stream, not just its audible frames. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts the silent-room billing pause. Accrual is back on wall clock for the whole session: smart mode already stops streaming after ~1.5 s of quiet, so those minutes cost almost nothing upstream and pausing the meter gave that away fleet-wide for something no user had asked for. Removes _GatedSource._last_speech_ts/idle_seconds, ModeController's IDLE_PAUSE_SECONDS/_speech_idle_seconds/_idle_too_long/_update_idle_notice and both call sites, plus the st_idle_paused/st_idle_resumed strings in all 23 locales. _is_session_live is deliberately untouched — a session whose capture or translator has actually died still stops billing. Silence is billed; failure is not.
Every session spoke with one voice: the client never sent `voice`, so the Qwen leg used the server default, which is female (Tina). There is now an auto/female/male setting per direction — incoming (the video, or the other party) and outgoing (how the user is heard in a meeting) are independent, because a meeting has two speakers and both legs read the same config. Qwen-only, and measured rather than assumed. Qwen honors session.voice: Tina reads ~261 Hz against Ethan's ~170 Hz, and the two arms are different voices by CAM++ cosine (0.34, against 0.92 between the default and Tina). Gemini's translate-preview ignores the field outright — five valid names AND a garbage name all returned the same voice, and the garbage name was not even rejected — so a gender is never mapped to a Gemini voice and the picker says so on those targets instead of silently doing nothing. Only synthesis-verified names may enter the table: DashScope validates the name lazily (session.update echoes anything back; the reject arrives with the first response and strands the session in the retry loop with no audio), and its own published roster is wrong for this model — Cherry and Dylan are documented for it and rejected live. Cloning still wins where it is on: a named voice alongside enable_voice_clone is a hard reject, and the clone already carries the speaker's gender. Alongside it, three things the same users kept missing: - Release notes in the app, once per update, in the user's own language. Store updates are silent, so the only place to read what changed was the Store listing, which nobody reopens — feature work shipped invisibly. A fresh install is marked seen silently; it gets the onboarding tour, not a changelog for a version it never ran. The text comes from the same 23-locale notes the release already writes for the listing (scripts/gen_whatsnew.py), so the two cannot drift. - The term box moved out of General into its own Translation tab and lost the name "Meeting terms" — it has always fed video sessions too — and it no longer starts empty: ~31 curated proper nouns ship with it, switchable and viewable in place. Brands that are also ordinary words are excluded on purpose; a hotword biases recognition toward the term. The user's own entries are merged first, so the 50-pair cap can only drop a default. - The history window's close button sits at the top right like every other window: .mfoot .spacer had a flex rule and .mhead .spacer did not. Also drops a CHANGELOG entry for the silence-billing pause, which a8530a9 had already reverted in code. i18n: 20 new keys across all 23 languages, in their own block that check_i18n.py now parses too, so a missing language fails the gate.
…ring The Qwen engine reports a rejected utterance as input_audio_transcription.failed. That branch logged the error and returned, without marking that input had arrived — so a run of ASR errors disarmed both self-heal watchdogs at once: the no-output watchdog is gated on recent input, and the stall watchdog cannot fire because every server frame, .failed included, resets it. The session then held its connection, showed as active, translated nothing and never reconnected. A field meeting lost its last 12m52s to this: audio still reaching the server, the server still answering, no captions, no voice, the timer running. A .failed only arrives when the server heard an utterance and tried to transcribe it, so it is a truthful input signal. Marking it arms the no-output watchdog, which reconnects up to WATCHDOG_ROTATE_MAX times and refills its budget as soon as healthy output resumes. Also folds in the pending Turkish and German status-string polish.
… notes The whatsnew gate degrades to silence on a version with no entry, which is the right runtime behaviour — a forgotten release should not raise an empty dialog. It is the wrong release outcome: bumping APP_VERSION and skipping `scripts/gen_whatsnew.py <ver>` ships an update nobody is told about, and every other test here still passes because they only check the entries that exist.
…ed release notes
Measured across every recorded session (usage_events at session_id level):
40% of Windows-app sessions end under 30s, 59% under a minute, and 71% of
those short sessions are followed by another attempt a median 14s later.
Users whose best session stayed under a minute come back on another day 8%
of the time; those who once got a 5-minute session, 39%. The first minute
is where the product is lost, and two silences caused it.
- No-input notice: the no-output watchdog only fires when input EXISTED
(base_translator gates on _last_input_ts > 0), so a session with nothing
playing — or a user speaking into their microphone, which Video mode does
not translate — got no signal, no caption and no explanation. Video mode
only: total silence on a meeting's incoming leg is a normal call nobody is
talking in yet. Reads the running input peak, not the slow-release meter.
Billing is untouched; this is a status line, not the idle-pause machinery
removed on 2026-07-29.
- Waiting cue: the stream was blank from Start until the first caption, while
the connect handshake alone exceeds 10s for 16% of sessions (p90 33s). The
cue shows progress, escalates past 10s, sets the latency expectation BEFORE
the wait, and clears on the first translation.
- session_end{reason, seconds, first_audio_s, speech_seen}: the funnel stopped
at session_live, so an abandoned session and a completed one were identical.
reason distinguishes a settings-driven restart from a real give-up; a null
first_audio_s means the user never heard one word. RTTEstimator already
measured that span for the UI and never reported it.
- session_error's `other` bucket swallowed 82 of 143 recorded start failures;
the fallback now carries the exception type, never its message, and a
socket/DNS failure is no longer mislabelled os_import.
- Release notes cover every version the user skipped, not just the running
one. whatsnew_seen already stored a version string, so the range was there
to compute. 1.0.49/1.0.50 backfilled; 1.0.49's silence-billing bullet was
dropped since that behaviour was reverted in 1.0.50 and would have read as
a billing promise this build does not keep.
…rever A translator session that connects and stays open past the 5-second 'proven healthy' mark, then dies having produced zero output, kept resetting the transient-failure counter every retry (measured 2026-08-01: DashScope's qwen3.5-livetranslate-flash-realtime repeatedly closing connections with a raw 500 / 'thread pool exhausted' after 6-13s, never having sent a single translated word). Time-alive alone was treated as proof the connection was healthy, so MAX_TRANSIENT_FAILURES was never reached and the engine never gave up in favor of a working substitute. The reset now also requires that the session actually produced output. Paid plans additionally get a 1-strike failover (any dropped connection swaps straight to the backup engine) instead of the default multi-retry budget — a paying session should not sit through a flaky free engine's normal tolerance.
…ool before Gemini DashScope's "thread pool exhausted" is qwen3.5-livetranslate-flash-realtime's account-wide 10 req/60s capacity ceiling (vs 60/60s for the omni models) - it was classified as generic transient and retried up to 8 times with fast 1-6s backoff, which only added more load to an already-saturated shared rate limit and prolonged the outage it was reacting to. It's now terminal. A terminal Qwen error with an unused server-provided fallback credential (session_key.go's new qwenFallbackCredentials, mirroring the fleet-level rotation order WatchEngineHealth already uses) gets one immediate retry on the sibling pool before giving up to Gemini - applies to every session, paid or free, layered under the existing paid-customer fast-fail carve-out which only covers unclassified transient errors. Backend change already deployed (additive-only JSON field). This commit is the client leg; not yet released to end users. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…volume ceiling on VB-CABLE setups Qwen's "thread pool exhausted" capacity error is now terminal instead of generic-transient (it was being retried up to 8 times, hammering an already-saturated shared rate limit). A terminal error now gets one immediate retry on the server-provided sibling pool before falling back to Gemini. The VB-CABLE volume mirror assumed the physical playback device sat at 0 dB; if it didn't, that device's own level silently capped the output under the mirrored value. OutputLevelNeutralizer now raises the physical device to full for the session and restores it on stop, crash-safe via a sidecar snapshot. Also: new legal/policy docs (EULA, Privacy, Refund, Terms, AI Disclosure, Meeting Consent), README copy edits, and a Palabra benchmark script.
ruff E741 flagged single-letter `l` in the manifest read comprehension.
… a public app.json manifest Voxis had no way to tell a user which build they were running, or that a newer one existed (Silvano feedback). Adds a top-bar version badge plus a background check against a new public https://voxislive.com/app.json (generated by scripts/gen_app_manifest.py, SSOT'd off config.LANGS + local_tts.VOICES + index.html LANG_NAMES) that surfaces an "update available" badge linking to the Store listing. The manifest fetch is unauthenticated and works on the OSS/BYOK build too, not just official.
app/whatsnew.py and CHANGELOG.md were missing the version badge / update check shipped in the previous commit. Regenerated from .local/store-listings/notes_1054.json (scripts/gen_whatsnew.py) after appending the new bullet across all 23 locales.
Bumps the pip group with 1 update in the / directory: [cryptography](https://github.com/pyca/cryptography). Updates `cryptography` from 49.0.0 to 50.0.0 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](pyca/cryptography@49.0.0...50.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-version: 50.0.0 dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] <support@github.com>
Davut Akça (DavutAkca)
force-pushed
the
main
branch
from
August 6, 2026 11:30
9ecedce to
f7c4a2f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps the pip group with 1 update in the / directory: cryptography.
Updates
cryptographyfrom 49.0.0 to 50.0.0Changelog
Sourced from cryptography's changelog.
... (truncated)
Commits
dcb7050Prepare for 50.0.0 release (#15372)53fccd9Don't leak how PKCS#7 encryptedKey decryption failed (#15369)d472f97Addfrom __future__ import annotationsto all src/ Python files (#15371)908773dBump downstream dependencies in CI (#15368)2cc07ccBump BoringSSL, OpenSSL, AWS-LC in CI (#15367)c94ede9chore(deps): bump ruff from 0.16.0 to 0.16.1 (#15366)67a8308chore(deps): bump virtualenv from 21.7.0 to 21.7.1 (#15365)95018ffRelease the GIL in one-shot AEAD encrypt/decrypt (#15361)6954733Release the GIL during DH and DSA parameter generation (#15364)6893b94Import _serialization instead of serialization in x509/extensions (#15363)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore <dependency name> major versionwill close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)@dependabot ignore <dependency name> minor versionwill close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)@dependabot ignore <dependency name>will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)@dependabot unignore <dependency name>will remove all of the ignore conditions of the specified dependency@dependabot unignore <dependency name> <ignore condition>will remove the ignore condition of the specified dependency and ignore conditionsYou can disable automated security fix PRs for this repo from the Security Alerts page.