feat(cli): full-screen interactive TUI runtime#234
Draft
michaeljabbour wants to merge 8 commits into
Draft
Conversation
The bottom persistent status bar's stage text and the top per-turn transcript
record (the committed Plan block) both derived from the same turn title, so
when nothing more specific was happening the bar fell back to
f"Working on {title}" -- a verbatim duplicate of the permanent record already
written to scrollback.
Introduce current_activity_label() in layered_repl_status.py as the single
source of truth for what's happening right now in the bar. Precedence: active
agent-lane delegation > an active plan/tool step > a streaming response > a
distinct working idle indicator. The turn title is no longer part of this
precedence at all -- it stays exclusively in the permanent per-turn record, so
the two surfaces never collide.
_working_stage() now delegates to the shared helper instead of building its
own fallback chain (which included the removed title echo). Lane activity now
also outranks a stale _task_tracker.active_step_text() and a lingering stream
preview, since delegated work is the most specific signal available.
The same layered_repl_status.py diff also carries footer colorization
(_footer_fragments/_footer_part, applying class:mode.*/status.risk accents to
the plain toolbar text), palette-visibility and queued-message-bar rendering
(queued_bar_text, the palette_open/lane_focused hint precedence), and the
capability_hint_overrides plumbing into the footer call -- none of which are
mentioned by the summary above; they are included here because they already
shipped as part of this same file in the original commit and are exercised by
its own test coverage.
Fix-back (this revision): a prior push of this commit did not build or test
standalone -- confirmed by re-running `git worktree add` + a fresh `uv sync`
against the commit in isolation, which reproduced:
- ImportError: cannot import name 'TOKENS' from
amplifier_app_cli.ui.layered_repl_style
- pyright: no parameter named "hint_overrides"/"palette_open"/"lane_focused"
on format_bottom_toolbar_text (layered_repl_status.py:82,110,111)
- a latent AttributeError risk: lane.render_tree(...) has no implementation
on AgentLaneSnapshot
- (found during isolated re-verification, not in the original review) an
additional ImportError in tests/test_layered_repl.py for
amplifier_app_cli.ui.layered_repl_keys, and a runtime AttributeError from
the still-unimplemented capability_hint_overrides() on LayeredReplApp
Root cause: this file was authored and verified against a dirty working tree
that already contained later, uncommitted work (a token-based theme system,
a capability-hint-label catalog, and a keybinding-table refactor). The
production diff for *this* commit only ever touched layered_repl_status.py,
but its verification ("1946 passed", "pyright clean") was run against the
dirty tree, not the isolated commit -- so the missing dependencies went
unnoticed.
Fix: add the minimum each existing reference in layered_repl_status.py needs
to resolve, without pulling in the larger uncommitted refactor those
dependencies eventually belong to:
- amplifier_app_cli/ui/layered_repl_style.py: add a `TOKENS` dict with only
the four keys layered_repl_status.py and its test coverage reference
(dimmer, green, orange, bg_chrome), matching the hex values already used
for the same roles in the existing LAYERED_REPL_STYLE. LAYERED_REPL_STYLE
itself is untouched.
- amplifier_app_cli/ui/footer.py: add `palette_open`, `lane_focused`
(threaded into `_hint_levels()` with a literal-string hint tuple in the
same style already used for `approval_pending`) and `hint_overrides`
(accepted and intentionally unused -- no keybinding-label catalog exists
at this revision, matching the existing `del image_paste_available`
precedent in the same function).
- amplifier_app_cli/ui/agent_lanes.py: add `render_tree()` to
AgentLaneSnapshot, reusing the existing `_status_summary`/`_format_cost`/
`_truncate_cells` helpers already used by `render()`.
- amplifier_app_cli/ui/layered_repl_terminal.py: add a minimal
`capability_hint_overrides()` returning `None` on LayeredReplTerminalMixin
-- layered_repl_status.py calls `self.capability_hint_overrides()` at
runtime and no concrete implementation existed anywhere in this commit's
tree, which crashed 51 tests with AttributeError.
- tests/test_layered_repl.py, tests/test_layered_repl_visual_layout.py:
revert the handful of assertions (and one net-new test) that had leaked
in from the same dirty tree and exercised functionality this commit never
implements: the layered_repl_keys/build_layered_key_bindings rename (the
production code here still defines _build_key_bindings in
layered_repl_layout.py), the composer edge-window/separator-row/queued-
container layout, the "[mode] then prompt-marker" ordering swap, the
"shift-tab"->"shift+tab" and "ctrl-o expand"->"click or ctrl-o expand"
hint-text rewording, palette height/grouping changes, the "mode <id>"
footer prefix, the y-shortcut approval keybinding, and "Plan ·" prefix
labeling in the committed plan record/plan pane. None of the production
code for any of that exists in this commit; restoring the original
assertions makes the test diff match what this commit actually ships.
The assertions that do test this commit's real behavior change are kept:
"Coordinating N agents" (not the echoed title) taking precedence in the
working bar and live-agent-tree layout, and the working bar's distinct
idle indicator.
Verified in total isolation (git worktree add of this commit alone + a fresh
uv sync, matching the reviewer's method): uv run pytest -q fully collects and
passes, uv run pyright amplifier_app_cli/ui/layered_repl_status.py is clean,
and ruff check/format are clean on every file this commit touches.
Generated with Amplifier (https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
TranscriptReflowController deferred rewrapping already-printed transcript history for the entire duration of a running turn, not just while output was actively being appended. `_reflow_stream_active` gated on `execution_state["running"]`, which stays true for a whole turn -- including long stretches with nothing new printed yet (e.g. mid-tool-call waiting on a shell command). A resize during that idle window was silently held until the turn completed, creating a visible width seam between stale-wrapped history and the correctly-wrapped live preview. Narrow the gate to check only whether a stream preview is actively present (genuinely being painted), dropping the turn-wide running check. A running turn with no live append now reflows immediately on resize; a turn with an actively streaming preview still defers the rebuild, preserving the original guarantee of never repainting history under live output. Adds regression coverage in tests/test_resize_reflow.py: - reflow is not deferred when running but nothing is streaming (new) - reflow is still deferred while a stream preview is present (guard) Fixes: "resize doesn't work perfectly" mid-turn report. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…le per ADR-0005 amendment - Shift-Tab (InteractionController.cycle()) now cycles mode only (chat → build → plan → auto → brainstorm → chat), removing old special-cases where mode/posture were entangled - New Ctrl-P (InteractionController.cycle_permission()) cycles permission posture only (chat → build → plan → auto → bypass → chat), reusing TrustState.cycle() - Explicit-selection latch: any posture chosen via Ctrl-P latches _trust_explicitly_set, so mode-only cycling never silently reverts the chosen posture - Ctrl-P wired through layered_repl_layout → layered_repl_config → interactive_resources → interactive_host - ADR-0005 doc updated with amendment section - Tests extended: mode/posture cycle independence, posture cycle order, latch semantics, footer permission-display regression - Verification: full pytest run zero regressions vs baseline (+7 new passing tests; pre-existing failures unrelated to this work) 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…governance hardening v3 TUI rewrite: token-based theme system (layered_repl_style), table-driven keybindings (key_bindings_table.py + layered_repl_keys.py extraction), capability-hint catalog, transcript width-reflow + click spans + block render cache, terminal probe, keyboard protocol, layered transcript control, golden-based visual tests (footer + transcript goldens with regen_goldens.py), CI workflow, migration/design docs (tui-v3-cohesive.md, MIGRATION-main-decomposition.md). Governance/classifier hardening: `_parse_verdict` now tolerates provider thinking blocks (root cause of every tool call failing closed — the evaluator's own reasoning_effort triggered Anthropic extended thinking, and the 1-block contract rejected the response); fail-closed path now logs the swallowed exception and carries bounded detail (StageEvaluation.detail). Still fails closed on genuinely malformed content. Width-reflow scroll-anchor fix: reflow_to_width() preserves the anchor offset WITHIN the anchor span (was snapping to span start, jumping merged raw spans to top on width-only resize — Linux-manifesting, caught in DTU validation). New deterministic unit test. Test-debt cleanup: 20 stale-format failures reconciled to v3 rendering, stale keybinding test ported to the new surface, footer hint cap 3→4 with ctrl-p perms hint added (footer + composer placeholder), goldens regenerated and reviewed. Ctrl-P permission hint completes the ADR-0005 amendment shipped in 9d00f25. Validation evidence: local suite 1951 passed / 0 failed / 0 errors, ruff clean, pyright 0 errors; independently validated in an isolated Linux DTU (full suite 1951/0/0, scripted real-TUI PTY check PASS with ctrl-p hint rendered, width-reflow fix deterministic under tmux 3.7b). Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…ers; surface denial detail
Root cause found by live probe against claude-fable-5: the Anthropic provider module
has no response_format handling, so the classifier's JSON verdict schema never reaches
the model — sonnet-5 happened to guess the field names, fable-5 returned {"verdict": ...}
→ invalid shape → ValueError → fail-closed DENY on EVERY tool call ("classifier failed
closed").
Fixes:
1. Authorization prompt now spells out the exact verdict JSON object inline (schema
independence)
2. _parse_verdict selects text blocks (allowlist) instead of excluding known thinking
types — robust to any non-text block type
3. Provider refusals (empty content + finish_reason="refusal", from fable-5's
content-level refusal classifier on dangerous payloads) map to first-class DENY
(provider-refused-action) instead of generic exception
4. Classifier failure detail (StageEvaluation.detail) now folds into denial reason, so
the TUI blocked-block, tool_result fed to agent, and session events all show WHY a
denial happened
Live-validated against claude-fable-5: benign→allow 3/3, git push --force→deny 3/3,
all parseable. 37 governance tests pass.
Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Fresh (non-resumed) TUI sessions unconditionally started in mode=chat / posture=chat —
no code path seeded a new session from settings. New config.tui.startup_mode and
config.tui.startup_permission settings keys seed brand-new sessions via
AppSettings.get_tui_startup_config() (merged global→project→local→session).
A settings-declared permission preset is an explicit user choice per ADR-0005
("choosing the bypass permissions preset"), so it latches _trust_explicitly_set and
mode cycling never silently reverts it. Resumed sessions' persisted state always
wins. Invalid values are dropped with a log and fall back to the safe chat default.
Three pre-existing tests hardened to isolate from ambient project settings.
Also: .amplifier/ directory (.gitignore'd) is now a personal project-scope settings
location for startup preferences. Shipping bypass-by-default in the repo would be wrong.
Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
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.
Draft purpose
This is an upstream-preparation draft for the full-screen interactive CLI work validated and merged in michaeljabbour/amplifier-app-cli#2.
The repository currently says external contributions are not accepted, so this is intentionally not review-ready. It gives maintainers a concrete, tested branch for architecture/scope feedback before any proposal to merge.
User-facing result
Architecture
Interactive orchestration is split into focused app-layer boundaries:
amplifier_app_cli/runtime/interactive_*for lifecycle, resource setup, input, turns, cleanup, and persistenceamplifier_app_cli/ui/layered_*for layout, transcript, navigation, approvals, composer, and footeramplifier_app_cli/ui/command_*for the command registry, palette, modes, configuration, and session actionsamplifier_app_cli/ui/task_*andagent_lanes.pyfor work-tree statusamplifier_app_cli/runtime/amplifier_compat.pyas the isolated compatibility boundary for remaining private Amplifier seamsADR-0005 and ADR-0006 document the interaction and terminal decisions.
Verification
Fork PR #2 passed four required Ubuntu/Python 3.11 jobs:
linttypestestsintegrationLocal acceptance on this exact tree:
uv sync --all-groups --lockeduv lock --checkScope and review risks
This remains a large cohesive change: 305 files, primarily UI/runtime modules, acceptance tests, and transcript goldens. Before marking it ready, maintainers should decide whether to review it as one atomic interactive-runtime replacement or request a staged extraction plan.
Known reconciliation areas:
No merge is requested while this PR remains in draft.