Commit Graph

16 Commits

Author SHA1 Message Date
ethernet
05a78671a7 Merge remote-tracking branch 'origin/main' into ethie/pm-clean 2026-09-24 13:13:35 -04:00
kshitijk4poor
2ab2270b32 fix(agent): hide MiniMax-M3 Chinese reasoning tags (#43827)
Add 思考/反思/推理/推敲 to THINK_TAG_NAMES so the streaming scrubber, CLI and
gateway stream filters and the final-response stripper all hide them, and
derive the auxiliary-client reasoning strip from the same list instead of a
hard-coded copy. Bare bracketless markers (unverified) are not covered.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
2026-09-24 22:41:10 +05:30
ethernet
0f65d698d0 Merge remote-tracking branch 'origin/main' into ethie/pm-clean 2026-09-24 13:10:14 -04:00
kshitijk4poor
41c17d9b5e refactor(cli): reuse storage tool-call strip patterns in _strip_reasoning_tags
The display stripper kept byte-identical inline copies of
_STRAY_TOOL_CALL_CLOSER_PATTERN and _UNTERMINATED_TOOL_CALL_PATTERN
(same flags), which had to be edited in lockstep (#102303). Import the
compiled patterns lazily instead.
2026-09-24 22:25:37 +05:30
AdamPlatin123
dfb0a8846e fix(agent): strip stray arg tags to line boundary, not end of text
(cherry picked from commit 443db6b1b7076fcb08fb6ddacf11baa902ac544c)
2026-09-24 22:25:37 +05:30
ethernet
cb7b18431a Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	apps/desktop/src/app/settings/connections-registry.tsx
#	scripts/install.ps1
#	scripts/install.sh
#	tests/hermes_cli/test_update_autostash.py
2026-09-24 03:48:31 -04:00
teknium1
0004e3a8df refactor(cli): move the output-history row helpers out of the cli facade (#95375)
_output_history_lines / _output_history_rows are new in this PR; they belong with the
rest of the history rendering in hermes_cli/cli_render.py, not appended to cli.py.
cli.py imports them for _replay_output_history; the terminal mixin imports them from
cli_render directly.
2026-09-24 00:17:13 -07:00
teknium1
a95242c15f fix(cli): tmux with a generic TERM still counts as reflowing (#95375)
tmux configured with `default-terminal xterm-256color` gives its panes a
generic TERM plus TMUX; 96f6c69 only trusted TMUX with a tmux*/screen* TERM,
so that common setup fell to "unknown" and a shrink replayed 5-12 lines.
TMUX/STY now decide right after the TERM prefixes (a non-reflowing TERM such
as st or rxvt launched from a tmux shell still wins) and before the inherited
XTERM_VERSION. Real tmux with TERM=xterm-256color, 149-col lines:
midstream/idle/twostep/drag all 0 dup / 0 lost, drag freeze 1.0 s.
2026-09-24 00:17:13 -07:00
teknium1
e389aa4061 fix(cli): close resize-drag row loss and modal refill dups (#95375)
- Gate every paint and chrome render on the live width: a paint or render that
  would land after the width changed but before its recovery now waits for it
  (checked when it actually runs on the loop, not when it was scheduled).
- Recovery that sees the width move again keeps holding; the next recovery runs
  right after the next width change once the 0.85s hold cap passed.
- Track the narrowest width of a drag: narrowing and widening back still refills.
- Chrome floor: after a refill the chrome is drawn down to the bottom row (what
  CPR would tell prompt_toolkit), so the next count is exact when it shrank.
- Rows output scrolled while the width changed under it are repainted by the
  next refill (twice rather than truncated for good).
- A resize whose viewport holds the whole history erases from its oldest row,
  keeping the unrecorded startup banner.
- _terminal_reflows: TERM decides before inherited env (st/urxvt from tmux,
  kitty or vscode); rxvt added; vte-256color no longer matches 'vt'.
2026-09-24 00:17:13 -07:00
teknium1
9f288ba32a fix(cli): treat GNU screen as a reflowing terminal
Screen 4.09 re-wraps its window on a width change and pushes the rows that grew into its
own scrollback, like tmux (checked in real screen inside a tmux pane). STY now counts as
a multiplexer next to TMUX, checked before XTERM_VERSION (screen started from an xterm
inherits it), and TERM=screen* over ssh as reflowing.
2026-09-24 00:17:13 -07:00
teknium1
68b0f85d46 fix(cli): resize keeps each transcript row once on tmux and never loses rows elsewhere (#95375)
- Resize no longer erases the viewport and replays it: prompt_toolkit's erase is aimed
  at the chrome's top as the terminal re-wrapped it, the transcript left in place.
- Unknown terminals (TERM=xterm-256color over ssh): the rows the aim would take on a
  non-reflowing terminal are replayed, so an erase never takes more than it puts back.
  st/screen/mosh/vt/cons TERMs count as non-reflowing; LC_TERMINAL=iTerm2 as reflowing.
- Debounce 0.3 s: tmux re-wraps a pane at once but signals it at most every 250 ms.
- No app.invalidate() paints while a resize recovery is pending.
- Ctrl+L/focus refill paints synchronously (tmux copies the screen on ED from home) and
  erases every viewport row (CUP rows are 1-based).
- Bare print() in the chat/stream mixins goes through _cprint; held paints are released
  at app exit; narrower excepts.
- tmux-backed E2E cell pins all of it.
2026-09-24 00:17:13 -07:00
teknium1
38f97640c0 fix(cli): size the resize replay for how the terminal re-wraps
Live tmux probes showed the painted-width budget from the previous
commit duplicating rows on reflowing terminals: a shrink re-wraps the
rows the replay counted at their old width (Rich pads lines to the full
width, so every reply line grows) and pushes the extra into scrollback.
Terminals that keep rows in place (xterm) need the opposite count, and
the app cannot see which kind it runs in.

- A shrink replays only on a reflowing terminal (_terminal_reflows: TMUX
  yes; STY, XTERM_VERSION, TERM=linux no; unknown counts as reflowing),
  counting rows at the new width and the old chrome as re-wrapped to it.
  Elsewhere the transcript stays as the terminal truncated it and
  prompt_toolkit's own erase covers the chrome: nothing is lost.
- Ctrl+L counts rows at their painted width only where rows stay put.
- _cprint output is held from the resize signal until its recovery ran:
  a paint in between erased the re-wrapped chrome from prompt_toolkit's
  stale cursor, leaving two rows of it in the transcript that the
  replay could not account for (two duplicated lines per shrink).
2026-09-24 00:17:13 -07:00
teknium1
d4865368bc fix(cli): a resize mid-stream repaints each transcript line once
The viewport-sized replay (#95375) still duplicated rows when the width
changed while a reply streamed: the review's e2e cell showed the warmup
reply twice after two widens.

- A widen leaves the transcript alone. It wraps nothing into extra rows,
  so prompt_toolkit's own stale-cursor erase still covers the chrome, and
  no replay size is right for both terminals that keep rows in place
  (xterm) and reflowing ones. The 3J rebuild opt-in still rebuilds.
- Output is recorded in _OUTPUT_HISTORY when it is painted, not when a
  worker requests it: a replay no longer prints lines still queued for
  the app loop (they printed again right after).
- Each recorded line carries the terminal width it was painted at, and
  the replay budget counts it at that width: a line painted after the
  terminal shrank but before the debounced handler ran wraps into more
  rows than the old width says, which pushed the budget into scrollback.
- The room above the chrome is the renderer's last paint (it can be
  taller than the preferred height), not a re-fit of the future chrome.
- A line taller than the room keeps its bottom rows (_ansi_drop_cells)
  instead of vanishing.
- The two blank separator lines of every turn go through _cprint, so
  the replay budget sees them (print() through patch_stdout bypassed it).

Tests: widen skips the replay; worker prints enter the history when
painted, tagged with the width; the tail fit counts painted widths and
keeps a tall line's bottom; the width-change test asserts the fit.
2026-09-24 00:17:13 -07:00
teknium1
f748481247 fix(cli): refill only the viewport on redraw and erase it without CSI 2J
The bounded replay still stacked a copy per resize/Ctrl+L: counting logical
lines against the terminal height ignores soft-wrapped rows and the prompt
chrome, and CSI 2J makes scroll-on-clear terminals (tmux, VTE) copy the
whole visible screen into scrollback before the replay repaints it.

- _clear_prompt_toolkit_screen erases the viewport row by row (EL) when
  scrollback is kept and returns the room above the chrome: terminal rows
  minus the layout's preferred height, at the current width. The 3J
  rebuild path (display.cli_rebuild_scrollback_on_redraw) keeps 2J+3J and
  replays the whole history, which is what rebuilds scrollback.
- _replay_output_history(fit) keeps the newest lines whose wrapped height
  fits that room (_output_tail_fitting in cli_render).
- the resize path budgets against the full chrome: the status bar/rules
  hidden while the reflow settles come back and pushed the top replayed
  rows into scrollback.

Tests: the two tests that pinned CSI 2J on the keep-scrollback path now
assert the row erase; replay stubs take the fit argument; the salvaged
visible-height tests collapse into one wrapped-height invariant.

Fixes #95375
2026-09-24 00:17:13 -07:00
ethernet
c13ea774e6 refactor: make install-stamp.json the single runtime version identity
Runtime identity resolved through hermes_cli.__version__ (a static 0.0.0
on source installs, rewritten by release stamping) leaked v0.0.0 into
About, /api/health, User-Agents, and plugin compat, and source updates
showed "couldn't reach update server" because identity and channel
authority disagreed with the checkout.

Now: get_version_info() resolves install stamp -> live git -> unknown,
never pyproject metadata, never a package constant. Source checkouts
derive identity from their reachable release tag; the completion tail of
every successful install/update/historical takeover atomically rewrites
install-stamp.json with that identity; a stale source stamp whose commit
no longer matches HEAD defers to live git. ACP/TUI use derived_version
for display and base_version for protocol fields; all ~44 runtime
__version__ consumers migrated; hermes_cli.__version__ and generated
_version.py are gone; release stamping only touches the native manifests
external builders consume (nix/tauri/cargo) and passes release identity
straight into write_install_stamp.py; pyproject.toml stays inert 0.0.0.
Desktop no longer synthesizes a competing install-stamp.json: the
checkout owns its stamp, and desktop-bootstrap classification keys on
the bootstrap-complete marker. verify-bootstrap-version-stamp.py now
cross-checks the checkout's stamp (baseVersion + commit == HEAD).

Validation: 31-file focused suite green (version identity, stamping,
adoption, providers, gateway, acp/tui runtime identity, api server via
extras env, release graph); desktop tsc + 25 vitest green; real-repo
probe: base=unknown derived=git.0635606.dirty source=git on this
checkout; clean-env imports resolve entirely from this tree; windows
footgun + compat-pointer scans clean.
2026-09-23 11:41:01 -04:00
teknium1
2ca53fc386 refactor(cli): move cli.py module-level helper clusters into topical siblings; cli.py lands under 2,000 lines (#116911)
Mechanical, behaviour-neutral extraction along the existing hermes_cli/cli_*.py
pattern. Six clusters of module-level helpers leave cli.py:

- cli_config_load.py     prefill messages, reasoning/service-tier parsing, terminal
                         env mirroring, CLI defaults + YAML merge, logging bootstrap
- cli_render.py          reasoning-tag stripping, ANSI/skin colours, light-mode
                         detection, markdown/final rendering, output-history
                         recording, _cprint, ChatConsole, compact banner, panel wrap
- cli_terminal_input.py  file drops/attachments, bracketed-paste patch, extended
                         Enter keys, CPR guards, TUI input height, query images
- cli_shutdown.py        process session-id sync, exit watchdog, cleanup steps,
                         session-finalize notifications, one-shot finalize
- cli_single_query.py    kanban goal loops, exit-code mapping, quiet -q runner,
                         image routing, signal handlers, single-query mode
- cli_auto_maintenance.py state-db / checkpoint startup maintenance

Every moved body is AST-identical to the base copy modulo two mechanical
edits: cli-level names are late-bound with a call-time `from cli import ...`
(the cli_init_mixin pattern) and mutable cli state is read through `_cli().NAME`
(the gateway_service_unit `_gw()` pattern), so every monkeypatch seam on the
`cli` facade still intercepts moved code and no read snapshots stale state.
Mutable module state and every `global`-writing function stay in cli.py; the
facade re-exports the moved names in one import block.

test_bracketed_paste_timeout AST-loads the paste helper from its new home.
cli.py 3959 -> 1820 lines.
2026-09-20 20:42:17 -07:00