fix(tui): tolerate malformed terminal input bytes#629
Open
HamsteRider-m wants to merge 755 commits into
Open
Conversation
…e support - app.js: triple-guard for CJK IME composition (compositionstart/end + keyCode 229) - lib.rs + Cargo.toml: add tauri-plugin-single-instance to prevent zombie processes - tauri.conf.json: add "dmg" target and png icons for macOS bundling
# Conflicts: # frontends/desktop/src-tauri/Cargo.lock # frontends/desktop/src-tauri/src/lib.rs
…evtools by default
… instead of _turn_parts
… tips Adds to frontends/tuiapp_v2.py: - /rename <name>: rename current session; collision-checked across in-memory sessions and the persistent registry. Topbar and sidebar refresh in place. - /continue <name>: resume by stored name in addition to the existing index form (/continue 1). The picker (/continue with no arg) labels each entry with its stored name when available. - /cost [all]: per-session token usage (input / output / cache_create / cache_read), elapsed time, request count, and context-window %-left. The "all" form lists every tracked thread, including ended sessions. - ask_user picker upgrades: free-text escape hatch on every list, Esc-back navigation, single/multi-select switch from a [多选]/[multi] question hint, candidate sanitizer that handles JSON-envelope debris. - Runtime tip footer (`└ Tip: ...`) that rotates short usage hints. - Topbar status chip showing the active model + idle/busy state. - Cross-platform launcher hardening: auto-install of rich/textual on first run, and a hint when running under git-bash/mintty. New modules: - frontends/session_names.py: JSON sidecar at temp/model_responses/ session_names.json keyed by log-file basename; touched only by /rename and /continue <name>. continue_cmd is not modified. - frontends/cost_tracker.py: per-thread TokenStats, captured via monkey-patches on llmcore._record_usage and llmcore.print (Anthropic SSE's final output count only arrives via the latter). Idempotent install(). No core files touched.
…ine#397) feat(tui): /rename, /cost, /continue <name>, ask_user picker, runtime…
Adds Ctrl+T theme cycling across six palettes: a custom ga-default matching the existing github-dark scheme plus Textual built-ins (nord, gruvbox, dracula, tokyo-night, textual-light). Themes flow through Textual's native register_theme + get_css_variables, so CSS references $ga-bg / $ga-fg / etc. that resolve from the active theme; ga-default uses our exact palette while built-ins derive the missing slots (border/dim/muted/border_hi) from resolved bg/fg via a blend. watch_theme keeps the C_* module globals (Rich Text colors) and the role-color dict in sync with the active theme, invalidates message caches, and remounts the current session. Rich Markdown rendering also passes a per-theme RichTheme so code blocks, links, headings, and quotes follow the active palette instead of Rich's frozen defaults. text-muted / text-disabled are deliberately not inherited from Textual: those resolve to 'auto NN%', a Textual-only syntax Rich Text rejects.
…sdefine#399) Rephrased docstring in cost_tracker.py to clarify API-mode tracking semantics.
Display a concise startup banner with model name when running in interactive TTY mode.
The five topbar chips (session name / model / effort / tasks / clock)
were hardcoded github-dark hex values introduced in the upstream
topbar redesign, so they stayed cyan/amber/lavender on every theme.
Add five chip_* slots to _DEFAULT_PALETTE and derive them for
built-in themes from primary / secondary / warning / accent / success,
keeping five distinguishable hues across themes. watch_theme syncs
the C_CHIP_* globals on switch.
Also move the '▾ fold' indicator from the LEFT column to the RIGHT,
just before the clock. The left column packs identity + session +
running status pill into a ratio=1 ellipsis column, so a long status
('running 2m 15s') was eating the trailing fold glyph as '...'.
The clock chip was github-dark's brighter success green (#56d364), which read as more vibrant than the rest of the muted palette and clashed with C_GREEN. Use the same #7ec27e the sidebar uses for the active session marker, so all 'positive accent' greens in the chrome sit at one saturation level.
These are agent-internal metadata: <summary> already feeds the sidebar preview and the fold title, <thinking> is meant to be hidden. They were leaking into the chat body, and worse, CommonMark parses '<summary>X</summary>\n<body>' (no blank line between) as a single HTML block that swallows the following body line — so the model's actual reply disappeared from view whenever it wrote the summary tag immediately before its answer with only a single newline. Strip both tags in _render_md before passing to the Markdown parser.
Rich's divide_line treats a Chinese run as one indivisible word and bumps it whole to the next line when it doesn't fit the remaining space, wasting cells like 'AI ↩ 助手...'. Patch both rich._wrap and textual.content so Text.wrap (assistant Markdown) and Visual.to_strips (user messages) pack leading CJK chars into the current line's remainder, then fold the rest at full width. Non-CJK words keep stock behavior; code-block content remains untouched because the patch only adjusts wrap break positions.
_render_md now produces two renders: the existing narrow ANSI roundtrip for display (mouse selection still anchors via segment.style.meta) and a wide render at width=10000 that contains only structural \n. An alignment walk maps each visual line back to a position in the wide source, so SelectableStatic.get_selection extracts source text without wrap-induced newlines. The aligner handles three layouts: - 1:1 (no wrap): narrow line maps directly to wide line - N:1 wrap: hanging indent on continuation lines is stripped, and the whitespace eaten at wrap points (e.g. spaces between English words) is recovered from the wide line - centered single-line (Markdown headers): wide and narrow have different leading-pad widths; source uses the lstripped content, and narrow's pad becomes per-line indent that selection clamps over
The two CJK-aware drop-ins (divide_line for Rich/textual.content, and compute_wrap_offsets for textual._wrap/_wrapped_document) shared identical greedy-pack-then-fold core logic, written twice. Extract into _fold_chunk_cells so each call site only handles interface shape (Rich words vs Textual chunk regex, tab handling). Also extend the patch to cover textual._wrap.compute_wrap_offsets and textual.document._wrapped_document — the TextArea / Static-via-Content paths use compute_wrap_offsets, not divide_line, so the prior patch missed input-box and user-bubble wrap. Drop try/except defensiveness around the Textual patch (we target latest Textual; let real breakage surface), split _CJK_WRAP_RE into commented sub-ranges, prune WHAT comments. Verified: non-CJK output remains byte-equivalent to upstream across both divide_line and compute_wrap_offsets fixture sets; live render at width 60 packs the first line to ~98% utilization on input box, USER bubble, and AGENT bubble.
…utput extract_ui_messages now renders tool_use headers and tool_result fences into the assistant content using the same string format that agent_loop yields live, so fold_turns folds restored sessions identically to live chat. Tool-only response rounds no longer drop the user message, and Turn 1 also carries a marker so the first turn folds like the rest.
_user_text was misclassifying any prompt whose text block didn't start with '### [WORKING MEMORY]' as a real user message — but auto-continue prompts can interleave [SYSTEM TIPS], [System] regenerate triggers, [DANGER] guard injections, and similar. Treat any prompt carrying a tool_result block as auto-continue (the natural signal that this is the next round of an in-flight LLM call), and extend the injection marker list as a secondary guard for the rare tool_result-less synthetic prompts.
Per-chunk _render_md on the growing tail segment is O(chunks x turn_len) with two Markdown parses each call. During streaming, update the last text widget with plain Text; on the terminal done chunk, render Markdown once and swap, restoring code blocks, lists, and clean-copy source.
- Replace cycle-theme with ThemePicker modal: OptionList live-previews on highlight, Enter commits + saves, Esc reverts to the prior theme. - Persist UI settings in temp/tui_settings.json; restored on startup. - Theme Markdown list bullets and ordered-list numbers via markdown.list / markdown.item.bullet / markdown.item.number — previously hard-cyan.
- Sidebar Q/S preview used C_DIM (0.35 bg/fg blend); under the active row's SEL_BG the contrast collapses on tokyo-night. Switch to C_MUTED (0.55). - Drop the 'theme: X' system message — the picker provides the visual feedback already, the extra row is noise on every preview tick.
…non-default themes - watch_theme no longer remounts the whole transcript: just clears render caches and repaints chrome. Long-history switches go from multi-second Markdown reparse to ~150ms. Already-mounted message text stays in the old palette until the next natural reflow (fold toggle, resize, new turn, session switch). - _markdown_rich_theme gains a colored flag. Non-ga-default themes render Markdown in plain fg with only structural emphasis (bold/italic/code backdrop) until per-theme accents are tuned — avoids text blending into the background on themes whose accent slots collide with sel_bg.
Non-ga-default themes fight Rich's frozen accent expectations — code spans on sel_bg, link colors, heading hues end up either washed out or invisible on themes like tokyo-night / textual-light. Until each is hand-tuned, collapse heading / list / code styles to fg + muted so body text stays readable; inline code also drops its sel_bg background under ga-default (was hiding text in some terminals).
_META_TAG_RE blindly stripped the tags so inline-code samples like '`<summary>x</summary>`' became bare empty backticks. Split the text on fenced/inline code regions and only strip in the gaps.
The earlier split-around-code-spans helper was incomplete (no ~~~ fences, no indented blocks, no multi-line inline code) and over-engineered. The underlying CommonMark hazard is only the start-of-line HTML block that swallows the next line — mid-line tags are inline HTML Rich already renders as text. Anchoring the regex to line start (^ + MULTILINE, optional <=3 leading spaces) handles the original swallow case while leaving every code-region variant intact. One known gap: a <summary> tag on the first column inside a fenced block still matches.
…ine#403) feat(tui v2): theme system + Markdown/streaming/continue fixes
修复个人微信前端在 headless 容器内无法登录
* feat(tui): workspace 项目模式 + @ 文件引用补全(v2/v3) - workspace: /workspace 设/off/picker、junction 复用 project_mode 记忆、 per-session(v2)/进程级(v3)、/continue 恢复、session map 持久化绑定状态 - @ 补全(completion-only): 子序列模糊 + path-like 目录补全(~/ / ./ C:\)、 提交期 @路径作为普通文本交 agent - v3 filterable 焦点链 picker(输入框作焦点环对象、free_input) - 新增共享模块 at_complete.py / workspace_cmd.py;核心零改动 * feat(tui): @ 路径绝对化 + 默认根=temp + 未绑显示完整路径 - 默认根 os.getcwd() → agent 工作目录 <GA根>/temp(与 file_read/code_run 一致、不随启动 cwd 飘) - 提交期 @相对 → @绝对(display 保留相对短路径),让 agent file_read(相对自身 cwd)找得到 - 未绑 workspace 时候选显示完整路径(根不直观);索引忽略 model_responses 会话日志噪音 - 启动预热 temp 索引 + candidates_for 惰性兜底(任何根首次访问自动建)
…lsdefine#606) - macljqCtrl.py: macOS implementation of mouse/keyboard/screenshot/window enumeration mirroring ljqCtrl API, plus AX accessibility control tree (AXElements/AXFind/AXPress) as the macOS equivalent of UIA - computer_use.md: add section 3 documenting the macOS platform branch - .gitignore: whitelist memory/macljqCtrl.py
… prevent UI freeze ESC cancel sent session/cancel RPC but never cleared the busy flag, leaving the UI stuck if the server-side cancelled event never arrived. Also clear busy on all sessions when the bridge closes so pending poll loops exit cleanly. Co-authored-by: dilong888 <dilong888@users.noreply.github.com>
v3 的 _do_restore 此前只把历史载入内存 backend.history,不碰当前进程日志。native 后端逐轮只把新增消息 append 到 model_responses_{pid}.txt,恢复出的旧历史从不写入当前日志——续聊只追加增量,下次 /continue 扫到的本进程文件就只剩续聊部分,旧历史丢失。
对齐 v2 _do_continue_restore:恢复前 reset_conversation(快照+清空当前日志),恢复后 copyfile(源日志 → 当前日志),续聊便接在完整历史之后。
…d, AXClick)Feat/macos ax enhancements (lsdefine#611) * feat(memory): enhance macOS AX control (resolve_pid fix, enabled field, AXClick) - _resolve_pid: fix bundle_id branch (used un-imported AppKit), 3-tier match (bundle id > app name exact > substring) - AXElements: collect 'enabled' field (SOP: check disabled before click) - AXClick: AXPress-first then fallback to physical-coord Click, honest success判定 via pixel diff - AXFind: add enabled_only filter, refactor if-forest into _hit helper * docs(ljqCtrl): tighten macOS AX guidance, drop boilerplate & app-specific detail - collapse 5-line cross-platform import boilerplate to one line - generalize control-identifier tip (remove app-specific identifiers)
Annotate config block to guide new GA instances: enumerate candidate mykey.py variable names and experimentally probe which config works; only print var/field names, model, apibase host/path, status codes and error types (never full dict or apikey/token); include a config format example. Also flag that apibase/endpoint may differ per provider/proxy at the two request-building sites.
…conflict) (lsdefine#617) /continue N 默认改为「原地续」:接管原会话日志、后续轮次写回同一文件,取代旧的 镜像复制(supersedes b4356c7 的 mirror 方案),不再每次续接都 fork 新会话、日志增殖。 - 空闲会话 → 直接原地接管;被活进程占用 → 弹窗确认后才复制一份续。 - 每会话出生持锁(temp/model_responses/.locks/<logid>.lock),整进程共用一个心跳 线程每 5s touch mtime、30s 无心跳判死可接管;atexit 干净释放,崩溃/强杀靠超时兜底。 - 切走/新对话不再「快照+清空」(原 _snapshot_current_log 因 logid≠pid 实为死代码, 从未生成过快照),旧日志原样留作空闲会话,新对话铸新 logid。 - list_sessions 新增 exclude_log,修正 exclude_pid=getpid 失效导致当前会话出现在 自己列表里的问题。 - continue_cmd 仅新增函数,reset_conversation/restore/handle 未改动 → 其他前端 (IM/qt/streamlit 等)行为完全不变。 - 改动仅限 continue_cmd.py + tui_v3.py + tuiapp_v2.py;rewind/worldline 逻辑不在本 PR。
Update WeChat group 21 QR code image. Co-authored-by: AspasZhang <AspasZhang@users.noreply.github.com>
…ne#621) In-place /continue retargets agent.log_path to the restored file, so the reset bind `_bind_workspace(None)` ran during restore was persisting session_ws_set(path, "") — erasing that session's own workspace mapping to "" (read back as "explicitly off") before we read it, which also short- circuited the log-scan fallback. The continued session never re-entered its workspace. Add a `persist` flag to `_bind_workspace`; the restore-time reset passes persist=False so it only refreshes in-memory state. The session→workspace map is now written solely by explicit /workspace, /workspace off, and a successful restore.
…sdefine#614) On macOS the screenshot path lacked a counterpart to win32's ClientToScreen. Detecting a target inside a cropped GrabScreen(bbox) image and feeding the in-image (px,py) straight to Click silently mis-clicks because the crop-origin offset is dropped. - add CropToScreen(bbox, x, y): in-crop coords -> absolute physical coords (pure crop-origin add, no scaling) - GrabScreen docstring: warn about the crop-origin trap and not to hand-roll screencapture -R (it takes logical points) - Click 0%-pixel-change warning now names the common causes - ljqCtrl_sop.md: add macOS subsection mirroring the win32 trap note
Co-authored-by: octo-patch <octo-patch@github.com>
lsdefine#624) Co-authored-by: Shen Hao <shenhao-stu@users.noreply.github.com>
* fix(continue): preserve session workspace on in-place restore In-place /continue retargets agent.log_path to the restored file, so the reset bind `_bind_workspace(None)` ran during restore was persisting session_ws_set(path, "") — erasing that session's own workspace mapping to "" (read back as "explicitly off") before we read it, which also short- circuited the log-scan fallback. The continued session never re-entered its workspace. Add a `persist` flag to `_bind_workspace`; the restore-time reset passes persist=False so it only refreshes in-memory state. The session→workspace map is now written solely by explicit /workspace, /workspace off, and a successful restore. * fix(tui_v3): support Home/End keys for line start/end PTK delivers Home/End as raw VT sequences in KeyPress.data, which _esc_repl swallowed (only arrows were decoded). Map \x1b[H/[F/[1~/[4~ and SS3 \x1bOH/\x1bOF to internal bytes 0x07/0x14, extend _ESC_RE to match SS3 as whole sequences, and add jump-to-line-start/end handlers in _keys. Home no longer collides with Ctrl+A select-all. * feat(worldline): integrate checkpoint-tree rewind into tui_v2 - tuiapp_v2.py: /rewind durable inline picker + /worldline three-pane checkpoint tree (tree UI inlined; colors follow the v2 theme) with a per-node diff viewport; conv-only /rewind restores via the persistent store. - worldline.py (new): UI-agnostic backend — RewindStore (persistent checkpoint tree + content-addressed blobs), reconcile, restore_plan, node_diff, native-log projection. - continue_cmd.py: gated worldline support — list_sessions(rewind_root=), continue_inplace/copy(allow_empty=); default-off, other UIs byte-identical. - tui_v3.py: status line shows concrete model id (unrelated minor tweak). * feat(worldline): /rewind reuses the restore-mode picker (conv/code/both) After picking a turn, /rewind now opens the same RestoreModeScreen as /worldline to choose conversation / code / both, then restores via the persistent store — making /rewind a true lite view of /worldline (linear list, no tree/diff) over the identical restore backend.
- stapp.py: add loop mode (auto-inject prompt after each reply) - stapp.py: replace DOM-based idle monitor with st.fragment timer - stapp.py: i18n for all new UI text and LLM prompts - launch.pyw: remove inject/get_last_reply_time/idle_monitor, keep only paste hook - agentmain/ga: raise max_turns 80->180, DANGER thresholds accordingly - remove obsolete tool_usable_history.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
frontends/tuiapp_v2.pyand avoid touching site-packages.Why
tui_v2could appear to lose sessions when the Textual input thread crashed on a malformed terminal byte (UnicodeDecodeError). The sessions were still present, but the UI could die before recovery/listing stayed usable.Testing
/Users/maygo/Projects/GenericAgent/.venv/bin/python -m py_compile frontends/tuiapp_v2.pytuiapp_v2withsys.path[:0]=['frontends', '.']b'\\xb5'decodes toU+FFFD; non-UTF-8 decoder path remains unchangedcontinue_cmd.list_sessions()returned 802 sessions in the local environment