Skip to content

fix: remap text/both input_mode from config file in TUI to avoid stdin conflict#193

Open
octo-patch wants to merge 1 commit intodnhkng:mainfrom
octo-patch:fix/issue-188-text-input-mode-tui-stdin-conflict
Open

fix: remap text/both input_mode from config file in TUI to avoid stdin conflict#193
octo-patch wants to merge 1 commit intodnhkng:mainfrom
octo-patch:fix/issue-188-text-input-mode-tui-stdin-conflict

Conversation

@octo-patch
Copy link
Copy Markdown

@octo-patch octo-patch commented Apr 4, 2026

Fixes #188

Problem

When input_mode: "text" (or "both") is set directly in the config YAML file, the TUI starts a TextListener thread that reads from sys.stdin. This conflicts with Textual's own stdin handling, causing keyboard input to require multiple keystrokes before registering.

The instantiate_glados() method in tui.py already correctly remaps "text""none" and "both""audio" when input_mode is passed as a CLI override (--input-mode text), but did not apply the same remapping when the value came from the config file itself.

Solution

Added two elif branches after the existing CLI-override block to also remap input_mode when the config file specifies "text" or "both":

  • "text""none": prevents TextListener from competing with Textual for stdin; the TUI Input widget submits text via submit_text_input().
  • "both""audio": keeps audio ASR active while suppressing TextListener to avoid stdin contention.

Testing

Reproduced by setting input_mode: "text" in glados_config.yaml and running uv run glados tui. Before the fix, each keypress required multiple attempts. After the fix, keyboard input registers correctly on the first keypress.

Summary by CodeRabbit

  • Bug Fixes
    • Corrected input mode behavior in the Textual TUI when using configuration overrides. Text-only mode now properly disables audio streaming, while combined input mode correctly maintains full audio and input processing capabilities.

