文件最后提交记录最后更新时间
fix(tui): clipboard copy on linux/wayland probeLinuxCopy and copyNative in osc.ts await execFileNoThrow for wl-copy / xclip / xsel. Those tools double-fork a daemon that holds the system selection live, and the daemon inherits stdio pipes from spawn(stdio: 'pipe'). Node's 'close' event only fires when stdio is fully closed → the daemon keeps the pipes open → 'close' never fires → the await leaks past the timeout (kill(SIGTERM) on an already-exited child is a no-op, daemon survives). Result: linuxCopy cache stays undefined permanently, the actual copy never runs, ctrl-c silently does nothing on wayland/x11. Reproduced in isolation, confirmed across wl-copy and a daemonization-shaped fixture. Fix: add resolveOnExit option to execFileNoThrow. When set, the promise settles on the immediate child's 'exit' event instead of waiting for stdio drainage. Wired into both the probe and the actual copy spawns for every clipboard tool (pbcopy, wl-copy, xclip, xsel, clip). Tests: 5 new vitest cases covering daemon-style child handling, non-zero exit propagation, timeout behavior, and double-resolve guard. The forever-hang case is committed as it.skip with documentation so a reviewer can verify the bug by hand. 16 天前
Merge pull request #27971 from NousResearch/austin/fix/goal-statusbar fix(tui): keep /goal verdict out of compact status row18 天前
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 个月前
feat(cli): add /update slash command to CLI and TUI (#23854) * feat: add /update slash command to CLI and TUI * test(cli): add Python tests for /update slash command Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): address Copilot review for /update slash command Route classic CLI /update through prompt_toolkit modal confirmation and defer relaunch to the main-thread cleanup path after app.exit(). Tighten Y/n semantics, add Python wrapper and catalog coverage tests, and assert /update stays visible in the TUI command catalog. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): address review feedback on /update command - Replace raw input() with _prompt_text_input_modal in _handle_update_command to avoid EOF/hang/keystroke-leak races with prompt_toolkit's stdin ownership - Fix confirmation logic: only proceed on recognized affirmative aliases (y/yes/1/ok); cancel on everything else including empty string, typos, and unrecognized input — matches all other [Y/n] prompts in the codebase - Route relaunch through main-thread shutdown path: set _pending_relaunch and return False from process_command so process_loop triggers app.exit(); run() then calls relaunch() after prompt_toolkit has restored terminal modes and after cleanup — safe on both POSIX (execvp) and Windows (subprocess+exit) - Fix misleading docstring in test_update_command.py: the Vitest only covers the TypeScript slash handler that emits code 42, not the Python wrapper branch that acts on it - Rewrite tests to use SimpleNamespace pattern (like test_destructive_slash_confirm) so _prompt_text_input_modal can be stubbed directly - Add Python test for _launch_tui exit-code-42 → relaunch branch in main.py Agent-Logs-Url: https://github.com/NousResearch/hermes-agent/sessions/f6da68cf-e7b1-4b7a-aed6-3d4b0f523bdb Co-authored-by: austinpickett <260188+austinpickett@users.noreply.github.com> * fix(cli): polish test fixtures for /update command - Remove unused _prompt_text_input from SimpleNamespace stub - Use pytest.fail sentinel in managed-install guard test to catch unexpected modal invocations Agent-Logs-Url: https://github.com/NousResearch/hermes-agent/sessions/f6da68cf-e7b1-4b7a-aed6-3d4b0f523bdb Co-authored-by: austinpickett <260188+austinpickett@users.noreply.github.com> * chore: re-trigger CI after Copilot review fixes Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: austinpickett <260188+austinpickett@users.noreply.github.com>18 天前
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.21 天前
fix(tui): prefer raw text over Rich-rendered ANSI in TUI message display (#17111) turnController.recordMessageComplete and recordMessageDelta both prioritised payload.rendered over payload.text. payload.rendered is the Rich-Console output tui_gateway builds for terminals that can't render markdown themselves; the TUI already renders markdown via <Md>. Two real bugs follow: 1. **Final answer garbled when display.final_response_markdown: render is set** (#16391). Raw ANSI escape sequences pass through into the React tree and the user sees overlapping coloured text instead of their answer. 2. **Streaming silently drops content.** Per-delta rendered is an *incremental* Rich fragment. The previous code did this.bufRef = rendered ?? this.bufRef + text, which on every tick replaced the whole accumulated buffer with the latest mid-sequence ANSI fragment. Long replies arrived truncated and looked half-painted — easy to miss as "model is being terse" instead of a client bug. Fix: * recordMessageComplete now prefers payload.text, falling back to payload.rendered only when the gateway elected not to send any. * recordMessageDelta always accumulates text; rendered is ignored on the streaming path entirely (Ink does its own markdown render via <Md> / streamingMarkdown.tsx). Tests: * prefers raw text over Rich-rendered ANSI on message.complete — the assistant message reflects raw markdown, not ANSI. * falls back to payload.rendered when text is missing — preserves the legacy "no text, only ANSI" path used by some adapters. * always accumulates raw text in message.delta and ignores rendered — pre-fix code would have made this assertion fail because each delta overwrote the buffer. Validation: npm run type-check clean, npm test --run 392/392 pass.1 个月前
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. 1 个月前
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 个月前
fix(tui): respect voice.record_key config (supersedes #19028, #19339) (#19835) * fix(tui): respect voice.record_key config instead of hardcoded Ctrl+B Classic CLI loaded voice.record_key from config.yaml and bound the prompt-toolkit handler dynamically (cli.py paths). The new TUI hard- coded Ctrl+B everywhere — isVoiceToggleKey (input handler), /voice status ("Record key: Ctrl+B"), and /voice on ("Ctrl+B to start/stop recording"). A user who set voice.record_key: ctrl+o (or any other key) saw the documented config silently ignored — only Ctrl+B worked, the displayed shortcut lied about it. Wire the configured key end to end through the existing channels: * **Backend** (tui_gateway/server.py): voice.toggle action=status AND action=on/off responses now include record_key, sourced from config.get('voice', {}).get('record_key', 'ctrl+b'). * **Backend types** (ui-tui/src/gatewayTypes.ts): ConfigFullResponse now exposes config.voice.record_key and VoiceToggleResponse carries record_key so the TUI can both bind and display it. * **Frontend parser/formatter** (ui-tui/src/lib/platform.ts): parseVoiceRecordKey() accepts ctrl+b / alt+r / cmd+space and the common aliases (option, cmd, win, …); falls back to the documented Ctrl+B for empty / multi-character / malformed input so a typo never silently disables the shortcut. formatVoiceRecordKey() renders for status text. isVoiceToggleKey now takes a parsed ParsedVoiceRecordKey argument; the hardcoded ch === 'b' is gone. Default arg keeps existing call sites back-compat. * **Hydration** (ui-tui/src/app/useConfigSync.ts, useMainApp.ts): startup config.get full already runs; extract cfg.voice.record_key from it, parse, push into a new voiceRecordKey state, and forward to the input handler ctx (InputHandlerContext.voice.recordKey). Mtime-poll path also re-applies the parsed key so a hand-edit of config.yaml takes effect the next tick — matches existing behaviour for display options. * **Input handler** (ui-tui/src/app/useInputHandlers.ts): isVoiceToggleKey(key, ch, voice.recordKey) so the configured binding fires. * **Slash command** (ui-tui/src/app/slash/commands/session.ts): /voice status and /voice on use formatVoiceRecordKey on the response's record_key instead of the hardcoded label. Tests: * parseVoiceRecordKey covers ctrl/alt/cmd/super aliases, multi-char rejection, and empty fallback. * formatVoiceRecordKey covers the doc examples (Ctrl+B, Ctrl+O, Alt+R, Cmd+B). * isVoiceToggleKey regression: ctrl+o configured → only o matches, not b; alt+r matches both alt-bit and meta-bit encodings (terminal protocol parity); omitted-arg call still binds Ctrl+B for back-compat. Full TUI suite (555 tests) passes; tsc --noEmit clean. Fixes #18994 Co-authored-by: asheriif <ahmedsherif95@gmail.com> * fix(tui): support named-key tokens in voice.record_key (space, enter, …) Reviewer caught that the round-1 parser in #18994 rejected every multi-character token, so a config value like ctrl+space (which the CLI happily binds via prompt_toolkit's c-space rewrite in cli.py) silently fell back to the documented Ctrl+B default — re-introducing the same false-shortcut bug the PR was meant to fix, just at a different surface. Add explicit named-key support that mirrors what the CLI accepts: * space (alias: spc) → matches ch === ' ' * enter (alias: return, ret) → matches key.return * tab → matches key.tab * escape (alias: esc) → matches key.escape * backspace (alias: bs) → matches key.backspace * delete (alias: del) → matches key.delete ParsedVoiceRecordKey gains an optional named field; ch holds either a single char (back-compat) or the canonical named token, and the runtime matcher dispatches on named before checking the modifier shape. Aliases collapse to one canonical name so ctrl+esc and ctrl+escape behave identically. Unrecognised multi-character tokens (e.g. ctrl+spcae typo, or unsupported keys like ctrl+f5) still fall back to the Ctrl+B default rather than silently disabling the binding — keeps the "typo never silently kills the shortcut" guarantee. Tests: * parseVoiceRecordKey parametrised over every named token + each alias variant. * New isVoiceToggleKey cases for space (ch-based match), enter (key.return), tab, escape, backspace, delete, including modifier-mismatch negatives. * formatVoiceRecordKey renders named keys in title case (Ctrl+Space, Ctrl+Enter). * Existing fall-back-to-Ctrl+B contract preserved for empty input AND unrecognised multi-char tokens. Full TUI suite: 559/559 pass; tsc --noEmit clean. Refs #18994 (round-1 review feedback) Co-authored-by: asheriif <ahmedsherif95@gmail.com> * test(tui): assert voice.toggle returns configured record_key Salvage the backend regression from #19339 — asserts voice.toggle action=on AND action=status responses carry the configured voice.record_key end-to-end through _load_cfg(). Keeps the CLI→TUI parity contract visible in the Python test suite alongside the existing frontend parser/matcher/formatter coverage from #19028. * fix(tui): address Copilot review on #19835 voice.record_key wiring Five tightenings on the parser + matcher + hydration surface, all caught by the Copilot review on the PR — each one turns a silent false-fire or display/binding skew into a deterministic behaviour. * **isVoiceToggleKey ctrl branch was too permissive for named keys.** The doc-default macOS Cmd+B muscle-memory fallback (isActionMod(key) on top of key.ctrl) fired for every configured key, so bare Esc — which hermes-ink reports with key.meta on some macOS terminals — triggered ctrl+escape, and Alt+Space / Alt+Tab triggered ctrl+space / ctrl+tab. Gate the fallback to the literal ctrl+b binding so any custom chord requires the real Ctrl bit. * **Alt branch guarded against Ctrl/Cmd co-press.** Without this, Ctrl+Alt+<letter> and Cmd+Alt+<letter> also fired alt+<letter>. * **Dropped the meta modifier variant and its alias.** In hermes-ink key.meta is Alt on xterm-style terminals and Cmd on legacy macOS ones, so a literal meta+b config displayed as Cmd+B while matching Alt+B — exactly the kind of false shortcut the PR was meant to remove. cmd / command now collapse onto super (kitty-style key.super, with a macOS key.meta fallback) and render as Cmd+B. Unknown modifier tokens fall back to the documented Ctrl+B default rather than silently coercing to Ctrl. * **Slash-command display/binding skew.** /voice status and /voice on rendered from the fresh gateway record_key response, but useInputHandlers() still bound the old key until the next 5s mtime poll. Thread setVoiceRecordKey through SlashHandlerContext.voice and push the parsed spec into frontend state on every response so text and binding stay consistent. * **Test coverage for the two paths Copilot flagged.** Added vitest coverage for (a) the three-case /voice slash output in createSlashHandler.test.ts and (b) the applyDisplay → voice.record_key hydration + omit-setter back-compat paths in useConfigSync.test.ts. Plus regression cases for every false-fire scenario above. Suite: 575/575 green, tsc --noEmit clean. * fix(tui): address Copilot round-2 review on #19835 Three tightenings on the surface introduced in the round-1 fix: * **/voice tts reset custom bindings to Ctrl+B.** The tts branch of voice.toggle omitted record_key from its response, so the frontend's r.record_key ?? 'ctrl+b' coerced a user's custom binding back to the default on every TTS toggle. Two-sided fix: the backend now includes record_key on the tts branch (parity with status/on/off), and the slash handler only pushes frontend state when the response actually carries record_key — belt-and-suspenders against any future branch forgetting to include it. * **super+b / win+b / cmd+b displayed "Cmd+B" on Linux and Windows.** formatVoiceRecordKey rendered mod === 'super' as Cmd universally, which told non-mac users the wrong modifier to press even though isVoiceToggleKey matched the right event bits. Gate the label to isMac so non-mac renders Super+B. * **control+b / ctrl + b lost the macOS Cmd+B fallback.** _isDefaultVoiceKey keyed off parsed.raw — so semantically-equal aliases of the documented default dropped into the strict branch even though they bind Ctrl+B. Compare on the parsed spec (mod + ch + named) instead. Coverage added: Linux Super+B rendering (and macOS Cmd+B), control+b / ctrl + b accepting the Cmd+B fallback on darwin, /voice tts without record_key not clobbering cached binding, and a backend regression asserting every voice.toggle branch carries the configured key. Suite: 579/579 TUI vitest green, 2/2 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot round-3 review on #19835 Three classes of robustness issue caught on the second pass — all revolve around malformed YAML tipping parseVoiceRecordKey or _voice_record_key into a crash instead of the documented fallback. * **Parser crashed on non-string YAML scalars.** config.get full returns raw yaml.safe_load output, so voice.record_key: 1 or voice.record_key: true in a hand-edited config would hit .trim() on a number/bool and throw, breaking startup and every mtime re-apply. Accept unknown at the signature, guard with typeof raw !== 'string', and fall back to the default. * **Backend blew up on non-dict voice:.** Same YAML hazard on the gateway side: voice: true / voice: cmd+b left _load_cfg().get("voice") as a bool/str, so .get("record_key") raised AttributeError and took every voice.toggle branch down with it. Centralised the lookup in a single _voice_record_key() helper that isinstance-guards both voice and record_key and falls back to ctrl+b. * **Multi-modifier chords silently dropped extras.** The previous validator only checked the first modifier token, so ctrl+alt+r silently parsed as ctrl+r and cmd+ctrl+b as super+b — a typo bound a different shortcut than the user configured. Reject multi-modifier spellings outright; the classic CLI only supports single-modifier bindings via prompt_toolkit's c-x / a-x rewrite, so this matches CLI parity. Coverage added: * parseVoiceRecordKey fallback on 1 / true / null / undefined / {}. * parseVoiceRecordKey fallback on ctrl+alt+r / cmd+ctrl+b / alt+ctrl+space. * test_voice_toggle_handles_non_dict_voice_cfg exercises every non-dict voice: shape (bool, str, None, int, list) and asserts each falls back to record_key: 'ctrl+b'. Suite: 581/581 TUI vitest green, 3/3 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot round-4 review on #19835 Four final corners of the voice.record_key surface: * **Bare-char configs silently coerced to ctrl+<key>.** A config like voice.record_key: o / space / escape fell through to the default mod = 'ctrl' and silently bound Ctrl+O, while the classic CLI's prompt_toolkit would bind the raw key (no rewrite) — so the two runtimes silently disagreed on what "o" means. Require an explicit modifier; bare-char configs fall back to the documented Ctrl+B default. * **Reserved ctrl+<letter> bindings would never fire.** useInputHandlers() intercepts ctrl+c (interrupt), ctrl+d (quit), and ctrl+l (clear screen) before the voice check runs, so those configs would be advertised in /voice status but the advertised shortcut never actually triggers push-to-talk. Added _RESERVED_CTRL_CHARS at parse time so the user gets the documented default instead of a dead shortcut. (alt+c, cmd+l, etc. are not intercepted and stay usable.) * **_load_cfg() root itself may be a non-dict.** _voice_record_key() isinstance-guarded the voice subkey but not the root — a malformed config.yaml that collapsed to a scalar/list at the top level (config.yaml: true or []) would still raise on .get("voice"). Added the top-level guard too so every malformed shape falls back to ctrl+b. * **Stale header comment on isVoiceToggleKey.** The doc-comment still claimed "On macOS we additionally accept the platform action modifier (Cmd) for the configured letter" even though the implementation gates the Cmd fallback to the documented default only. Rewrote to match. Coverage added: * parseVoiceRecordKey fallback on bare chars (o, b, space, escape). * parseVoiceRecordKey fallback on ctrl+c / ctrl+d / ctrl+l; positive case for alt+c / cmd+l still usable. * Backend test_voice_toggle_handles_non_dict_voice_cfg now exercises 5 non-dict shapes at the YAML root too. Suite: 583/583 TUI vitest green, 3/3 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot round-5 review on #19835 Three follow-ups on the voice matcher's modifier + shift discipline: * **super branch falsely fired on Alt+<key> / bare Esc on macOS.** isVoiceToggleKey accepted isMac && key.meta as a Cmd fallback for the super modifier — but hermes-ink sets key.meta for plain Alt/Option AND for bare Escape on some macOS terminals. A cmd+b config silently fired on Alt+B; cmd+space on Alt+Space; cmd+escape on bare Esc. Drop the fallback and require the literal key.super bit. Legacy- terminal users who need Cmd should upgrade to a kitty-protocol terminal or bind alt+X explicitly. * **Shift bit was never checked.** The parser rejects multi- modifier configs like ctrl+shift+tab, but the runtime matcher didn't check key.shift — so ctrl+tab also fired on Ctrl+Shift+Tab and alt+enter on Alt+Shift+Enter. Early-return on key.shift === true so the runtime only fires the exact chord the user configured. * **Test leaked HERMES_VOICE=1 into later tests.** voice.toggle action=on writes to os.environ directly (CLI parity, runtime-only flag); ``test_voice_toggle_returns_ configured_record_key`` dispatched action=on without letting monkeypatch take ownership of the var first. Any later test that read voice mode in the same Python process could inherit a stale enabled state. Added ``monkeypatch.setenv("HERMES_VOICE", "0")`` up front so monkeypatch restores the original value at teardown. Coverage added: * cmd+b / cmd+space / cmd+escape do NOT fire on key.meta-only events on darwin. * ctrl+tab / alt+enter / ctrl+o reject matches when key.shift is held; sanity cases without Shift still fire. Suite: 585/585 TUI vitest green, 3/3 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot round-6 review on #19835 Three classes of modifier-discipline tightening + one config-surface honesty fix: * **Default ctrl+b Cmd fallback leaked Alt+B.** The default's macOS Cmd+B muscle-memory path used isActionMod(key), which returns key.meta || key.super on darwin. hermes-ink also reports plain Alt as key.meta, so Alt+B silently fired the default binding. Replaced with strict ``isMac && key.super === true`` — kitty-style Cmd+B still works, Alt+B correctly rejected. Legacy-terminal mac users (Terminal.app without CSI-u) now get raw Ctrl+B only; the documented default still works everywhere. * **ctrl / super branches accepted extra modifier bits.** The parser rejects multi-modifier configs like ctrl+alt+o, but the runtime matcher was permissive — ctrl+o fired on Ctrl+Alt+O / Ctrl+Cmd+O, and super+b fired on Cmd+Alt+B / Ctrl+Cmd+B. Added strict ``!key.alt && !key.meta && key.super !== true on ctrl, and !key.ctrl && !key.alt && !key.meta`` on super, so the runtime only fires the exact chord the parser would let you configure. * **Dropped cmd / command aliases.** They parsed to super and rendered as Cmd+X, but legacy macOS terminals report Cmd as key.meta (same signal as Alt), so a cmd+o config was advertised as working but never actually fired on Terminal.app-without-CSI-u. That recreated the "displayed shortcut does not work" problem this PR was meant to remove. Users who want the platform action modifier spell it super / win — that matches the unambiguous key.super bit, and kitty-style macOS terminals render it as Cmd+X via platform-aware formatter. Coverage updated: * Default ctrl+b no longer fires on Alt+B via key.meta leak; raw Ctrl+B and kitty-style Cmd+B still fire. * ctrl+o rejects Ctrl+Alt+O / Ctrl+Cmd+O / Ctrl+Meta+O chords. * super+b rejects Cmd+Alt+B / Cmd+Meta+B / Ctrl+Cmd+B chords. * cmd+b / command+b / meta+b all fall back to the documented default at parse time (joined the ambiguous-mac-mod rejection class). * Round-2 expectations that asserted cmd+b parsed as super and accepted key.meta on darwin updated to reflect the new stricter contract. Suite: 588/588 TUI vitest green, 3/3 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot follow-up on wire typing + escape precedence Two follow-ups from the latest Copilot pass: * **Config wire typing honesty (gatewayTypes.ts)** config.get full forwards raw yaml.safe_load() output, so voice.record_key can be any scalar/container when hand-edited. Typing it as string suggests a normalized contract that the backend does not guarantee and makes unsafe callers more likely. Change ConfigVoiceConfig.record_key to unknown with an explicit comment that callers must normalize at runtime. * **Escape-based voice bindings were swallowed before voice check** useInputHandlers() handled key.escape for queue-edit cancel and selection clear before isVoiceToggleKey(...), so configured ctrl+escape / alt+escape / super+escape chords were advertised but never toggled recording in those UI states. Add an early escape+voice check before generic Esc handlers so escape-based voice bindings win when configured, while plain Esc behavior remains unchanged. Also updated PR #19835 description text to remove stale cmd/command alias claims and match the current parser contract. * fix(tui): pass configured voice shortcut through TextInput layer Thread the live parsed voiceRecordKey into TextInput so configured voice.record_key chords bubble to useInputHandlers instead of being consumed as editor input. This removes the last hardcoded Ctrl+B pass-through in the composer path while preserving existing global control chord behavior. * fix(tui): require explicit alt bit for escape-based alt chords Hermes-ink reports bare Escape as meta=true+escape=true on some terminals, so a configured alt+escape binding was firing on bare Esc. Require an explicit key.alt bit when the configured named key is escape so plain Esc stays plain Esc; kitty-style alt+escape still fires. * fix(tui): harden voice.record + TextInput paste + super-mod reserved list Three round-7 Copilot follow-ups on #19835: - voice.record start handler used _load_cfg().get('voice', {}).get(...) without shape checks, so malformed YAML (bool/scalar/list) returned 5025 instead of using VAD defaults. Centralized _voice_cfg_dict() helper and type-guarded silence_threshold/silence_duration with numeric fallbacks. - TextInput pass-through check moved above paste/copy handling so configured voice chords (ctrl+v / alt+v / cmd+v) beat the composer's paste/copy defaults. - parser now also rejects super+{c,d,l,v} — on macOS those are copy/exit/clear/paste and would be advertised in /voice status but never actually toggle recording. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(tui): round-8 Copilot review — allow ctrl+x, gate super reservations to macOS, preserve voice key on transient RPC failure Three round-8 Copilot follow-ups on #19835: - Revert ctrl+x addition to _RESERVED_CTRL_CHARS (landed via Copilot Autofix commit 731ec86): ctrl+x is only claimed during queue-edit (queueEditIdx !== null), so voice works the rest of the session and matches CLI ctrl+<letter> parity. - Gate super+{c,d,l,v} reservation to isMac. Linux/Windows TUI globals key off Ctrl, so kitty/CSI-u super+<letter> configs don't collide on non-mac and should stay usable. - applyDisplay() now skips setVoiceRecordKey when cfg is null so one transient quietRpc() failure after a config edit doesn't clobber the cached binding back to Ctrl+B until the next successful poll. New coverage: - parseVoiceRecordKey preserves ctrl+x on linux - super+{c,d,l,v} rejected on darwin, allowed on linux - applyDisplay(null, ...) leaves voiceRecordKey untouched * fix(cli,tui): normalize voice.record_key aliases across CLI + TUI for parity Round-9 Copilot review on #19835: TUI accepted control+/option+/opt+/super+/win+ aliases but the classic CLI only rewrote literal ctrl+/alt+ before handing to prompt_toolkit, so a TUI-valid config silently bound a different (or no) shortcut in the CLI. - Added normalize_voice_record_key_for_prompt_toolkit() in hermes_cli/voice.py with a single alias table (ctrl/control/alt/option/opt → c-/a-). - Wired it into all three cli.py sites (_enable_voice_mode hint, _show_voice_status display, and the prompt_toolkit binding in _register_voice_handler). - /voice status display now renders control+x as Ctrl+X and option+x as Alt+X (canonical casing) to match TUI formatVoiceRecordKey. - super/win/windows are intentionally left unchanged: prompt_toolkit has no super modifier, so the CLI will reject them loudly at startup rather than silently binding Ctrl+B. Documented this split at both the TUI _MOD_ALIASES comment and the CLI normalizer docstring. - Added tests covering ctrl/control/alt/option/opt mapping, case-insensitivity, non-string fallback, empty-string fallback, and super/win pass-through. * fix(cli): port TUI parser contract into CLI voice.record_key normalizer Round-10 Copilot review on #19835. hermes_cli/voice.py's normalize_voice_record_key_for_prompt_toolkit() previously did blind substring replacement with no trim/validate step, so the CLI diverged from the TUI parser on: - whitespace ('ctrl + b' -> 'c- b' instead of 'c-b') - typoed named keys ('ctrl+spcae' passed through as 'c-spcae' and prompt_toolkit would reject at startup) - bare-char configs ('o' should fall back, not pass through as 'o') - multi-modifier chords ('ctrl+alt+r') - reserved ctrl chars ('ctrl+c/d/l') - unknown modifiers ('meta+b' / 'shift+b') - named-key aliases ('return'/'esc'/'bs'/'del' not collapsed to prompt_toolkit canonicals) Port the TUI parser contract into Python (_VOICE_MOD_ALIASES, _VOICE_NAMED_KEYS, _VOICE_RESERVED_CTRL_CHARS) so one config value binds the same shortcut in both runtimes. Also added format_voice_record_key_for_status() shared between the PTT hint and /voice status display. Non-string scalars (voice.record_key: true / 1) now surface as 'Ctrl+B' instead of the raw scalar — /voice status no longer advertises a shortcut that can never bind. Tests: 29/29 in test_voice_wrapper.py, including 11 new regressions covering whitespace, named-key aliases, typos, bare-char, multi-modifier, reserved ctrl, unknown mods, non-string fallback, and formatter contract. * fix(cli): shape-safe voice config read + graceful super/win fallback Round-11 Copilot review on #19835. Two remaining cross-runtime gaps: 1. load_config().get('voice', {}) still assumed voice was a dict, so a hand-edited voice: true / voice: cmd+b at the top level raised AttributeError before the voice UI could start. Added voice_record_key_from_config(cfg) to hermes_cli/voice.py that isinstance-guards both the root and the voice subkey. All three cli.py read sites (_enable_voice_mode hint, _show_voice_status, PTT binding) now use it. 2. The CLI normalizer previously passed super+/win+/windows+ through unrewritten so prompt_toolkit would reject them loudly at startup — but that crash was a worse UX than a silent fallback. Normalizer now returns c-b for those spellings, and the PTT binding site logs a warning so users see why their TUI-only shortcut isn't binding in the CLI. Coverage: 34/34 in tests/hermes_cli/test_voice_wrapper.py (5 new cases for voice_record_key_from_config + malformed-root + malformed-voice + extractor/normalizer composition). * fix(cli): self-audit cleanup — remaining voice-config shape safety + doc drift Self-review of the voice.record_key change set turned up four remaining items Copilot would very likely flag next round: 1. cli.py _voice_start_continuous still read load_config().get('voice', {}).get('silence_threshold') without an isinstance guard, so a hand-edited voice: true / voice: cmd+b (non-dict) raised AttributeError on VAD recording start. Shape-safe coerce the voice dict and numeric-guard silence_threshold/silence_duration. 2. cli.py _enable_voice_mode's auto_tts check had the same bug — fixed with the same isinstance guard. 3. hermes_cli/voice.py module comment on _VOICE_MOD_ALIASES still said super/win/windows 'pass through unchanged and prompt_toolkit's add() call loudly rejects them at startup'. Round 11 changed the normalizer to silently fall back to c-b with a warning at the binding site; updated the comment to match. 4. ui-tui/src/lib/platform.ts header comment had the same stale 'CLI will loudly reject them at startup' claim; updated to 'falls back to the documented default and logs a warning'. No behavior change on the code paths already covered by test_voice_wrapper.py; the two cli.py fixes are defensive against malformed YAML that previous rounds already hardened in tui_gateway/server.py but missed in the classic CLI. * fix(cli,tui): round-12 Copilot review — alt-collide on mac, bool-in-int guards, voice UI hardcodes, mtime-reload test Five round-12 Copilot review items on #19835: 1. platform.ts: hermes-ink reports Alt as key.meta on many terminals; isActionMod on darwin accepts key.meta as the action modifier. So alt+c/d/l get claimed by isCopyShortcut / isAction('d')/'l') before the voice check. Reject those configs at parse time on macOS only (non-mac keeps them usable). 2. cli.py: four remaining hardcoded 'Ctrl+B' sites in voice-facing UI (_get_voice_status_fragments status bar, _voice_start_recording hints, _get_placeholder composer text) were still lying about non-default configs. Added self._voice_record_key_label() shared helper and wired it into all three sites. 3. server.py + cli.py: bool is a subclass of int, so isinstance(silence_threshold, (int, float)) accepted True/False from malformed YAML and forwarded 1/0 to the VAD engine. Exclude bool explicitly so boolean typos fall back to the documented 200 / 3.0 defaults. 4. useConfigSync.ts: extracted the config.get-full fetch+apply body into a shared hydrateFullConfig() helper. Both the initial hydration and mtime-reload paths now use it, so the polling/RPC wiring is exercised by direct unit tests (4 new cases: fresh apply, reapply on new value, transient RPC failure preserves cache, back-compat without voice setter). 5. Added alt+{c,d,l} rejection regressions on darwin + allow on linux, and bool-leak regressions for both silence_threshold and silence_duration in tests/test_tui_gateway_server.py. Suite: 602/602 TUI vitest, 38/38 backend voice tests, typecheck + lints clean. * fix(cli): cache voice record-key label at binding time + status-bar coverage Round-13 Copilot review on #19835. _voice_record_key_label() was reading live config on every render, which caused two problems: 1. prompt_toolkit registers the push-to-talk binding once at session start (@kb.add(_voice_key)); the binding does NOT re-read config. Editing voice.record_key mid-session would switch the status-bar / placeholder / recording-hint label to the new shortcut while the actual keybinding stayed on the startup chord — reintroducing the display/binding drift this whole PR is fighting. 2. Hot render path: during recording the UI is invalidated every 150ms, so re-loading + deep-merging config on every call added avoidable UI overhead. Fix: cache the label at the same site that registers the prompt_toolkit binding via new set_voice_record_key_cache(raw_key). _voice_record_key_label() now just returns the cached value (falls back to 'Ctrl+B' before startup). Status/placeholder/hint are always in sync with the live binding; no config reload per render. Also added 4 regression cases to tests/cli/test_cli_status_bar.py: configured ctrl+<letter> renders in both wide and compact status bars, configured named key (ctrl+space) renders in the recording hint, pre-startup absent cache falls back to Ctrl+B, and malformed configs (bool True) fall through the formatter to Ctrl+B. Suite: 60/60 test_cli_status_bar + test_voice_wrapper, typecheck + lints clean. * fix(cli): route /voice on + /voice status through startup-pinned label; mac alt+cdl parity Round-14 Copilot review on #19835. All three comments legit: 1. _enable_voice_mode still formatted label from live load_config() — mid-session config edit would make /voice on announce the new shortcut while the prompt_toolkit binding stayed the startup chord. Use self._voice_record_key_label() (cached at binding time, round-13) so /voice on cannot drift from the live binding. 2. _show_voice_status had the same bug — /voice status reported live config instead of the pinned startup binding. Fixed the same way. 3. CLI normalizer accepted alt+c/alt+d/alt+l even though the TUI parser rejects them on macOS (Copilot round-12 — hermes-ink reports Alt as key.meta, isActionMod on darwin accepts it, collides with isCopyShortcut / isAction). Added _VOICE_RESERVED_ALT_CHARS_MAC = {c,d,l} gated to sys.platform == 'darwin' so a shared config like option+c falls back to c-b on both runtimes on macOS; non-mac still binds a-c. Coverage: 4 new tests in test_voice_wrapper.py covering mac alt+cdl rejection, linux alt+cdl allowed, option/opt alias forms, and mac-specific exclusions for other alt letters. 62/62 in voice wrapper + status bar suites. --------- Co-authored-by: Tranquil-Flow <tranquil_flow@protonmail.com> Co-authored-by: asheriif <ahmedsherif95@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>1 个月前
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 个月前
Merge pull request #28829 from NousResearch/bb/tui-no-history-truncation fix(tui): render full assistant text in scrollback (no history truncation)17 天前
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.1 个月前
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 个月前