feat: statusline v2, agentic orchestration hardening, and TUI polish - #118
Conversation
Add a configurable footer: [tui.statusline] selects which segments render (cwd, git, flags, context, tokens, model) and an optional external command whose first stdout line shows in the footer. The command runs without a shell, refreshes on a cadence with an explicit task lifecycle, and fails closed (timeout/non-zero/spawn failure -> segment omitted, warn once). Defaults reproduce the previous footer exactly; PYTHINKER_STATUSLINE=0 disables customization per session.
…l-up RunAgents foreground batches now execute children concurrently, bounded by background.max_running_tasks so a large batch cannot fork-bomb the session. Results keep request order and a crashing child reports its own error entry instead of aborting siblings. Batch results also gain batch_risks/batch_blockers blocks: RISKS and BLOCKERS sections from completed child reports are deduplicated and attributed per reporter, giving the orchestrator cross-child findings without re-parsing each report body.
Switch the input-area slash-command highlight to a clear blue with bold in both themes, and harden review findings on the branch: - Treat 'recoverable' as terminal in TaskOutput retrieval_status and map it to a failure tool status instead of error, so orphan-recovered agent tasks no longer report not_ready/timeout forever. - Mark agent tasks 'starting' via the store's guarded update_runtime instead of a bare read->write outside the lock. - statusline: suppress ProcessLookupError around kill+reap in the timeout and cancellation paths so a dead process can't mask errors. - tests: fail fast when the statusline pid file is never written; fix pyright errors in slash-highlight and statusline-slash tests.
- RunAgents approval summary no longer asserts children will be deferred; capacity is rechecked at launch time and the wording now reflects that. - aggregate_findings: an unclosed code fence in a malformed child report no longer swallows the RISKS/BLOCKERS sections that follow it. - tests: assert the statusline fallback table by its rows instead of the incidental word 'Usage'; split the path-like highlight case into two named tests; rename and strengthen the none-placeholder rollup test and add an unclosed-fence regression; guard the command-clear test against a trivially-true assertion; assert disabled statusline ignores segment overrides at render time.
Extract reusable lessons from the session and persist them via the Memory tool. The prompt enforces pattern-over-instance extraction, one pattern per lesson, and a strict quality bar; an optional focus argument narrows the review. Wire handshake snapshot updated for the new command.
…anager The non-blocking poll counter lived on the TaskOutput tool instance, so a rebuilt toolset reset the streak and the escalating STOP-polling hint never fired. Track it on BackgroundTaskManager (shared across role copies) and clear it on blocking waits or terminal retrieval. Also fold the /learn CHANGELOG and slash-command docs entries that belonged with 905cc97.
Bugsink triage showed the error stream dominated by conditions pythinker cannot fix: expired or invalid credentials (401/403), rate limits (429), provider outages (5xx), offline DNS, abandoned OAuth flows, and MCP servers lacking optional methods. New is_expected_error() classifies an exception by walking its cause chain; report_handled_error() and the asyncio crash handler tag OTel events expected= and skip Sentry capture for those, while sys.excepthook stays ungated — an expected error escaping to process death is still a missing-handler bug. Source checkouts now report deployment.environment=development, with the version read from the live pyproject.toml instead of a stale editable dist-info snapshot, so hacking on the tree never pollutes the production release stream. A ripgrep binary that cannot execute (wrong arch) now degrades Grep to the Python fallback instead of failing the tool.
Clean-code-guard scan of the branch. /statusline matched verbs with startswith, so "/statusline commands" parsed as command with argument "s" and persisted a junk external command; verbs now require an exact match. The capped-output proc.kill() in StatusLineCommandRunner was the only kill site not wrapped in suppress(ProcessLookupError), turning a process-exit race into a spurious refresh failure. aggregate_findings stripped a leading "-"/"*" from non-bulleted lines, mangling findings that start with CLI flags like "--force"; only real "- "/"* " bullet markers strip now. Also a stale live-view docstring. Regression tests cover the parser and roll-up fixes.
Follow-up to the /learn command: the bundled-assets pin list must name the new prompt file or the datas test fails against the real tree.
The engineering best-practices profile was opt-in via /best-practices only. The default system prompt now ships a condensed always-on subset — smallest-complete-change ownership, environment detection from artifacts, blast-radius mapping, never-invent-APIs with dependency-name verification, dirty-worktree and git safety, honest testing without verification gaming, debugging method, migration and concurrency conformance, secrets and boundary parameterization, idempotent operations with a three-failures escalation rule, and answer-shape guidance — inherited by the root agent and every subagent role. The full /best-practices profile is expanded to match: five new sections (operating principles, context gathering, design and implementation, version control, agent operational discipline) and sharper rules throughout, while the /bp section filter keeps working against the new headings. Phrase pins updated for both prompt assets; the reference docs and changelog note the new default.
Add StatusLineContext, GitInfo, StatusFlags, ProviderLimits, SegmentSpec, ZoneSplit, SEGMENT_REGISTRY, and split_zones to the statusline module as pure data/dispatch layer for statusline v2. Renderers are stubs (_not_rendered) pending Tasks 5-7. Existing resolve_segments / StatusLineLayout / DEFAULT_STATUSLINE_SEGMENTS left untouched.
Implement five line-1 segment renderers replacing _not_rendered stubs. Add get_statusline_colors import at top level (no circular risk: theme.py does not import statusline.py). Every glyph has an ASCII fallback; plain style emits empty style strings.
Add session_cost_usd, total_input_tokens, and total_output_tokens to StatusSnapshot (with defaults so all existing constructions stay valid). Accumulate cost in PythinkerSoul._session_cost_usd via estimate_cost_usd on every step-loop and compaction LLM call; surface all three on status().
Add RateSampler (sliding-window tokens/sec) and parse_shortstat (git --shortstat output → (added, removed)) as pure, self-contained helpers in statusline.py, with full test coverage.
…test coverage - Refactor agent YAML specs (ask, code_reviewer, coder, debug, debugger, implementer, judge, plan, planner) with streamlined system prompts - Add telemetry instrumentation: OpenTelemetry metrics and otel integration - Update test coverage for agent specs, telemetry, and prompt handling - Enhance release workflow for PyPI distribution - Restructure system.md for clarity and maintainability
Default agent specs (code_reviewer, security_reviewer, review, judge, verifier, scout, explore) and the shared system prompt now emit findings as the fenced report JSON block defined in base Section 8 instead of free-form paragraphs. Soul agent/toolset and the shell visualize blocks handle the new format; core agent-spec, subagent builder, and streaming content block tests updated to match.
Compact prompt wording in the default agent specs, adjust config and OTel resource setup to match, and sync the pinned tests across the core, telemetry, and statusline suites. Update task tracking notes.
Address the findings that survived validation of the deep-scan review: - subagents/usage: attribute a child once per finding even when its report repeats the same RISKS/BLOCKERS bullet (order-preserving) - background/manager: drop _nonblocking_polls entries when a task is seen terminal in reconcile, so unpolled finishes don't leak counters - config: stop pointing scope-lock errors at a "corresponding PYTHINKER_*" env var — no locked path has an ENV_FIELD_MAP override - slash: register /statusline in shell mode like its sibling settings commands; reject non-finite (nan/inf) budget values - visualize: cancel and reap mid-turn slash-command tasks when the live view exits so they can't outlive their output surface - statusline: add missing -> None on StatusLineCommandRunner.__init__ - agents/default: document that the bare `subagents:` key in coder/debugger/explore deliberately clears the roster inherited from agent.yaml (leaf agents) — it is not a leftover - tests: deterministic RateSampler eviction assertion, reporter-dedup and non-finite-budget coverage, updated scope-lock message pin
Sibling rows in the RunAgents result tree stair-stepped because the
subagent type and name are variable width, and a name identical to the
type was echoed twice ("code-reviewer · code-reviewer"). Pad the label
and status columns to a shared width so the "· status" and "· task_id"
separators line up, and show the name only when it differs from the type.
Also fixes a latent bug where the per-agent preview line read a stale
loop variable, echoing the last agent's summary under every row.
Replace the language-selector landing page with a zero-flash, base-aware meta-refresh so the site root forwards straight to /en/ (no hero flash, no post-hydration JS redirect). The redirect is injected via config transformHead using the build-time base, so it resolves correctly under the GitHub Pages base path. Audit every reference, configuration, customization, and guide page against the source and fix factual drift so the docs reflect actual behavior: - CLI: correct --agent values, host short flag -H, wire protocol 1.9; add --no-telemetry/--no-yolo/--thinking-effort; fix `pythinker term` passthrough. - Slash commands: rewrite /statusline, add 16 previously undocumented commands, fix /usage and aliases. - Keyboard: Shift-Tab cycles thinking effort (not plan mode); add ?/!/Ctrl-T. - Config: paste-threshold defaults, openai_codex provider, /login platforms, theme: auto, web config. - Customization: refresh default agent toolset, document RunAgents and codenames, fix hooks/skills/wire-mode schemas to match source. - Guides: plan-mode entry, YOLO confirmation, statusline v2 context format. - Telemetry: complete the site-values enumeration and fix span name.
Restricted-profile (review/verify/read-only) subagents are now sandboxed by construction rather than by prompt: - Workspace jail for raw shell path args — discovery/search commands (find, rg/grep, ls/du/tree, git -C/--git-dir/--work-tree, --directory/--project) are denied when a path resolves outside the workspace + approved dirs; file-read commands keep ReadFile parity. Symlinks and ~ resolved first; shared by foreground and background shell. - Network is denied at execution time (allow_network on PermissionProfile), not just hidden; yolo root flag can never broaden a hard subagent profile. - Credential-looking env vars (*_API_KEY/_TOKEN/_SECRET/_PASSWORD/AWS_*/...) are scrubbed from shell subprocesses in profiles without shell-mutation rights; background TaskSpec persists only a boolean. - Retry-loop hard stop: a verbatim command that already failed twice under review/read-only is denied with guidance instead of flag-thrashing. UX/TUI: - Parallel subagents with generic/duplicate names get a generated adjective-noun codename, flowed through the result tree, TaskList, TaskOutput, and notifications. - Slash commands ghost-complete inline; Tab (or right-arrow/ctrl-e) accepts without submitting. - Tool-call rows are monotonic — late/duplicate wire events can't flip a finished row or restyle a committed one. - Subagent todo lists normalize to a single in_progress item. pythinker review/secscan now surface the origin/main -> main/master base fallback loudly via requested_base_ref/fallback_reason in ResolvedDiff, RunMeta, JSON output, the pretty renderer, and PR-artifact metadata. Plus CodeRabbit triage fixes (base-ref typing, TOMLDecodeError handling, re-raise of caught CwdLostError, one-shot otel error breadcrumb, usage placeholders).
Slash ghost text + Tab completion now fire for a /command typed anywhere on the line (e.g. "use /desi"), not just at the start. The dropdown completion menu stays line-start-only, so mid-sentence typing never pops a list. Extend the input lexer (renamed SlashCommandHighlightLexer -> InputHighlightLexer) to also colour @file mentions and a leading "!" bash prefix, composing all three token kinds on one line. @ and ! are agent-mode-only and styled syntactically. Add file-mention and bash-prefix style classes to both themes. Status bar: - Drop the redundant "tokens" segment when "context" is present (and from the stock default); "context" already prints the used/total ratio, so "ctx 78k/262k ... | 78k/262k" was pure duplication. - Render the context/limits progress bar with the compaction view's neutral-width ▰/▱ cells so it measures exactly as it paints. - Reserve the final column on the right-aligned row (matching the rule) so the last cell can't wrap on Windows conhost / PowerShell.
The hardened permission profiles and the default agent specs contradicted each other: most subagent specs instructed network/MCP doc lookups their own profiles hide and deny, the scout researcher was accidentally offline entirely, and a live review session showed the orchestration gaps that follow (no decomposition, no finding verification, opaque task handles). - Rewrite reviewer-class specs (review, code-reviewer, security-reviewer, judge, debugger, explore) offline-honest: never assert third-party claims from memory; verify what the repository proves; return the rest under RISKS as structured needs-verification items for the parent to resolve directly or via scout. Drop the dead SearchWeb/FetchURL and mcp__context7__*/mcp__tavily__* allowed_tools entries; plan and scout route docs work through live web tools instead. - Map scout to the network-enabled ask profile: it was unmapped, fell to the offline read_only default, and its entire research mission was dead. - system.md section 5/8: review fan-out scope measured at the merge base, per-subsystem decomposition above ~1,500 lines / 25 files, adversarial finding verification (drop, never severity-launder), re-anchor and recount, scoped doc verification of needs-verification claims, judge gate named at the delivery point. deep-scan playbook updated to match. - Close the workspace-jail bypass family: reject unexpanded $VAR/backtick path args fail-closed, validate glob args by their literal prefix (absolute and parent-climbing globs denied, ReadFile parity preserved), track cd/pushd across segments via the effective cwd, and reject popd / cd - / bare cd / grouping as untrackable. Escape denials now name the jail root so agents correct paths instead of retrying blind. - Scope-lock the statusline execution knobs (enabled, segments, command_timeout_ms) so a project config cannot trigger the user's command; bound command_timeout_ms at 60s; extend the secret env scrub (PRIVATE_KEY/JWT/COOKIE/BEARER); whitespace-normalize the retry-cap key. - TaskOutput steers to notification-driven waiting: the timeout hint leads with return-control, consecutive blocking timeouts escalate to a STOP-waiting streak, and a timed-out blocking attempt no longer resets the non-blocking poll escalation. - Background agent task ids are codenames (agent-tidal-wren): the id is the visible handle in TaskOutput headers, the task list, and notifications; random suffixes made single background launches opaque. - TUI streaming polish: suppress the transient red <invalid> flash while tool-call args stream, and bracket redraw frames in DEC 2026 synchronized updates (sync_output.py, capability-gated). Task: tasks/todo.md "Agent robustness arc" (decisions + triage evidence). Verified: tests/ 5225 passed, tests_e2e 65 passed, make check-pythinker-code clean.
Each finding was verified against the tree before acting. The structural claims (duplicate _skip_path_candidate, spliced TaskOutput module, duplicated jail tests) were stale-view artifacts: single definitions exist, the module imports, and the full suite passes. The docs backtick finding was already satisfied (the hooks table is fully backticked), and docs/en/release-notes/changelog.md is generated from the root CHANGELOG at docs build time — the root file is what changed. Applied: - ids.py: filter the used-id set to agent-prefixed ids before stripping suffixes for codename avoidance; bash ids polluted the avoid set. - _interactive.py: log mid-task slash-command task crashes — the task object is discarded on completion, so exceptions vanished silently. - telemetry/config.py: debug-log source-checkout detection failures instead of silently returning False. - artifact_context.py: requested_base_ref falls back to "" like its sibling optional metadata fields. - agent.py renderer: compute the tree label width on demand instead of storing it stringly and round-tripping through int(). - slash.py: type the statusline persist callback precisely (Callable[[StatusLineConfig], None]). - test_tui_card_tool_renderers.py: select the code-reviewer tree row by content instead of list position. - usage.py: document the unclosed-fence tradeoff (extraction noise over silent loss of later RISKS/BLOCKERS entries) — behavior unchanged. Declined: skills.md bold-list restyle (internally consistent block); fence-behavior change (the proposed alternatives drop real findings). Verified: tests/ 5225 passed, make check-pythinker-code clean, make check-pythinker-review clean (pyright 0 errors), review pkg 170 passed.
The gate piped the Unreleased block through `grep -q`, which exits at the first match; under `set -o pipefail` the resulting SIGPIPE to awk (exit 141) read as an empty block once it outgrew the pipe buffer — denying `gh pr create` exactly when the changelog was at its fullest. The non-blank check now runs inside awk with no pipe. Deny behavior for a genuinely empty block or a missing CHANGELOG.md is unchanged.
|
Important Review skippedToo many files! This PR contains 153 files, which is 3 over the limit of 150. To get a review, narrow the scope: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (153)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Defect review of a live session export (ae105609): the agent silently dropped an inline /best-practices reference, implemented against the first 1000 of 1206 spec lines without continuing the read, claimed checklist compliance it never verified, and misread injected reminders as user replies. Product fixes: - New InlineCommandReminderProvider flags inline /command and /skill:<name> references (known commands/aliases, path tokens excluded, one-shot per user message) so they are handled, not dropped; matching standing rule in system.md. - ReadFile capped reads now state remaining lines and the exact line_offset to resume from; the cap flag was unreachable for default reads (n_lines == MAX_LINES ordering) and is fixed; tool description and base prompt require finishing partial reads of governing specs. - system.md: user-designated spec files get artifact-scoped authority inside untrusted_data (requirements to implement, never directives to obey); <system-reminder> arrival is machinery, never a user reply; Definition of Done gains a task-spec checklist walk with per-item compliance evidence and honest unverified-artifact reporting. Adversarially reviewed (3 lenses, 11 agents): 3 confirmed findings fixed, 5 rejected as false positives. Verified: tests/ 5251 passed, tests_e2e 65 passed, make check-pythinker-code clean.
asyncio resolves Process.wait() only once the exit status is known AND every pipe has hit EOF. The bounded first read can leave the stdout transport flow-control-paused on a full buffer, so after kill() the pipe never disconnects and wait() hangs forever — CI's test runners hit this deterministically (Linux pipe dynamics), macOS only by luck of buffer sizing. The reap helper now kills, drains stdout to EOF, then waits with a timeout guard. A command that produced output but will not exit within the timeout now renders its captured first line instead of failing closed.
Rich memoizes each Style's SGR string at first render and shares value-equal combined styles process-wide via lru_cache, so the ANSI this test rendered depended on which console (e.g. a 256-color one earlier in the suite under TERM=xterm-256color with no COLORTERM) rendered the header styles first. Inspect the span styles' color triplets instead — the test's actual contract is that markers carry the theme diff tokens.
Exclude docs/** and tasks/** from CodeRabbit path filters: prose and working notes don't need bot review (no docs CI gate exists), and the mythos-enhancements PR's 152 changed files exceeded the 150-file per-review limit, skipping the review entirely.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Summary
This branch bundles three related work streams: the v2 status line, a hardening pass over agentic orchestration (subagent specs, review safety, sandbox jail), and a set of TUI input/footer refinements.
Statusline v2
StatusLineContext, per-segment renderers (spinner, model, cost, speed, effort, cwd, git, diff, flags), and a two-line footer assembler with drop-order degradation on narrow terminals/statuslinecommand grows style, bar-width, budget, and segment-listing controls; smooth eighth-block usage bar with gradient paletteStatusSnapshotAgentic orchestration
_SUBAGENT_PROFILESbrought into truth: reviewer specs are offline-honest with a needs-verification RISKS contract, scout→ask is the online delegate, and MCP stays fail-closed for non-implement profiles/learnself-improvement commandTUI polish
Other
/en/, content synced with current code## Unreleasedoutgrows the pipe bufferTest plan
make check(ruff + format + pyright) greentests_e2e(wire handshake snapshot updated for new slash commands)