Skip to content

fix(tui): strip terminal controls from status-line text - #1

Open
lee3Q wants to merge 3482 commits into
devfrom
fix/tui-status-line-terminal-controls
Open

lee3Q wants to merge 3482 commits into
devfrom
fix/tui-status-line-terminal-controls

Conversation

@lee3Q

@lee3Q lee3Q commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Summary

Part 3/4. Parent scope: four terminal sinks for repository text. This PR addresses sink 3, status-line header text (renderStatusLine). Preceding sinks: sink 1 (code cells, Yeachan-Heo#7338), markdown cells (Yeachan-Heo#7331).

Finding / reproduction

renderStatusLine in packages/coding-agent/src/tui/status-line.ts renders tool headers, commands, file paths, and execution status. It applied theme.fg() directly to title, description, badge.label, and meta without control sequence filtering.
When an untrusted repository file path, command argument, or execution output carries terminal control escapes (OSC 52 clipboard access, OSC 0 window title manipulation, CSI terminal commands like screen clear or cursor move, DCS, C1 controls) or embedded newlines (\r\n / \n), these raw control bytes passed directly to the operator terminal, or broke single-line status row layout.

Change

  • Sanitize options.title, options.description, options.badge.label, and options.meta using sanitizeDisplayLine from @gajae-code/utils.
  • Flattens embedded CR/LF sequences to spaces, stripping C0/C1 control characters, ANSI escape sequences, and bidirectional format controls.
  • Preserves theme styling, bracket format, status icons, and clean text.

Scope

path status reason
renderStatusLine (title, description, badge, meta) covered status-line header sink
code cells merged (Yeachan-Heo#7338) sink 1
markdown cells merged (Yeachan-Heo#7331) sink 2
hyperlinks next part sink 4

Contract

Display only. Status line layout remains strictly single-row. Standard letters, digits, spaces, punctuation, bracket formatting, and theme colors remain intact. Embedded control codes (OSC, CSI, DCS, C1) and newlines are sanitized before styling.

Base vs head

input base head
OSC 52 / OSC 0 / CSI / DCS in status line emitted to terminal stripped; safe text preserved
Multi-line description (\n / \r\n) broke single-line layout flattened to single row with spaces
Safe title, description, badge, meta styled styled identically

Verification

  • Added packages/coding-agent/test/tui/status-line-terminal.test.ts:
    • status line strips terminal controls from title, description, badge, and meta
    • flattens newlines in description and meta to preserve single-line rendering
    • preserves ordinary text, brackets, and status icon styling
    • Property test: status line strips arbitrary terminal controls, fc.property seed 71291, numRuns 64
  • Added changelog fragment: packages/coding-agent/changelog.d/status-line-terminal-controls.md

Risk classification

  • low-risk — ordinary fix/maintenance; sanitized display line input for status header rendering.

Oreochococukie and others added 30 commits October 25, 2026 01:24
…chan-Heo#7218)

* fix(mcp): keep a shared API-key token on its own pool connection

The shared pool treated the binding label as the Authorization identity, so a second token reused the first connection.

* test(mcp): read Authorization only on http pool configs
…n-Heo#7217)

* fix(discord): bind nonce threads only when this bot owns them

A copied public starter marker could rebind a session to another member's thread. Reconciliation now requires the bot author, the configured parent, the bot owner, and the starter message itself.

* fix(discord): name the matched thread as owned

The local is the bot-owned thread record from the owner check.

* test(discord): pin nonce ownership to a fixed-seed property

Generated author, owner, parent, and starter combinations must match an independent reference, including a later marker echo and an outsider placed first.
…o#7107)

A bounded polling delay only guessed when the starting shell was owned. Acknowledge entry into the held readiness callback, while racing execution to propagate setup failures without masking the original disposal assertion.

Lore-id: 6286b012
Constraint: preserve all disposal assertions and real contract waits
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: isolated full-file baseline and after; both rerun-each20; package checks; actual early-factory rejection
Not-tested: full suite; compiled CLI; measured performance
…7085)

The Sonnet default case does not belong in DeepSeek registration coverage. Move it without changing assertions, and retain every unowned DeepSeek case.

Lore-id: 6286sc02
Constraint: retain all18cases and all121expectations
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: focused baseline and after; full AI checks; independent critic PASS
Not-tested: full suite and comparable CI wall time
An escaped dollar is a literal, not an interpolation. Provenance compares Bun's decoded text, so a different operator shell value stays and a matching project path does not.
… fails

A refused marker write skips publication, so path presence in an older config.yml must not retire the newer source.
An unconnected RecordingTracer only proved that its own array starts empty.
The optional-chain checks also passed when the observed agent_end was absent.
Require the terminal event before checking both disabled payload values and
narrow the title to what the case actually observes.

Lore-id: 6286b131
Constraint: preserve all 16 cases, both field checks, and the real stream drain
Confidence: high
Scope-risk: narrow
Reversibility: trivial
Tested: whole file 16 pass/80 assertions and same-process whole-file20 320/1600 before and after
Tested: Agent package check; old missing-observation control passes and new guard fails, then restored
Not-tested: real OpenTelemetry exporter or live provider behavior
…tdout-alias

fix(coding-agent): reject assignment aliases of planning output streams
…Yeachan-Heo#7222)

* fix(bus): scrub own notification frame fields before dispatch

Inherited prototype text was rendered into Discord and Slack bodies. Copy own fields onto a null-prototype object and scrub those strings with the existing crash redactors before adapters truncate, while leaving ask choices unchanged.

* test(bus): keep a lone slash in a notification frame

* fix(bus): scrub replayed notification frames before provider dispatch

A Slack generation rollover keeps the ask mark on the new root so distinct option paths stay readable, and a legacy Discord or Slack frame body is scrubbed at dispatch or refused when it is not text.

* test(bus): pin the replayed Discord frame to its endpoint generation

* test(bus): separate replay scrub cases from paths that stay readable

* fix(bus): keep ask marks and redact opt-out through provider dispatch

A Slack root that waits out another owner's lease keeps the ask mark, and an explicit redact opt-out stays literal at Discord and Slack dispatch, including replay.

* test(bus): separate the opted-out Slack path from its replay

* test(bus): refuse a replayed Discord notification without a string body
…7214)

* ci: always plan package check for every touched package

The planTargetedTasks function had a logic error where changes to a source
file with a mapped test would skip the package-level check job entirely.
When mappedTests.length > 0, the code would continue to the next path
without adding the owning package's check task.

This caused PR Yeachan-Heo#7194 and ~14 other PRs to omit package checks despite having
changed files, leaving only individual test files and coding-agent smoke tests
in the plan. The probepark approval gate then reported NEED_LOCAL due to
missing lint+typecheck coverage.

Fix: Move the owner package check addition before the mapped test check,
so the check is always included for any package with changed files.

Root cause: scripts/ci-dev-affected.ts:1217
The continue statement at line 1217 was skipping the owner check when
mapped tests existed.

Tested: Updated ci-dev-affected.test.ts to verify package checks are
always included, both for files with mapped tests and without.

Constraint: must preserve existing test-file selection behavior
Confidence: high
Scope-risk: medium
Reversibility: trivial

* ci: keep the planner change minimal and assertions exact

Plan the owning package check before the mapped-test early exit instead of
reordering the fallback branch, and keep exact toEqual expectations for the
directly-mapped source case.

---------

Co-authored-by: clawdbot <clawdbot@anthropic.com>
…han-Heo#7224)

DeepSeek and DeepInfra regressions exercised the same registration
surfaces from separate issue-named modules. Give both complete families
one feature owner and share their existing imports without mixing
credential and routing contracts into unrelated catalog tests.

Lore-id: 6286b133
Constraint: preserve all seven cases and 31 assertions verbatim, including both environment restorations
Constraint: retain provider-specific defaults, existing OpenAICompat typing, compatibility mappings, and issue attribution
Rejected: move into preset or coalescing tests | neither owns the complete registration boundary
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: complete original and consolidated suites, same-process whole suites x20, and canonical AI checks
Not-tested: live authenticated providers, transport compatibility, Windows, full suite, or CI speedup
…content

Repository lines echoed in those tool results could close the frame early.
MockModel implements the model interface directly; its legacy accessor only
returns the same instance. Passing that instance aligns force-abort coverage
with the current API without changing its scripts, gates, or assertions.

Lore-id: 6286b134
Constraint: preserve all 11 abort cases, defaults, lifecycle, and fault-sensitive waits
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: complete force-abort suite; same-process whole-suite x20; agent package check
Not-tested: live provider transport; full repository suite; Windows
…eachan-Heo#7226)

A session resumed under the same id while the Telegram daemon kept running
never got a topic or a single notification: the session_closed tombstone
matched every attachment of that logical session, so replay admission
rejected the successor and dropped its subscription without a log line.
The tombstone originally fenced the exact closed endpoint; b245458 widened
it to the whole logical session when endpoint keys left the topic binding.

Record the closing attachment key with the tombstone so it fences that
attachment's late frames only, and log replay-recovery refusals.

Lore-id: 5f0c2a71
Constraint: the closed attachment and its late frames must stay fenced
Rejected: clear the tombstone when a successor binds | leaves the closed attachment unfenced in the same window
Rejected: fence by SDK event generation | every host process currently reuses generation 1
Confidence: high
Scope-risk: narrow
Reversibility: clean
Tested: closed-then-resumed attachment rebinds a fresh topic and delivers (red before, green after)
Tested: notifications/telegram/router/chat-daemon suites (65 files); coding-agent tsc; biome
Not-tested: live Telegram network delivery
…an-Heo#7225)

* fix(coding-agent): ignore repository subagent model overrides

A repository settings file must not replace the model the task tool uses for a subagent. User config and runtime overrides still apply.

* fix(coding-agent): limit the project model-override claim

State that only the project-layer agent model record is ignored, and cover that merge with a fixed-seed property test.

* fix(coding-agent): type the operator model comparison

Compare the merged selector as a string so the property test typechecks.
…iew-fence

fix(coding-agent): frame hashline and ast-edit previews as untrusted content
…abort-direct-model

test(agent): use direct mock models in force-abort coverage
Yield parameter descriptions copied agent output-schema prose into tool-definition text. Truncate that prose first, then mark it untrusted, and leave structural schema checks in place.
…e file

The base module does not export the schema text cap, and truncation cases are attack tests rather than inputs the base already accepted.
Literal keywords were also skipping user fields with those names, and closer escaping could grow a framed excerpt past the byte cap. Walk schema maps separately and shrink the excerpt until the finished fence fits.
A project config must not choose where copy and paste send text. User config and the clipboard CLI flags still can.
Repository viewport and diff text reached the model without the untrusted-content frame.
…is not an object

A null, array, or scalar clipboard value in a project file replaced the whole user clipboard object.
Debugger and program text reached the model without the same frame used for other tool output, so a close tag in that text could end the frame early.
A workspace rule body could close the interrupt or tool-reminder frame and place its own text outside that frame.
lee3Q and others added 30 commits October 28, 2026 03:40
…ay-properties

fix(utils): respect explicit EditorConfig tab width and unset
…-started-promises

fix(utils): observe started promise rejection on pre-abort
…al-preservation

fix(utils): preserve literal content inside Markdown fences
Update getTimeRangeConfig to check for own properties in TIME_RANGE_TO_CONFIG.
The stats dashboard now correctly handles unknown range names by falling back to the last 24 hours instead of treating them as inherited members.
Add buildModelPreferenceSeries function to process time series data and generate model preference series.
The Model Preference chart now correctly handles model name collisions with reserved property names and provider-qualified labels.
fix(tui): preserve pre-yank text when cycling combining characters
…nge-fallback

fix(stats): apply fallback for inherited time range names
…ence-series

fix(stats): preserve arbitrary model preference series labels
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants