Skip to content

fix(tui): v2+v3 bug fixes, pending input queue, ! shell magic, /resume#513

Merged
lsdefine merged 6 commits into
lsdefine:mainfrom
shenhao-stu:tui-v2-v3-fixes-pending-shell
May 28, 2026
Merged

fix(tui): v2+v3 bug fixes, pending input queue, ! shell magic, /resume#513
lsdefine merged 6 commits into
lsdefine:mainfrom
shenhao-stu:tui-v2-v3-fixes-pending-shell

Conversation

@shenhao-stu
Copy link
Copy Markdown
Contributor

@shenhao-stu shenhao-stu commented May 27, 2026

Summary

Bundle of v2/v3 TUI bug fixes + two new features (pending-input queue, ! shell magic) + /resume wiring + cherry-picked upstream fixes (#466, #461).

Bug fixes

  • v2 Ctrl+S freeze on long sessions — Defers reset() + cleanup via call_after_refresh. _skip_change_next short-circuits the synchronous Changed event so the keystroke handler returns instantly even when streaming saturates the reactive queue.
  • v3 terminal title only "GenericAgent" — Route _set_term_title via os.write(1, ...); PTK redirects sys.stdout to sb_agent.log, so the OSC 0 escape was landing in the log instead of the terminal. Default session name = \"session\" for v2 parity; repaint on running→idle.
  • v3 user input bg cutoff_tile() now pads explicit bg-active spaces to width. PTK's cell renderer doesn't honour \x1b[K, so the charcoal user-bubble band used to stop at text length.
  • v3 ask_user freeze on "请你coderun以下"_tool_status no longer marks an in-flight ask_user chip as ✓ ok while the stream still contains Waiting for your answer .... Bumped DoneEvent grace to 10 s when that marker is in the stream so a slow ask_user_queue.put doesn't cause the picker to fall through to _finalize.
  • v3 /scheduler can't see running services — Added pre_checked kwarg to _show_menu so the running set lands atomically with the rest of the menu state (PTK could otherwise render the menu before the post-_show_menu override took effect). slash_cmds._match_service relaxed to path-only so directly-launched (python reflect/foo.py) services are detected. Running rows colored functional green in both v2 + v3.
  • /scheduler confirm card → left arrow rollbackChoiceList and v3 _menu_card now treat as cancel (parity with Esc) so the user can roll back to the picker one-handed.
  • "Turn 1 ..." marker leaking into scrollback — Setting agent.task_dir (required for the _intervene hook below) flips agent_loop.py:52 to emit the short **Turn N ...** form. Extended all four v3 turn-marker regexes (_TURN_MARKER_RE, _TURN_SPLIT_FOLD_RE, _TURN_MK_RE, and the _safe_pos inline patterns) plus the v2 split-pattern to match either form.

New features

  • Pending input queue (v2 + v3) — Submit while the agent is running no longer rejects. Queued entries render as [queued #N] markers and live in a preview block above the input. 5 s cooldown that resets per new pending, then drains via <task_dir>/_intervene (mid-turn) — the agent picks up the combined text at the next turn boundary thanks to ga.turn_end_callback. Falls back to put_task when the agent is idle. recalls the most-recent queued entry for amendment; Esc clears the queue.
  • ! shell magic command (v2 + v3) — Prefix ! enters shell mode: pink border, prompt mark swaps to !, body strips the leading ! so the glyph shows once. Enter runs the rest as subprocess.run(shell=True, timeout=30), echoes ! cmd + └ output into scrollback, and appends to the LLM history so a follow-up like "what did I just run?" finds the context. The history append routes through _intervene while the agent is running to avoid racing the runner thread's iteration of backend.history.
  • /resume wiring — Forwarded as a literal to the agent so GA's _handle_slash_cmd (agentmain.py:124) can expand it. Added to the v3 palette, v2 COMMANDS, slash_cmds.COMMANDS, i18n (en+zh), /help, and tip rotations.

v3 emoji polish

  • Dropped _PETS_ASCII / _PETS_BUNNY / _PETS_BLOB.
  • Restored bear calm tier to ʕ•ᴥ•ʔ (default).
  • cat + dot calm tiers use ; focused tier uses o — mood progression matches user-requested aesthetic.
  • Pet refresh decoupled from spinner via self._spin // 5 so the face cycles at ~0.5 s while the spinner stays snappy at 0.1 s.

v3 keybindings

  • Esc Esc (within 800 ms) → /rewind. Handled inside _handle_key because PTK's Esc interception swallows it before _keys runs.

Cherry-picked upstream fixes

Test plan

  • v2 Ctrl+S after 100+ messages in a streaming session — input clears within a frame, no freeze
  • v3 terminal title shows ⠋ <session> · GenericAgent during run and <session> · GenericAgent at idle
  • v3 user message bubble extends its charcoal band to the right edge
  • v3 /emoji opens picker; bear default; pet visibly slower than spinner
  • v3 input 请你coderun以下 → ask_user chip stays · … until answered; ask card renders inline with input
  • /scheduler shows running services pre-checked (green) in both v2 + v3; ← on confirm card rolls back
  • No Turn 1 ... text in scrollback during normal runs
  • v3 Esc Esc within 800 ms → /rewind picker
  • Submit while agent is running → row appears in pending preview; ↑ recalls; Esc clears; 5 s of silence → message lands as [MASTER] ... in the next LLM call's prompt
  • !echo hi → scrollback shows ! echo hi / └ hi; agent recalls the command on a follow-up question
  • /resume in either TUI lists recent sessions

shenhao-stu and others added 6 commits May 27, 2026 23:16
Added installation instructions for Windows and Linux, including one-click install options and manual clone instructions. Updated configuration examples and GUI launch commands.
v2 Ctrl+S freeze: defer reset() + cleanup via call_after_refresh so the
keystroke handler returns immediately even when streaming has the
reactive queue saturated. Added _skip_change_next flag to short-circuit
on_input_area_changed during the deferred clear.

v3 terminal title: route _set_term_title via os.write(1,...) — sys.stdout
gets redirected to sb_agent.log during PTK app run, so OSC 0 escapes
were silently ending up in the log file. Default _session_name = 'session'
for v2 parity, and repaint title on running→idle transition.

v3 input row bg fill: _tile() now pads explicit bg-active spaces to
width — PTK's cell renderer doesn't honour \x1b[K (erase-to-EOL), so
the user bubble's charcoal band used to stop at text length.

v3 /emoji picker: arrow-key menu (mirrors /llm). Pet styles cleaned to
bear (default) + cat + dot + unicode. Bear calm tier restored to ʕ•ᴥ•ʔ.
• used for the calm tier, o for focused — matches user-requested mood
progression. Pet frame rate decoupled from spinner via self._spin // 5.

v3 ask_user "coderun freeze" fix: _tool_status no longer marks an
in-flight ask_user chip as ✓ ok when the stream contains the
"Waiting for your answer ..." marker. Bumped DoneEvent grace from 2 s
to 10 s when the marker is in the stream so a slow ask_user_queue.put
doesn't cause the picker to fall through to _finalize.

v3 /scheduler picker: added pre_checked kwarg to _show_menu so the
running set lands atomically. slash_cmds._match_service relaxed to
path-only matching so directly-launched (`python reflect/foo.py`)
reflect tasks are detected. Running rows highlighted in functional
green. Confirm card accepts ← as cancel (parity with Esc) — same on
v2 ChoiceList.

v3 keybindings: Esc Esc (within 800 ms) → /rewind (handled in
_handle_key so PTK's Esc interception path actually fires).

v3 pending input queue: agent.task_dir wired so ga.turn_end_callback
can consume `<task_dir>/_intervene`. New AgentBridge.inject_intervene
appends to the file (atomic vs read-modify-write), and _drain_pending
routes the combined message either through that hook (mid-turn) or via
put_task (idle). Cooldown 5 s, resets per new pending. ↑ recalls the
last queued message, Esc clears the queue. v2 has equivalent shape:
AgentSession.pending + per-session agent.task_dir + _inject_intervene
+ _poll_pending timer.

! shell magic: prefix `!` enters shell mode — pink border, prompt mark
swaps `❯` → `!`, body strips the leading `!` so the glyph appears
once. Enter runs the rest as `subprocess.run(shell=True, timeout=30)`,
echoes `! cmd` + `└ output` into scrollback, and appends to LLM
history (via _intervene when agent is running, direct backend.history
append when idle — sidesteps the iterator race).

/resume: forwarded as a literal to the agent so GA's _handle_slash_cmd
(agentmain.py:124) can expand it. Added to v3 _cmds() + v2 COMMANDS +
slash_cmds.COMMANDS + i18n + /help + Tips.

Turn marker regex fix: agent_loop.py:52 switches `**LLM Running
(Turn N) ...**` to the short `**Turn N ...**` when task_dir is set.
Extended all v3/v2 turn-marker regexes to match either form.

Cherry-picked upstream fixes:
- PR lsdefine#466 (8ae3645): _collapse_choice early-return when the
  on_select callback (e.g. /rewind) detaches the anchor widget.
- PR lsdefine#461 (08f21e8): SelectableStatic.has_valid_selection_parent +
  app-level _is_stale_selectable_mouse_event filter that swallows
  mouse events on detached widgets.
Per user screenshot 040748, the committed `! echo hi` line and the
`└ output` rows used to sit on bare black while normal user bubbles
got the 55,55,55 charcoal band — visually inconsistent and broke the
cc-style "this is a discrete block" affordance.

New `_TILE_SHELL` (65,60,65 bg, light fg) wraps both halves via
`_tile()`, so the pink `!` prompt and dim `└` glyph inherit the band
across the full row width.  Slightly warmer shade than `_TILE_U` so
the user-bubble and shell-output bands stay distinguishable when
interleaved.  Black-terminal only — matches user spec.
Previous commit's _TILE_SHELL forced a light fg across both the `└`
gutter and the command output, erasing the dim styling on the gutter
and overwriting any ANSI the shell command emitted into stdout.

Make the tile bg-only (`\x1b[48;2;65;60;65m`).  Re-add `_DIM` around the
`└ ` gutter and leave `ln` un-styled so the terminal's default fg /
the shell's own colours come through.  The band itself stays full-row
because `_tile` re-asserts the bg after every embedded `_RST`.
Last commit wrapped both the `! cmd` echo AND the `└ output` rows in
the charcoal tile.  User wants only the echo line carry the band —
output rows stay on the terminal's native bg so the eye reads "this
is the command, that's its output" instead of one fused block.
@lsdefine lsdefine merged commit 2f0b603 into lsdefine:main May 28, 2026
shenhao-stu added a commit to shenhao-stu/GenericAgent that referenced this pull request May 28, 2026
…lone

`tuiapp_v2.py:27` imports `fmt_key`, `fmt_keys` from `keysym`, but the
module file was never tracked. PR lsdefine#513 (tui v2/v3 fixes) is unusable on a
fresh checkout without this. Adds the cross-platform shortcut formatter
that turns binding strings into mac/Win-Linux key labels (e.g.
`"ctrl+b"` → `"⌃B"` / `"Ctrl+B"`), honoring `GA_KEYSYM_STYLE`.
FeiNiaoBF pushed a commit to FeiNiaoBF/GenericAgent that referenced this pull request May 28, 2026
…lone

`tuiapp_v2.py:27` imports `fmt_key`, `fmt_keys` from `keysym`, but the
module file was never tracked. PR lsdefine#513 (tui v2/v3 fixes) is unusable on a
fresh checkout without this. Adds the cross-platform shortcut formatter
that turns binding strings into mac/Win-Linux key labels (e.g.
`"ctrl+b"` → `"⌃B"` / `"Ctrl+B"`), honoring `GA_KEYSYM_STYLE`.
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