Skip to content

fix(tui v2): normalize first printable-key insert path in InputArea#587

Open
rodboev wants to merge 695 commits into
lsdefine:mainfrom
rodboev:fix/tui-v2-first-keystroke
Open

fix(tui v2): normalize first printable-key insert path in InputArea#587
rodboev wants to merge 695 commits into
lsdefine:mainfrom
rodboev:fix/tui-v2-first-keystroke

Conversation

@rodboev

@rodboev rodboev commented Jun 8, 2026

Copy link
Copy Markdown

Summary

  • Fix the startup / first-typing regression in frontends/tuiapp_v2.py without touching other files.
  • Ensure printable key handling in InputArea._on_key() goes through the same insertion path used by paste/newline via _insert_via_keyboard(), including a Windows-safe fallback.

Root cause

  • In InputArea._on_key(), printable keys relied on super()._on_key(event).
  • On some Windows key events, event.is_printable can be true while event.character is missing, which can cause the base TextArea insertion path to not apply that first key reliably.

Fix

  • Updated frontends/tuiapp_v2.py:2334 inside InputArea._on_key():
    • Added a printable-key branch before the super() path.
    • Uses event.character first and falls back to event.key when character is None and key is a single character.
    • Routes the insert through _insert_via_keyboard().
    • Resets _history_index immediately for in-line editing (same intent as existing history-reset behavior).
  • Left Enter/Shift+Enter submit behavior and existing history/palette routing unchanged.

Verification

  • python -m py_compile frontends/tuiapp_v2.py
  • python -c "import ast; ast.parse(open('frontends/tuiapp_v2.py', encoding='utf-8').read())"
  • python frontends/tuiapp_v2.py — run attempt blocked by existing runtime config issue in this environment:
    • ZeroDivisionError: integer modulo by zero in agentmain.py:84 (load_llm_sessions() with empty LLM config list).
  • Manual startup typing checks (repro steps from prompt) not executed due launch failure above.

Upstream

Closes #498.
Reported by @luodh0157.

lsdefine and others added 30 commits May 8, 2026 17:28
)

* feat: add setup.py -- interactive initialization wizard

* fix: address PR lsdefine#295 review — rename to configure.py, improve all feedback

- Rename setup.py → configure.py to avoid pip/setuptools conflict
- Fix lark exception: catch generic Exception instead of non-existent AppAccessDeniedError/AppExpiredError
- Add proxy support to probe_models via HTTPS_PROXY/HTTP_PROXY env vars
- Implement masked_input() with real-time API key masking
- Add pip install hints for messaging platform dependencies
- Add 2 new providers: CRS Gemini Ultra (Antigravity), Kimi/Moonshot OAI
- Restructure main menu: LLM only / platform only / or both with cascade prompts
- Add Python 3.14 compatibility warning and reconfiguration detection
- Fix welcome_message: WeCom only, correct variable name
- Show GETTING_STARTED example commands after configuration
- Add advanced LLM options: proxy, context_win, stream, user_agent

---------

Co-authored-by: guowenjiao54 <guowenjiao54@users.noreply.github.com>
…sdefine#307)

When the model only outputs tool calls with no meaningful text,
combined_content may contain only whitespace text blocks (e.g. '\n').
Strict API proxies reject this with HTTP 400 'empty content' error.
Filter out such blocks before sending to the API.
Co-authored-by: Kailigithub <Kailigithub@users.noreply.github.com>
Mirrors Claude Code's /btw: ask the agent a quick question without
interrupting the in-flight main task.

- frontends/btw_cmd.py: snapshot backend.history under lock + deepcopy →
  background-thread backend.raw_ask single-turn answer; never writes
  backend.history; never touches task_queue. Reuses backend.raw_ask +
  make_messages, no new LLM instance.
- frontends/chatapp_common.py: register /btw in HELP_COMMANDS; handle
  /btw in AgentChatMixin (sync via asyncio.to_thread); install() at
  module bottom so the slash hook is wired for any frontend that
  imports chatapp_common.
- frontends/stapp.py: persist display_queue + partial_response in
  st.session_state so the streaming bubble survives Streamlit reruns;
  new render_main_stream() helper covers both new tasks and resume;
  /btw branch uses plain st.rerun() (preserves dq) so the main task's
  remaining output renders in a follow-up bubble; non-/btw prompts
  explicitly call agent.abort() to keep the original "submit cancels"
  UX. Strip <summary> meta tags from text segments and drop dangling
  "LLM Running (Turn N) ..." markers in resumed partials to clean up
  the rendered chat.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Keep Telegram streaming overflow messages tied to the primary live message, reuse them across edits, and delete stale overflow replies when streamed content shrinks.
新增 command/ 包和 ga.cmd/ga 入口脚本,用户可通过 `ga <command>` 启动各前端。
PPT/Word copies include both image/png and text/plain items.
Only intercept paste when no text item is present (pure screenshot/image).
新版 TUI 与 tuiapp.py 并存,共享 GenericAgent 调度/队列/线程协议,
重做视觉与交互:
- 多行 InputArea:≤3 行自适应、>3 行三行滑动窗口、粘贴占位符
- 命令面板(OptionList + 模糊匹配 + Tab 补全)
- /llm、command 选择走 ChoiceList,选完区域收缩
- Markdown HardBreak 渲染 + ANSI 缓存
- 响应式布局(窄屏隐藏侧栏 / 宽屏按比例放大)
- selection-aware 自动滚动(选文本时不抢滚动)
- 新增 /btw、/continue、/export、/restore

不改 cli / hub / README / tuiapp.py 入口。
nianyucatfish and others added 29 commits May 29, 2026 21:55
* fix(tui): drain exit-boundary intervened replay

* refine(tui): soften intervened message prompt
Update WeChat group QR code (group 19 → group 20)
…search nav (lsdefine#554)

* fix(tui): stop CRLF stdout rendering as blank lines (v2)

Windows child stdout is `\r\n`; the streaming path fed it straight to
`Text.from_ansi`, which treats `\r` as a carriage return and erases each
line's text, so a `[Stdout]` block rendered as a run of blank lines until
the turn finished (the done-state Markdown render strips `\r`). Normalise
`\r\n` -> `\n` before `from_ansi`; lone `\r` is kept so progress-bar
overwrites still work.

* fix(tui): stop leaking empty per-session task_dir signal folders

Both v2 and v3 eagerly `os.makedirs` the per-session `_intervene` /
`_keyinfo` signal dir, but the only writer (`_intervene`) already creates
it lazily — so every session that never used intervene left a stale empty
`temp/_tui_v2_<pid>_<id>` / `temp/_tui_v3_<pid>` behind (85+ accumulated).
`consume_file` tolerates a missing dir, so the eager create is redundant.

- Set only the task_dir *path*; let the writer create it on first inject.
- Remove empty dirs on session close (v2 `_cmd_close`), on graceful exit
  (v2 `on_unmount`, v3 `_cleanup`), and sweep stale ones left by crashes
  at startup (v2 `on_mount`, v3 `main`). `os.rmdir` only removes empty
  dirs, so a still-pending `_intervene` is never clobbered.

* fix(tui): repair /continue search box cursor & up-nav loop (v2)

The SearchablePicker swallowed Right while the search Input had focus
(always treating it as "select"), so the caret could never move right in
the query. And Up could leave the list but never return: from the search
box Down entered the list, yet Up at the top row was stuck.

- Right commits the highlight only when the caret is already at end of the
  query; otherwise it moves the caret within the search text.
- Up on the first list row returns focus to the search box (scoped to
  /continue — no-op for ChoiceLists outside a SearchablePicker), clearing
  the highlight so the next Down re-enters at the first row, not the second.
- Up from the search box wraps around to the bottom of the list.
fix: add plugins/__init__.py for proper package recognition
…证后增补验收线/锁边界质疑目标/遗留项如实入报告; TMWebDriver: 修mark_disconnected重复打印
/continue list previews showed JSON debris (e.g. `{`, `" } ]} === Response ===`)
for some sessions. Two causes in _preview_from_file:

1. Models sometimes emit an unclosed <summary>, so the non-greedy DOTALL regex
   pairs it with a far-away </summary> and captures === block headers / JSON
   across rounds. Now only the latest <summary> is used, and it is rejected if
   it looks like such a cross-segment capture (contains `=== `/`"role"` or
   >200 chars) — falling through instead of digging for older summaries.
2. The fallback scanned for the first non-=== line, returning JSON structure
   lines. It now uses the last real user prompt via a new _last_user(), which
   scans Prompt blocks directly (so response-less/aborted sessions still
   preview) and reuses _user_text() to skip tool_result continuations and all
   _INJECT_MARKERS (WORKING MEMORY / SYSTEM TIPS / DANGER ...).

Scanned 261 local logs: garbage previews 26 -> 0.
…ew-garbage

fix(continue): robust session preview for malformed logs
…loop, log name collision fix, checklist poll hard cap
- conductor_im_plugins: 新增 lark.py(飞书收消息插件,无状态轮询)、email.py、_TEMPLATE.py
- wechat.py/config.local.json 经gitignore排除(含个人会话ID)
- conductor.html/py: UI重构(grid/可折叠卡片/console/approvals)、action与pool化重构
- stapp.py: 移除_js_scroll_fix
- ljqCtrl_sop: 补充Click点歪诊断/DPI坐标陷阱/文本输入经验
Rename tracked email.py/lark.py to _email_example.py/_lark_example.py so
the poller (which skips _-prefixed files) no longer polls them on a fresh
clone. Activate by copying to email.py/lark.py (now gitignored). Also nudge
manager to reuse existing subagents on IM signals.
…ar (lsdefine#572)

- _term_write(): route OSC/control sequences through Textual's driver writer
  queue instead of direct sys.__stdout__. A title/OSC write landing mid-frame
  during streaming used to split Textual's own escape sequences, rendering as
  flashing ANSI garbage; serializing on the same writer queue lands it cleanly.
- sidebar: wrap the session list in a VerticalScroll (#sidebar-scroll) so it
  scrolls when overflowing (scrollbar-gutter reserved to avoid row reflow);
  inner #sidebar Static keeps padding so the click hit-test math is unchanged.
  Keyboard session-switch scrolls the active session into view.
…e#581)

Ctrl+C / selection copy fell back to Textual's OSC 52 escape on Windows/Linux.
Some terminals (notably VSCode's integrated terminal) decode the OSC 52 base64
payload as latin-1 instead of UTF-8, so non-ASCII copied text lands in the
clipboard as mojibake (e.g. 需要 -> éè¦). Classic Windows conhost ignores
OSC 52 clipboard writes entirely, so copy was a silent no-op there (incl.
Windows Server).

Prefer the OS-native clipboard (_copy_to_clipboard: win32 CF_UNICODETEXT /
pbcopy / wl-copy / xclip), which writes real Unicode and bypasses the terminal.
Keep OSC 52 only as a fallback, and force it under SSH (SSH_CONNECTION /
SSH_TTY) where the native call would write the remote host's clipboard instead
of the user's local one.
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.

ga tui2启动模式下,输入框输入内容需要输入最少2遍才能显示内容