`tuiapp_v2.py:27` imports `fmt_key`, `fmt_keys` from `keysym`, but the
module file was never tracked. PR #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`.
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.
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`.
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.
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 #466 (8ae3645): _collapse_choice early-return when the
on_select callback (e.g. /rewind) detaches the anchor widget.
- PR #461 (08f21e8): SelectableStatic.has_valid_selection_parent +
app-level _is_stale_selectable_mouse_event filter that swallows
mouse events on detached widgets.
Added installation instructions for Windows and Linux, including one-click install options and manual clone instructions. Updated configuration examples and GUI launch commands.
1. _TILE_U: switch tile bg/fg to truecolor (48;2;55;55;55 / 38;2;230;230;230)
so the file-tree tiles render legibly on Win Terminal dark themes
instead of a muddy 8-color block.
2. _ticker: tighten the redraw cadence 0.4s -> 0.1s so the spinner and
status line feel responsive on Windows.
3. terminal title: add _set_term_title() helper and emit OSC 0 on init /
session new / rename / quit so the tab title tracks the active session.
4. _drain: stream-aware grace window — when the pending stream carries a
pending ask_user ("Waiting for your answer"), extend the post-done poll
so the question card is not swallowed before the prompt appears.
5. /emoji command: split _PETS into _PETS_UNICODE / _PETS_ASCII behind a
module-level _pet_style toggle; add the /emoji dispatch + _cmd_emoji
handler so users on fonts without emoji can fall back to ASCII pets.
Local-only edits, no cross-module API change; AST parses clean.
Co-authored-by: Shen Hao <shenhao-stu@users.noreply.github.com>
/continue previously pushed plan_scan_baseline past every restored
message, so plan_state.is_active() saw an empty slice and hid the card —
even when the prior session's plan.md still had `[ ]` items the user
wanted to keep working on. `working['in_plan_mode']` isn't replayed by
the log restore either, so the card never came back.
Make the baseline conditional: default to 0 so the scanner walks the
full restored history; only push past it when the restored plan.md is
already all-done, suppressing the stale ✓ card. Resolved-path missing
or empty items naturally fall back to "no card".
/stop now also refills the input box with the just-sent user message,
so an interrupted send is one keystroke from edit-and-resend. Only
when the box is empty (don't clobber a half-typed follow-up). Agent
history is untouched — a resend duplicates the turn in LLM context;
/rewind 1 is the manual escape.
Behaviour matrix (/continue):
unfinished plan → baseline=0, scanner finds path, card resumes
completed plan → baseline=len(msgs), card stays hidden
plan.md deleted → resolve_path None, card stays hidden
fresh enter_plan_mode after continue → working flag re-arms, normal
* fix(tui): slash-cmd polish — /scheduler perf, v2 i18n typo, v3 bundle i18n
- slash_cmds: cache `running_services` list (≥1s TTL) and use a single
service-name preload + lru-cached lookup. /scheduler picker cold path
~2.1s → ~1.0s, warm path ~0ms. Cache is invalidated whenever
`start_service` succeeds so the picker reflects the new state on the
next open.
- tuiapp_v2: drop the stray space inside `/reload-keys` zh description
("重新加载 mykey.py" → "重新加载mykey.py").
- tui_v3: route the remaining slash_cmds-bundle rows (/autorun,
/morphling, /goal, /hive, /scheduler) through _t() the same way
/update already was, with matching en/zh entries in _I18N. Keeps the
v3 command palette fully bilingual.
* tui: settle spinner on abort + DRY-extract annotation helpers
Aborting a turn (Ctrl+C / /stop) left the live spinner ticking elapsed
until the LLM stream unwound in the background. Now we freeze a
`_stop_summary` snapshot the instant the user aborts and flip the line to
a settled "Stopping…", then "Stopped after Xm Ys" once done.
- ChatMessage._stop_summary: abort-time (elapsed, in, out) snapshot
- _mark_stopping(): freeze + repaint every in-flight assistant on /stop
- _stopping_annotation() / _done_annotation() stopped-state branch
- DRY: _fmt_tokens() single source for `↑ N · ↓ M`, _freeze_summary()
single source for the snapshot tuple (de-dups spinner/done/stopping
and the two capture paths)
- carries the branch's existing ask_user multi-choice "; ".join collapse
---------
Co-authored-by: Shen Hao <shenhao-stu@users.noreply.github.com>
The previous /update prompt always did `git pull` + auto-merge and kept
local edits without committing, which left users with upstream changes
sitting as staged-only changes and an unintended divergent local branch.
Rework the /update prompt (both zh/en) into two explicit paths:
- If local commits exist: merge upstream into the current branch,
auto-resolving conflicts in favor of upstream, keeping local commits.
- Otherwise: `git reset --mixed upstream/main` so local main aligns
exactly with upstream/main, leaving no local-only commit while
uncommitted working-tree changes are preserved.
Never push; never create a commit in the no-local-commit path. Also adds
a concise preview + summary step. Prompt kept short per user request.
Before: /scheduler picker always showed an empty selection, so reflect/*.py
and frontends/*app*.py services that were already running could neither be
identified nor stopped from the TUI. Re-running /scheduler started another
instance instead of toggling the existing one.
This change keeps discovery in the shared slash_cmds module and wires both
TUIs through the same helpers (parity rule from tui_frontend_sop):
* frontends/slash_cmds.py
- running_service_ids(): inspect hub.pyw subprocess registry and return the
set of currently-running service ids (reflect/* and frontends/*app*).
- stop_service(sid): graceful terminate via the same registry, idempotent.
- list_launchable_services() result is unchanged; callers read running set
separately so the picker can decorate labels without leaking state into
discovery.
* frontends/tuiapp_v2.py, frontends/tui_v3.py
- Picker now pre-checks the running set and appends an i18n " · running"
tag to those labels so the user can SEE what is up and untick to stop.
- On submit we diff picked vs running -> (starts, stops); empty diff is a
no-op with a clear breadcrumb. Confirm card lists both sides.
- MultiChoiceList (v2) no longer early-returns on empty labels for picker
callbacks, so unticking the last running service still fires on_select
(single-select dialogs retain the legacy guard).
- Full bilingual i18n: scheduler.running_tag / no_change / diff.start /
diff.stop added to both en/zh tables.
Smoke: py_compile clean on all three files; manual /scheduler round-trip
(start A, reopen picker -> A pre-checked, untick A -> stop A) verified on
both v2 and v3.
The /scheduler picker previously listed only reflect/*.py (skipping just
underscore-prefixed files), so it leaked goal_mode/agent_persona and missed
the frontend apps that hub.pyw's launcher shows. It also started tasks via
`python reflect/X.py` instead of the canonical `agentmain.py --reflect`.
- slash_cmds.py: add list_launchable_services() + start_service() that mirror
hub.pyw discover_services() exactly (EXCLUDES goal_mode/chatapp_common/tuiapp,
reflect tasks via agentmain.py --reflect, frontend *app*.py incl. streamlit).
- tuiapp_v2.py / tui_v3.py: /scheduler now shows the full hub.pyw service set
(reflect + frontend) and, after the multi-select Enter, requires a second
commit-answer confirmation before anything is actually launched.
Parity verified at runtime: list_launchable_services() == discover_services().
Hook the slash_cmds bundle into tui_v3: register the commands in the
palette, add the prompt-injection dispatch and the /scheduler launcher.
Extend the in-house _show_menu with an opt-in multi_select mode
(Space toggles, Enter submits) so /scheduler can start several reflect
tasks at once, plus the matching command tips.
Hook the slash_cmds bundle into tuiapp_v2: register the commands in the
COMMANDS palette and dispatch table, add the prompt-injection handlers
(/update /autorun /morphling /goal /hive) and the /scheduler picker
(multi-select reflect tasks via the existing multi_choice picker), plus
the matching command tips.
Introduce frontends/slash_cmds.py, a shared bundle backing the new
slash commands for both Textual (v2) and curses (v3) frontends.
Provides prompt builders for /update /autorun /morphling /goal /hive
(each injects a concise, imperative directive into the agent prompt)
and the reflect-task discovery/launch helpers used by /scheduler.
Wire the existing /review handler into tgapp so Telegram users can invoke in-session code review, including help responses and streamed agent execution.