Skip to content

fix!: preserve voice model selection and prevent PipeWire self-ducking - #25

Merged
malhashemi merged 6 commits into
malhashemi:mainfrom
francocarballar:fix/voice-new-session-and-linux-ducking
Oct 7, 2026
Merged

malhashemi merged 6 commits into
malhashemi:mainfrom
francocarballar:fix/voice-new-session-and-linux-ducking

Conversation

@francocarballar

@francocarballar francocarballar commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

The voice plugin connects OpenCode’s terminal interface to a live audio call.
OpenCode’s terminal interface provides the active session and the model selected for written prompts.

Startup flow

The plugin first reuses the active session or creates one.
The plugin saves the selected model to the session before starting audio.
The controller starts the audio helper and requests the live call.
If no model is selected, the plugin leaves the new session’s model unset.
OpenCode applies its normal default to that session.

Cancellation

The controller releases ownership of a cancelled startup immediately.
The controller ignores results from older attempts.
This lets you retry or run /voice-new while an earlier session request is still pending.

Linux ducking

PipeWire can omit the process ID (PID) from the helper’s audio stream.
Without that ID, the ducking logic could lower the call’s own output.
The plugin excludes the helper’s known stream name when the PID is missing.
The ducking logic still uses the PID when available and continues to cover other applications.

Limits and verification

These changes prevent stale startup attempts and self-ducking, but they do not guarantee provider availability or hardware behavior.
bun run check passed with 83 tests.
Linux native tests passed with three tests.
Clippy and formatting checks passed.
Real-call testing on macOS has not been performed.

@francocarballar
francocarballar marked this pull request as ready for review September 30, 2026 22:43
@francocarballar

Copy link
Copy Markdown
Contributor Author

Hi @malhashemi90! When you have a moment, could you take a look at this PR? CI seems to be awaiting maintainer approval, and I'd love your feedback. Thanks!

@malhashemi malhashemi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @francocarballar, thank you so much for this! It's a genuinely great PR: you found and reproduced two real bugs (the new-session model mismatch and PipeWire self-ducking), the write-up is excellent, and the tests around the startup races are really thorough. I appreciate the care you put into it.

I've been through it in detail. The model fix, the startup cancellation and the Linux ducking fix look good to me. The playback changes touch the audio engine on every platform and have only been live-tested on Linux, so I'd like to land them separately. Would you be up for splitting this into two PRs?

1. This PR: model selection, startup cancellation and Linux ducking, with a few small changes:

  • No model selected: rather than refusing to start the call ("Choose a model before starting a voice call."), could callModel return undefined and the call just go ahead without setting a model (skip the model on session.create and skip switchModel)? It's an edge case and I'd rather keep the old behaviour there than add a new failure.

  • Cancel, then retry: stop() invalidates the generation, but preparing (index.ts) and starting (controller.ts) stay true until the cancelled await finishes. So:

    • stopping during model sync and immediately starting again skips the new attempt;
    • /voice-new while a Home session is being created cancels the first attempt and silently drops the second, so no call starts.

    Could the gates be released (or owned per generation) when an attempt is cancelled?

2. A new PR for the playback changes (engine.rs / io.rs / resample.rs), with two things I'd love to see addressed:

  • Unbounded backlog: service_playback now decodes up to 32 packets per cycle even when the speaker ring is full, and the remainder accumulates in the resampler's VecDeque. Previously the 4 s ring bounded it. If the output callback stops (device unplugged or switched), memory and latency grow for the rest of the call. Could pending audio be capped (dropping the oldest), or the call ended on output failure?
  • Clear acknowledgement with no way out: after a clear, the engine waits for the renderer to acknowledge before it refills. If the callback has stopped, GPT-Live stays silent for good while the mic is still live. A timeout or fallback would cover it.

I'll test that PR on macOS (built-in mic and speakers, interruptions, talking over it, switching the output device) once it's up, since I can't reproduce the PipeWire setup here.

Thanks again, this is really appreciated! 🙏

francocarballar and others added 6 commits October 6, 2026 12:50
Use the same model and variant for session creation and persistence. Do not start on a session the user left during preparation.

Co-authored-by: OpenAI <noreply@openai.com>
Guard asynchronous startup and helper callbacks after stop or disposal. Stop late RPC replies by their returned call ID, and cancel Home preparation before navigation or model persistence.

Co-authored-by: OpenAI <noreply@openai.com>
Libera las trabas por intento al cancelar para que un retry inmediato o /voice-new durante la creación de sesión pueda arrancar.

Co-Authored-By: OpenAI <noreply@openai.com>
@francocarballar
francocarballar force-pushed the fix/voice-new-session-and-linux-ducking branch from 9c16438 to 826d50a Compare October 6, 2026 16:25
@francocarballar

Copy link
Copy Markdown
Contributor Author

Hi @malhashemi, I’ve split the changes as requested. PR #25 now covers model selection, startup cancellation, and Linux ducking. PR #29 contains the playback changes and is ready for the macOS test you mentioned. Could you review them when you have a chance? Thanks!

@malhashemi

Copy link
Copy Markdown
Owner

Thanks so much @francocarballar! The split and the follow-up fixes are exactly what I was hoping for: the default-model fallback and the per-attempt startup gates both look great, and the tests are excellent. Merging now. Really appreciate the effort you put into this. 🙏

@malhashemi
malhashemi merged commit c3e44de into malhashemi:main Oct 7, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants