Repository navigation
fix(tui): harden the TUI surface — markup, audit, rate limit, bounded context - #42
Closed
CryptoJones wants to merge 2 commits into
Closed
CryptoJones wants to merge 2 commits into
CryptoJones wants to merge 2 commits into
Conversation
… context Six fixes to the interactive TUI and the controls it shares: - Escape untrusted page titles before they reach the tab bar (#14): a title with Rich-markup metacharacters (e.g. "[2026] deals") no longer raises MarkupError mid-render or injects styling. - Route the TUI through a shared RequestGuard (#19): the bucket+audit pairing that server.py's _guard implemented inline now lives in security.RequestGuard, used by the MCP server, the TUI commands/assist path, and the background tasks — so the TUI surface finally emits audit events. - Meter background monitor probes (#16): TaskManager takes an optional guard and acquires a token per probe, so recurring watches can't make unmetered, unaudited API/web calls. Bucket enforcement is no longer per-call-site only. - Bound per-turn assistant context (#21): only the most-recent _MAX_CONTEXT_TABS open tabs are sent as context, and Store.tabs() caps + dedups by URL so a long-lived space doesn't reload (or re-upload) unbounded page text. - Run /search's ranked-results and grounded-answer calls concurrently (#32) instead of paying both round trips in series. - Derive /task's valid kinds from get_args(TaskKind) and cast (#33), dropping the duplicated literal tuple and the type: ignore. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…guard.record Audit follow-up on the TUI hardening: - _cmd_search's asyncio.gather now uses return_exceptions=True and re-raises the first failure, so if one of the two concurrent calls fails the other still completes instead of being orphaned with an unretrieved exception. - _loop only notifies on a *new* error: a persistent failure (e.g. a drained shared rate-limit bucket starving background probes) is reported once instead of spamming the same line every interval. It still keeps retrying. - server.py's tool_result audits now go through RequestGuard.record() instead of the raw AuditLogger, so the guard is the single audit chokepoint its docstring claims (and record() is no longer dead production-wise). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jun 12, 2026
Owner
Author
|
Superseded by #49 (TUI + background-task hardening, re-targeted onto v0.2.0). |
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.
Resolves the
tui/app.pyhardening cluster (six issues). The sharedRequestGuardties the rate-limit and audit controls together so every network surface goes through the same gate.#14 — untrusted titles rendered as Rich markup
_refresh_tabbarnowescape()s the page title, so a title like[2026] Best [GPU] dealsis shown literally instead of raisingMarkupErrormid-render or injecting styling.#19 — TUI emitted no audit events
New
security.RequestGuardencapsulates thebucket.acquire()+audit.record()pairing thatserver.py::_guarddid inline.server.pynow delegates to it (behavior preserved), and the TUI constructs one and calls it from_command/_assist— so TUI activity is finally audited.#16 — background probes bypassed the rate limiter
TaskManagertakes an optionalguard;_probeacquires a token per run, so recurring/taskmonitors are metered and audited like interactive calls instead of hammering the API unmetered.#21 — unbounded per-turn context
Only the most-recent
_MAX_CONTEXT_TABS(3) open tabs are sent as assistant context, andStore.tabs()now caps (LIMIT) and dedups by URL (newest wins), so a long-lived space no longer reloads or re-uploads unbounded page text. (Index/pruning follow-up tracked in #30.)#32 — sequential search round trips
/searchruns the ranked-results and grounded-answer calls viaasyncio.gather.#33 — duplicated task-kind tuple +
type: ignore/taskderives its valid kinds fromget_args(TaskKind)andcasts, removing the hand-maintained("search","fetch")tuple and the suppression.Tests (88 passed, +6)
RequestGuardaudit + rate-limit-then-raise;tabs()dedup-by-URL and limit; a probe rate-limited through the guard; tab bar escaping a bracketed title. ruff + mypy --strict clean, coverage 88.5%.Closes #14, closes #16, closes #19, closes #21, closes #32, closes #33.
🤖 Generated with Claude Code