文件最后提交记录最后更新时间
fix(tui): clear TTS env var on voice off, and add TTS indicator to status bar Bug 1: /voice off in TUI mode did not clear HERMES_VOICE_TTS, leaving TTS stuck ON with no way to disable it (the voice.toggle tts handler requires voice mode to be ON). Bug 2: TUI status bar only showed 'voice on/off' without any indication of whether TTS speech output is active, because the frontend never tracked voiceTts state. - tui_gateway/server.py: clear HERMES_VOICE_TTS when voice is turned off - ui-tui/src/app/useMainApp.ts: add voiceTts state, thread setVoiceTts through voice contexts, display [tts] in status bar - ui-tui/src/app/slash/commands/session.ts: sync tts from voice.toggle response - ui-tui/src/app/interfaces.ts: add setVoiceTts to all voice context interfaces 12 天前
fix(tui): ignore late thinking deltas after completion (#31055) * fix(tui): ignore late thinking deltas after completion Prevent stale reasoning events from repainting the TUI status after a turn has already completed and the UI is idle. * test(tui): restore timers after thinking delta assertion Keep fake timer cleanup in a finally block so assertion failures cannot leak timer mode into later tests.12 天前
fix(goals): make /goal work in TUI and fix gateway verdict delivery (#19209) /goal was silently broken outside the classic CLI. TUI: /goal was routed through the HermesCLI slash-worker subprocess, which set the goal row in SessionDB but then called _pending_input.put(state.goal) — the subprocess has no reader for that queue, so the kickoff message was discarded. No post-turn judge was wired into prompt.submit either, so even a manual kickoff would not continue the goal loop. Intercept /goal in command.dispatch instead, drive GoalManager directly, and return {type: send, notice, message} so the TUI client renders the Goal-set notice and fires the kickoff. Run the judge in _run_prompt_submit after message.complete, surface the verdict via status.update {kind: goal}, and chain the continuation turn after the running guard is released. Gateway: _post_turn_goal_continuation was gated on hasattr(adapter, 'send_message'), but adapters only expose send(). That branch was dead on every platform — users never saw '✓ Goal achieved', 'Continuing toward goal', or budget-exhausted messages. Replace the dead call with adapter.send(chat_id, content, metadata) and drop a broken reference to self._loop. Tests: - tests/tui_gateway/test_goal_command.py — full /goal dispatch matrix (set / status / pause / resume / clear / stop / done / whitespace) plus regressions for slash.exec → 4018 and 'goal' staying in _PENDING_INPUT_COMMANDS. - tests/gateway/test_goal_verdict_send.py — locks in the adapter.send path for done / continue / budget-exhausted and verifies the hook no-ops when no goal is set or the adapter lacks send().1 个月前
feat(tui): subagent spawn observability overlay Adds a live + post-hoc audit surface for recursive delegate_task fan-out. None of cc/oc/oclaw tackle nested subagent trees inside an Ink overlay; this ships a view-switched dashboard that handles arbitrary depth + width. Python - delegate_tool: every subagent event now carries subagent_id, parent_id, depth, model, tool_count; subagent.complete also ships input/output/ reasoning tokens, cost, api_calls, files_read/files_written, and a tail of tool-call outputs - delegate_tool: new subagent.spawn_requested event + _active_subagents registry so the overlay can kill a branch by id and pause new spawns - tui_gateway: new RPCs delegation.status, delegation.pause, subagent.interrupt, spawn_tree.save/list/load (disk under \$HERMES_HOME/spawn-trees/<session>/<ts>.json) TUI - /agents overlay: full-width list mode (gantt strip + row picker) and Enter-to-drill full-width scrollable detail mode; inverse+amber selection, heat-coloured branch markers, wall-clock gantt with tick ruler, per-branch rollups - Detail pane: collapsible accordions (Budget, Files, Tool calls, Output, Progress, Summary); open-state persists across agents + mode switches via a shared atom - /replay [N|last|list|load <path>] for in-memory + disk history; /replay-diff <a> <b> for side-by-side tree comparison - Status-bar SpawnHud warns as depth/concurrency approaches caps; overlay auto-follows the just-finished turn onto history[1] - Theme: bump DARK dim #B8860B → #CC9B1F for readable secondary text globally; keep LIGHT untouched Tests: +29 new subagentTree unit tests; 215/215 passing. 1 个月前
feat: add inline token count etc and fix venv 1 个月前
fix(tui): mouse + keyboard text selection in the composer (#16732) * feat(tui): auto copy-on-select for transcript text Drag in the transcript already highlighted but you had to press Cmd+C to land it on the clipboard, and the highlight cleared on copy — most users never realised selection existed. Now drag-release fires copySelectionNoClear so the text is on the clipboard immediately while the highlight stays put, matching iTerm2's "Copy to pasteboard on selection" default. Esc clears. Behaviour: - Single click in the input still positions the cursor (TextInput onClick). - Single click in the transcript still does nothing destructive. - Double / triple click select word / line, then drag extends. - /copyselect [on|off|toggle] (alias /cos) flips the setting at runtime, HERMES_TUI_DISABLE_COPY_ON_SELECT=1 disables at startup, persists via display.tui_copy_on_select in config.yaml. Help overlay now lists drag-select, multi-click, and click-to-position so the gestures are discoverable. Made-with: Cursor * fix(tui): support prompt text selection gestures Add mouse drag selection and Shift+Arrow/Home/End extension inside the TUI composer so prompt text behaves like a normal editable field while keeping click-to-position and right-click paste intact. Made-with: Cursor * Revert "feat(tui): auto copy-on-select for transcript text" This reverts commit 6701288fe07a53af873e1ef53855a9618d733327. * fix(tui): allow composer selection from prompt whitespace Give the composer a one-cell mouse capture pad before the editable text. The prompt glyph/gutter still does not become selectable, but dragging from the edge now anchors at input offset 0 so users do not need to hit the first character precisely. Made-with: Cursor * fix(tui): clear selections from blank composer space Clicking blank space in the transcript or composer now clears active TUI/input selections like a normal text surface. TextInput clicks stop bubbling so cursor placement and selection gestures keep their local behavior. Made-with: Cursor * fix(tui): delegate prompt gutter drags to composer text The prompt gutter is now an input gesture region, not selectable content. Dragging from the whitespace or prompt area anchors the composer selection at offset 0, while selection highlight/copy remains limited to actual input text. Made-with: Cursor * fix(tui): move composer cursor to end on selection clear External clear actions now collapse the composer selection to the end of the input, matching normal text-field behavior after dismissing a selection. Made-with: Cursor * fix(tui): capture composer padding before prompt Add an explicit mouse capture cell over the left padding before the prompt glyph. Drags starting there now delegate to the composer input at offset 0 instead of starting terminal-level selection over the prompt chrome. Made-with: Cursor * fix(tui): avoid npm install on lockfile mtime churn Compare package-lock.json against npm's hidden node_modules lock by content instead of mtimes. Git checkouts and npm lock rewrites can make the root lockfile newer even when installed dependencies already match, causing hermes --tui to print Installing TUI dependencies on every launch. Made-with: Cursor * fix(tui): include prompt leading cell in gesture region Use the prompt box's real layout region to cover the leading whitespace cell before the glyph. The cell now participates in mouse hit testing and delegates to composer selection instead of starting terminal-level selection. Made-with: Cursor * fix(tui): widen prompt-side gesture capture band Capture a wider left-side band around the composer prompt row so drags starting in terminal gutter/padding cells are consumed and delegated to input selection, instead of triggering terminal-level selection chrome. Made-with: Cursor * fix(tui): make pre-prompt spacer non-selectable content Replace the sticky-prompt fallback Text(' ') with an empty spacer box so the visual gap remains but no literal space character is rendered/copyable before the composer prompt. Made-with: Cursor * fix(tui): capture pre-prompt spacer without shifting prompt layout Revert the widened negative-margin prompt capture band and instead capture drags on the dedicated spacer row above the prompt. This keeps prompt/text alignment stable while still delegating whitespace-start drags to composer selection. Made-with: Cursor * fix(tui): align prompt with status bar and capture full input row Drop the leading prompt column from 3 to 2 so the input first character lines up with the status bar text. Wrap the prompt+input row in a single mouse-capture box and stop event propagation from TextInput's own handlers so any drag in that row delegates to composer selection without leaking to terminal-level selection. Made-with: Cursor * fix(tui): anchor hardware cursor during composer selection When a composer selection covers a row exactly the column width, the rendered text fills the row and the terminal auto-wraps the hardware cursor to col 0 of the next row, leaving a ghost block beneath the prompt. Park the cursor at the start of the input box during selection so it can't escape the input region. Made-with: Cursor * fix(tui): hide hardware cursor during composer selection Stop fighting auto-wrap by hiding the hardware cursor outright while the composer has an active selection. This prevents both the ghost block under the prompt (cursor wrapping past the last cell) and the parked-cursor block on the first selected character. The cursor restores as soon as the selection clears or focus changes. Made-with: Cursor * chore(tui): /clean — drop dead capture-pad path, dedupe gutter handlers - TextInput: remove unused leftCaptureColumns prop and capture-pad math, drop unused mouseApi.startAt, fold mouse offset into a single offsetAt helper, share a MouseEventLite type across the four handlers. - appLayout: hoist a GutterMouseEvent type and an endInputDrag callback so the spacer/prompt/input rows share one shape. - _tui_need_npm_install: lift the runtime-only key set to a module constant, collapse nested isinstance checks, and document the mtime fallback. Made-with: Cursor * fix(tui): address copilot review on PR #16732 - Split InputSelection.clear() into clear() (cursor-preserving) and collapseToEnd() (clear + jump to end). Cmd+C copy paths keep using clear() so the cursor stays put; the blank-area click in useMainApp switches to collapseToEnd() to match the requested UX. - Spacer-row drags now force row=0 when forwarding into the input, since the spacer's vertical origin doesn't align with the input box and Ink mouse-capture keeps dispatching motion to the original target. Prompt+input row drag keeps localRow because origins match. Made-with: Cursor * fix(tui): give TextInput Box an explicit width After the /clean pass dropped the unused capture-pad math, the wrapping Box also lost its explicit width and started sizing to its rendered content. Clicks past the last character missed TextInput and fell through to the parent prompt-row Box, which collapsed the cursor to offset 0. Pin the Box back to columns so the input owns its full column span regardless of value length. Made-with: Cursor * feat(tui): double-click select-all + hide cursor on terminal blur - Track click time/offset in TextInput so a quick second click on the same offset triggers select-all. Ink's screen-level multi-click is bypassed once our onMouseDown captures, so the gesture has to be detected locally. - Extend the cursor-hide effect to also fire when the terminal loses focus, so the hollow-rect ghost most terminals draw at the parked cursor position disappears too. Made-with: Cursor * chore(tui): /clean — extract isMultiClickAt helper Pull the click-recurrence math out of TextInput's onMouseDown into a small isMultiClickAt(offset) helper so the handler reads as the gesture list it actually is (multi-click → select-all, otherwise start). Drop the redundant length>0 guard now that selectAll() already noops on an empty value. Made-with: Cursor * docs(tui): explain _tui_need_npm_install content-vs-mtime comparison Expand the docstring so future readers understand why we parse the lockfiles instead of comparing mtimes, what the optional/peer skip covers, how stale hidden-lock entries are handled, and when we fall back to mtime.1 个月前
fix(tui): clear TTS env var on voice off, and add TTS indicator to status bar Bug 1: /voice off in TUI mode did not clear HERMES_VOICE_TTS, leaving TTS stuck ON with no way to disable it (the voice.toggle tts handler requires voice mode to be ON). Bug 2: TUI status bar only showed 'voice on/off' without any indication of whether TTS speech output is active, because the frontend never tracked voiceTts state. - tui_gateway/server.py: clear HERMES_VOICE_TTS when voice is turned off - ui-tui/src/app/useMainApp.ts: add voiceTts state, thread setVoiceTts through voice contexts, display [tts] in status bar - ui-tui/src/app/slash/commands/session.ts: sync tts from voice.toggle response - ui-tui/src/app/interfaces.ts: add setVoiceTts to all voice context interfaces 12 天前
feat(tui): subagent spawn observability overlay Adds a live + post-hoc audit surface for recursive delegate_task fan-out. None of cc/oc/oclaw tackle nested subagent trees inside an Ink overlay; this ships a view-switched dashboard that handles arbitrary depth + width. Python - delegate_tool: every subagent event now carries subagent_id, parent_id, depth, model, tool_count; subagent.complete also ships input/output/ reasoning tokens, cost, api_calls, files_read/files_written, and a tail of tool-call outputs - delegate_tool: new subagent.spawn_requested event + _active_subagents registry so the overlay can kill a branch by id and pause new spawns - tui_gateway: new RPCs delegation.status, delegation.pause, subagent.interrupt, spawn_tree.save/list/load (disk under \$HERMES_HOME/spawn-trees/<session>/<ts>.json) TUI - /agents overlay: full-width list mode (gantt strip + row picker) and Enter-to-drill full-width scrollable detail mode; inverse+amber selection, heat-coloured branch markers, wall-clock gantt with tick ruler, per-branch rollups - Detail pane: collapsible accordions (Budget, Files, Tool calls, Output, Progress, Summary); open-state persists across agents + mode switches via a shared atom - /replay [N|last|list|load <path>] for in-memory + disk history; /replay-diff <a> <b> for side-by-side tree comparison - Status-bar SpawnHud warns as depth/concurrency approaches caps; overlay auto-follows the just-finished turn onto history[1] - Theme: bump DARK dim #B8860B → #CC9B1F for readable secondary text globally; keep LIGHT untouched Tests: +29 new subagentTree unit tests; 215/215 passing. 1 个月前
fix(tui): refresh scroll height at cached bottom 29 天前
feat(tui): /model and /setup slash commands with in-place CLI handoff - hermes-ink: export withInkSuspended() + useExternalProcess() that pause/resume Ink around an arbitrary external process (built on the existing enterAlternateScreen/exitAlternateScreen plumbing) - tui: launchHermesCommand(args) spawns the hermes binary with inherited stdio, with HERMES_BIN override for non-standard launches - tui: /model and /setup slash commands invoke the CLI wizards in-place, then re-preflight setup.status and auto-start a session on success — no more exit-and-relaunch to finish first-run setup - setup panel now advertises those slashes instead of only pointing users back at the shell 1 个月前
fix(tui): handle timeout/error subagent statuses in /agents (#26687) Accept delegation timeout/error statuses in the TUI subagent model, normalize unknown status strings defensively, and harden /agents overlay rendering/sorting so unknown statuses cannot crash glyph/color lookup. Add regression tests for live event normalization and disk snapshot replay.20 天前
fix(tui): surface verbose tool details (#30225) * fix(tui): surface verbose tool details Emit redacted structured verbose args/results to the TUI so /verbose verbose can show full tool detail without reopening stdout, and fail closed if redaction is unavailable. Salvages #29011. Co-authored-by: helix4u <4317663+helix4u@users.noreply.github.com> * fix(tui): address verbose detail review Label verbose tool failures as errors, cover forced verbose reasoning, and avoid new diff type warnings from the redaction regression tests. * fix(tui): bound verbose tool payloads Cap verbose tool detail text before emitting JSON-RPC events and preserve verbose results on inline diff completions. * fix(tui): align termux argv test with gc flag Update the stale TUI launch expectation so the Termux freshness path matches the current direct Node argv. --------- Co-authored-by: helix4u <4317663+helix4u@users.noreply.github.com>14 天前
chore(tui): remove dead branch cleanup code - drop unused TUI helpers, test-only layout scaffolding, and stale public debug exports - remove an unused profiler import and trim test-only coverage for deleted helpers 1 个月前
perf(ui-tui): narrow overlay subscriptions to focused selectors Subscribe overlay components to computed theme/session selectors instead of the full UI store so unrelated UI state updates trigger fewer overlay renders. 30 天前
feat(tui): delete queued message while editing with ctrl-x / cancel with esc Today there's no way to remove a queued message — ↑ loads it for edit, ctrl-K dispatches the head, but a draft you no longer want stays put forever. ctrl-C just clears the composer and exits edit mode without touching the queue. Two new bindings, both gated on queueEditIdx !== null so they're inert when the user isn't pointing at a queue item: - ctrl-X — delete the queue item being edited, clear composer, exit edit mode. "cut" matches the mental model and doesn't collide with any existing binding. - esc — cancel the edit (composer clears, item stays in queue). Mirrors ctrl-C's existing behavior so muscle memory has two paths. Header line now reads queued (3) · editing 2 · ⌃X delete · esc cancel when in edit mode, so the affordance is discoverable without /help. The /help hotkey table also gets a Ctrl+X entry. ctrl-C is intentionally unchanged: it should never destroy queued content. Cancel is non-destructive (esc / ctrl-C); only ctrl-X removes the item. 1 个月前
feat(tui): mouse_tracking DEC mode presets (salvage of #26681) (#30084) * feat(tui): make display.mouse_tracking pick which DEC modes to enable Previously the boolean flag was all-or-nothing across modes 1000+1002+1003+1006. Inside tmux, mode 1003 (any-motion) makes every mouse cross of the prompt row fire a clipboard probe that surfaces as "No image in clipboard" — sometimes dozens in a row. Disabling tracking entirely killed scroll-wheel scrolling too, since tmux's own scrollback is preempted by the alt-screen TUI. display.mouse_tracking (and /mouse <preset>) now accepts `off | wheel | buttons | all in addition to the legacy booleans. wheel` is 1000+1006: scroll wheel + click only, no drag, no hover — the tmux-friendly subset. buttons adds 1002 for drag-to-select. all (= legacy true) keeps the hover-driven UI (scrollbar paginate-on-hover, link mouseenter, etc.). * fix(tui): repaint + sync mouse mode when display.mouse_tracking changes Two interacting bugs left the TUI blank when display.mouse_tracking switched at runtime (config edit, /mouse <preset>): 1. AlternateScreen's effect re-runs on every mouseTracking change, tearing down and re-entering the alt screen. After re-entry, ink's frame buffers are reset by resetFramesForAltScreen() but nothing schedules the follow-up render — the alt screen sits blank until some other state change happens to trigger one. Add a scheduleRender() in setAltScreenActive's active=true branch so the freshly-entered alt screen gets a full repaint immediately. 2. setAltScreenActive early-returns when active hasn't changed, which silently drops a mouseTracking change if the cleanup→setup pair somehow leaves altScreenActive already true. Call setAltScreenMouseTracking explicitly from the AlternateScreen effect so the in-memory mode and terminal DECSET sequence stay in sync regardless of how setAltScreenActive resolved (the call is a no-op when the mode is unchanged). * fix(tui): address copilot review #4341269705 - tui_gateway/server.py: drop the never-referenced _MOUSE_TRACKING_MODES frozenset (comment #3284802434). _MOUSE_TRACKING_ALIASES already centralizes the canonical preset set via its values; the separate constant added no behavior. - tests/test_tui_gateway_server.py: update the existing test_config_mouse_uses_documented_key_with_legacy_fallback to assert the new preset strings ('all'/'off' instead of 'on'/'off', display.mouse_tracking persisted as 'all' instead of True) and add test_config_mouse_accepts_preset_strings_and_aliases covering /mouse set with wheel/click/unknown (comment #3284802453). The on/off legacy config.set return shape was an implementation detail of the boolean flag, not a stable API — the slash command, gateway help text, and docs all advertise the preset values now. - ui-tui/packages/hermes-ink/src/ink/ink.tsx: schedule a render at the end of reenterAltScreen() (comment #3284802461). Mirrors the same fix in setAltScreenActive() from ece0a2f4c — without it, SIGCONT/resize self-heal/stdin-gap re-entry leaves the alt screen blank because every caller returns early after invoking us. * fix(tui): address copilot review #4341308478 round 2 - ui-tui/src/config/env.ts (comment #3284837577): the precedence comment was misleading. Actual behavior on origin/main is HERMES_TUI_MOUSE_TRACKING (explicit override) > Termux default > HERMES_TUI_DISABLE_MOUSE legacy kill-switch. This is preserved from main; the only change here was the wrong comment that claimed DISABLE_MOUSE kept kill-switch semantics. Rewrote the comment block to document the actual precedence ladder. - tui_gateway/server.py /mouse set (comment #3284837607): replaced 'str(value or "").strip().lower()' with the explicit None idiom already used for /indicator, so programmatic callers can pass 0 / False and have them route through _MOUSE_TRACKING_ALIASES → 'off' instead of collapsing to '' and triggering the toggle path. - ui-tui/packages/hermes-ink/src/ink/components/AlternateScreen.tsx (comment #3284837620): always prepend DISABLE_MOUSE_TRACKING before enableMouseTrackingFor(...) on mount. Otherwise selecting 'wheel'/'buttons' from a state where DEC 1003 was already asserted (crash, another app, debugger) would silently leave hover on. Also unconditionally DISABLE on unmount so a crash mid-mount can't leak DEC modes back to the host shell. * chore(release): map nat@nthrow.io to @nthrow for #26681 salvage * fix(tui): drop redundant setAltScreenMouseTracking in AlternateScreen Copilot review #4341356637 (comment #3284880417). The explicit setAltScreenMouseTracking(mouseTracking) after setAltScreenActive(true, mouseTracking) was defensive paranoia added in the previous fix commit that's not actually reachable in practice: - React's cleanup always runs before the next setup, so on any prop change (mouseTracking or writeRaw) the cleanup sets active=false first. Setup then sees active was false and applies the new mode via setAltScreenActive without early-returning. - On the impossible 'active stayed true' path, the writeRaw above has already sent DISABLE_MOUSE_TRACKING + enableMouseTrackingFor(newMode) to the terminal, so the in-memory mode would lag but the visible state is already correct. Removing the redundant call means a single DEC sequence per mount. If the 'active stayed true' path ever manifests in practice, the right fix is in setAltScreenActive (track mode regardless of the active early-return), not here. * fix(tui): always DISABLE before enableMouseTrackingFor in ink.tsx Copilot review #4341379994 (comments #3284900825, #3284900840, #3284900852). Three remaining call sites in ink.tsx still re-enabled mouse tracking without first sending DISABLE_MOUSE_TRACKING: - handleResize alt-screen recovery (line ~577) - reassertTerminalModes stdin-gap re-assertion (line ~1351) - reenterAltScreen SIGCONT/resize/stdin-gap self-heal (line ~1408) For 'wheel'/'buttons' presets, omitting DISABLE leaves any externally- asserted DEC 1003 (other apps, prior crash, tmux state) still active and the hover-free preset silently has hover on. DISABLE_MOUSE_TRACKING is idempotent and safe to send unconditionally — it resets all four modes. Matches the pattern already in setAltScreenMouseTracking and the AlternateScreen mount path. * fix(tui): always DISABLE before enableMouseTrackingFor in exitAlternateScreen Copilot review #4341452823 (comment #3284959762). exitAlternateScreen() was the last call site in ink.tsx still re-enabling mouse tracking without DISABLE first. Editors (vim/nvim/less) and tmux can leave DEC 1003 hover asserted across the handoff back; without DISABLE, 'wheel'/'buttons' presets silently kept hover on after the editor quit. Now all five enableMouseTrackingFor() call sites in ink.tsx prepend DISABLE_MOUSE_TRACKING — handleResize, reassertTerminalModes, reenterAltScreen, setAltScreenMouseTracking, exitAlternateScreen. * fix(tui): add defensive default to enableMouseTrackingFor switch Copilot review #4341485231 (comment #3284979323). TS exhaustive switch returns string per the type system, but a JS caller / corrupted config / hot-reload-in-dev could reach the function with an unknown value at runtime. Without a default, that path returns undefined which then concatenates as the literal string 'undefined' into the terminal byte stream — visibly garbling output. Treat unknown as 'off' (no DEC sequences) so the worst case is silent input loss rather than a wrecked screen. --------- Co-authored-by: Nat Thrower <nat@nthrow.io>14 天前
fix(tui): allow transcript scroll + Esc during approval/clarify/confirm prompts (#26414) When an approval / clarify / confirm overlay was active, the global input handler in useInputHandlers returned for every key that wasn't Ctrl+C, which silently disabled transcript scrolling. On long threads the context the prompt was asking about often lived above the visible viewport, and being unable to scroll while answering felt like the prompt had locked the UI. ApprovalPrompt also had no Esc handler at all, so the one obvious 'abort' key did nothing during a permission prompt and the user had to memorize Ctrl+C or hunt for the deny number. Fixes: - Extract shouldFallThroughForScroll(key) (pure, exported) covering wheel scrolls, PageUp/PageDown, and Shift+ArrowUp/Down. When a prompt overlay is up and the pressed key is a scroll input, skip the early return so it reaches the existing wheel/PageUp/Shift+arrow handlers below. Plain arrows still drive in-prompt selection — they don't fall through. - ApprovalPrompt now maps Esc to onChoice('deny'), parity with the global Ctrl+C cancellation path that already invokes cancelOverlayFromCtrlC() for approvals. The bottom-of-prompt hint now advertises 'Esc/Ctrl+C deny'. - Extract approvalAction(ch, key, sel) — pure key-dispatch helper for the approval prompt, exported so the regression matrix (Esc, numbers, Enter, arrows, edge clamping, precedence) is testable without mounting Ink. Tests: - useInputHandlers.test.ts: 6 cases covering shouldFallThroughForScroll positives (wheel/PageUp/PageDown/Shift+arrows) and negatives (plain arrows, bare shift, no scroll key). - approvalAction.test.ts: 8 cases covering Esc→deny, numeric mapping, Enter, ↑↓ within bounds, edge clamping, Esc-beats-others precedence, unrelated keystrokes.20 天前
chore(tui): clean live progress lint 1 个月前
fix(tui): refresh virtual transcript on viewport resize (#31077) * fix(tui): refresh virtual transcript on viewport resize Notify scroll subscribers when ScrollBox viewport bounds change and key virtual-history updates on viewport height so resize/keyboard changes remount the tail rows instead of leaving stale spacers visible. * test(tui): isolate viewport-height remount regression Keep the resize delta below the virtual history scroll quantum so the regression test specifically depends on viewport height entering the snapshot key. * test(tui): clarify virtual history resize snapshot Update the resize regression and comments so the test specifically guards viewport-height changes in the virtual-history snapshot key. * docs(tui): clarify scrollbox subscription signals Document that ScrollBox subscribers are notified for renderer-computed viewport and content bound changes, not only imperative scrolls. * fix(tui): recompute virtual tail after width resize Avoid preserving a frozen virtual transcript range when wrapped rows shrink enough that the old tail window no longer covers the viewport. * fix(tui): preserve transcript tail across resizes Wraps + heights are column-dependent, so a width change must remeasure every row and the renderer must repaint the full viewport. - Key virtualRows on cols so React remounts wrapped rows on resize. - Snap back to bottom after sticky-mode resize once React rerenders. - Reserve a scrollbar + gap column in transcriptBodyWidth (non-termux). - Full repaint on any viewport height change (was: shrink-only). - ScrollBox scrollHeight uses deepest child bottom so sticky-bottom math can reach the real final rendered row after reflow. - DECSTBM fast-path now requires full container rect match. * feat(tui): responsive banner tiers Terminals can't scale glyphs, so the banner now picks a layout per column width instead of always rendering the full 101-col logo: - Wide (>= logo width): full ASCII logo + tagline. - Mid (>= 58 cols): centered rule banner that expands with viewport. - Narrow (>= 34 cols): brand line + tagline, both width-aware. - < 34 cols: hidden. SessionPanel surfaces model/cwd/sid inline when the hero column is hidden, so narrow layouts don't lose that info. Logo width constants derive from the art itself. * fix(tui): re-check sticky inside resize debounce + document remount Addresses Copilot review on PR #31077: - onResize now re-checks isSticky() inside the 100ms timer so manual scrolls during the debounce window don't get snapped back to tail. - Comment on the virtualRows cols-keying calls out the deliberate trade-off: per-row local state (e.g. systemOpen) resets on resize so yoga can remeasure off live geometry. The hook's scale-by-ratio path is too approximate for mixed markdown widths.12 天前
fix(tui): close slash parity gaps with CLI (#20339) * fix(tui): close slash parity gaps with CLI Route unsupported /skills subcommands through slash.exec, support /new <name> titles, and handle /redraw natively so TUI behavior matches classic CLI. Also filter gateway-only commands out of the TUI catalog while keeping /status discoverable. * fix(tui): run remaining CLI parity paths natively Forward chat launch flags into the TUI runtime and handle live-session status and skill reloads in the gateway process so TUI state no longer depends on the slash worker's stale CLI instance. * fix(tui): block stale snapshot restores Prevent snapshot restore from running through the isolated slash worker because it mutates disk state without refreshing the live TUI agent. * chore: uptick * fix(tui): guard async session title updates Handle failures from the fire-and-forget session.title RPC so title-setting errors do not surface as unhandled promise rejections while preserving session-scoped messaging.30 天前
fix(tui): always call input.detect_drop for reliable image attachment Remove frontend regex pre-check that truncated paths containing spaces, quotes, or Windows drive letters. Backend _detect_file_drop correctly handles these patterns. This fixes image attachment for common filenames like "Screenshot 2026-04-29.png". Add tests: - test_input_detect_drop_path_with_spaces: attaches image with spaces in name - test_input_detect_drop_path_with_spaces_and_remainder: remainder handling Also restored missing in test_rollback_restore_resolves_number_and_file_path. Scope: tui, vision, tests 1 个月前