…n conflict (fixes dnhkng#188)

When input_mode is set to "text" or "both" directly in the config YAML,
the TUI would start a TextListener that reads from sys.stdin, competing
with Textual's own stdin handling. This caused keyboard input to require
multiple keystrokes before registering.

The TUI's instantiate_glados() already handled this for the CLI
--input-mode override but not for the config file value. This commit
extends the remapping to also apply when the config file specifies
input_mode: "text" (to "none") or "both" (to "audio"), so the TUI's
Input widget handles text submission via submit_text_input() without
stdin contention.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 4, 2026

📝 Walkthrough

Walkthrough

Updated input-mode resolution logic in the Textual TUI's instantiate_glados() method. The changes implement conditional remapping: "text" input mode now forces input_mode="none" (disabling audio and TextListener), while "both" maps to input_mode="audio" (suppressing TextListener while keeping audio/ASR active). This applies both to direct overrides and default configuration values.

Changes

Cohort / File(s) Summary
Input Mode Remapping
src/glados/tui.py
Modified input-mode resolution logic in instantiate_glados() to remap "text" to "none" and "both" to "audio" for both _input_mode_override and glados_config.input_mode, addressing keyboard input registration issues in text mode.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #166: Introduced initial input_mode config option, CLI override functionality, and TextListener handling—this fix directly depends on and refines the input mode mappings established in that PR.

Poem

🐰 Twitchy paws met text input woes,
Each keystroke whispered, nothing flows—
Now modes remap with careful grace,
Text to none, both finds its place,
Keyboard clicks dance smooth as moss! 🎹✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: remapping text/both input_mode in TUI config to prevent stdin conflicts.
Linked Issues check ✅ Passed The changes address issue #188 by remapping input_mode values from config file to prevent TextListener/stdin conflicts with Textual.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the input_mode remapping logic in TUI's instantiate_glados() method related to issue #188.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/glados/tui.py (1)

1417-1427: Please lock the YAML path down with a regression test.

The original bug only reproduced when input_mode came from config rather than the CLI override. A focused test for the "text" -> "none" and "both" -> "audio" remaps here would keep the two paths from drifting again.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/glados/tui.py` around lines 1417 - 1427, Add a regression test that
verifies the remapping logic that converts glados_config.input_mode == "text" to
"none" and "both" to "audio" when the TUI is running: simulate a config-only
input (no CLI override) and assert that the function which mutates the updates
dict (the code handling glados_config and updates in tui.py) sets
updates["input_mode"] to "none" for "text" and to "audio" for "both"; also add
negative tests ensuring CLI overrides prevent these remaps (i.e., when a CLI
input_mode is present, updates should not be rewritten), and wire the tests to
run under the same code-path used by the TUI startup so the regression is locked
in.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/glados/tui.py`:
- Around line 1406-1427: The code writes "none" into updates["input_mode"],
which violates GladosConfig.input_mode's Literal["audio","text","both"]; instead
avoid injecting "none" and add a TUI-only flag to suppress the TextListener:
leave updates["input_mode"] set to one of "audio"/"text"/"both" (use the same
mapping logic in the branches around _input_mode_override and
glados_config.input_mode) and add a separate boolean key such as
updates["tui_suppress_text_listener"] (or self._tui_suppress_text_listener)
where you currently set "none" to indicate the TUI should not start
TextListener; update Glados.from_config()/engine code to read that flag to
conditionally skip TextListener (affecting speech_listener/text_listener
startup) rather than changing input_mode's value.

---

Nitpick comments:
In `@src/glados/tui.py`:
- Around line 1417-1427: Add a regression test that verifies the remapping logic
that converts glados_config.input_mode == "text" to "none" and "both" to "audio"
when the TUI is running: simulate a config-only input (no CLI override) and
assert that the function which mutates the updates dict (the code handling
glados_config and updates in tui.py) sets updates["input_mode"] to "none" for
"text" and to "audio" for "both"; also add negative tests ensuring CLI overrides
prevent these remaps (i.e., when a CLI input_mode is present, updates should not
be rewritten), and wire the tests to run under the same code-path used by the
TUI startup so the regression is locked in.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b7f0237-0ac2-42c4-bf5e-e26d2bb23ef4

📥 Commits

Reviewing files that changed from the base of the PR and between add0e59 and 6becc3f.

📒 Files selected for processing (1)
  • src/glados/tui.py

Comment on lines +1406 to +1427
if self._input_mode_override == "text":
# In TUI text mode, use "none" so neither audio stream nor TextListener
# is started. The TUI Input widget handles all text input, avoiding
# both high CPU from audio and stdin contention with TextListener.
updates["input_mode"] = "none"
elif self._input_mode_override == "both":
# In TUI+both mode, keep audio ASR active; the TUI Input widget handles
# text input, so TextListener is not needed (avoids stdin contention).
updates["input_mode"] = "audio"
else:
updates["input_mode"] = self._input_mode_override
elif glados_config.input_mode == "text":
# Config file sets input_mode: "text" but TUI is running — remap to "none"
# to prevent TextListener from competing with Textual for stdin, which causes
# keyboard input to require multiple keystrokes. The TUI Input widget submits
# text via submit_text_input() instead.
updates["input_mode"] = "none"
elif glados_config.input_mode == "both":
# Config file sets input_mode: "both" but TUI is running — remap to "audio"
# so audio ASR stays active while TextListener is suppressed to avoid stdin
# contention with Textual. Text input is handled by the TUI Input widget.
updates["input_mode"] = "audio"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "GladosConfig.input_mode declaration:"
sed -n '104,112p' src/glados/core/engine.py

echo
echo "Engine input_mode branches:"
rg -nP -C2 'self\.input_mode\s*(==|in)' src/glados/core/engine.py

echo
echo 'Current TUI remap block:'
sed -n '1403,1434p' src/glados/tui.py

echo
echo 'Any existing engine-side support for "none":'
rg -nP '"none"' src/glados/core/engine.py src/glados/tui.py

Repository: dnhkng/GLaDOS

Length of output: 4007


Don't inject an undeclared input_mode value.

Lines 1410 and 1422 write "none" to input_mode, but GladosConfig.input_mode is declared as Literal["audio", "text", "both"] and the engine only branches on those three values. When this config is passed to Glados.from_config(), the "none" value will violate the type contract and cause both speech_listener and text_listener to be silently skipped without explicit intent.

Either extend the input_mode Literal type to include "none" across config and engine layers, or suppress TextListener using a separate TUI-only flag while keeping input_mode within its declared contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/glados/tui.py` around lines 1406 - 1427, The code writes "none" into
updates["input_mode"], which violates GladosConfig.input_mode's
Literal["audio","text","both"]; instead avoid injecting "none" and add a
TUI-only flag to suppress the TextListener: leave updates["input_mode"] set to
one of "audio"/"text"/"both" (use the same mapping logic in the branches around
_input_mode_override and glados_config.input_mode) and add a separate boolean
key such as updates["tui_suppress_text_listener"] (or
self._tui_suppress_text_listener) where you currently set "none" to indicate the
TUI should not start TextListener; update Glados.from_config()/engine code to
read that flag to conditionally skip TextListener (affecting
speech_listener/text_listener startup) rather than changing input_mode's value.

@dnhkng
Copy link
Copy Markdown
Owner

dnhkng commented Apr 4, 2026

@octo-patch can you comment on the issues raised by coderabbit?

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.

Keyboard inputs don't register properly with input_mode: text

2 participants