Skip to content

rocm-dash ↔ rocm-cli engineering merge (unified TUI + chat OAuth; pip→wheel; tui.rs retirement deferred) - #7

Merged
michaelroy-amd merged 29 commits into
land-pr4-rebased-onto-mainfrom
eai-6871-rocm-dash-merge
Jun 14, 2026
Merged

rocm-dash ↔ rocm-cli engineering merge (unified TUI + chat OAuth; pip→wheel; tui.rs retirement deferred)#7
michaelroy-amd merged 29 commits into
land-pr4-rebased-onto-mainfrom
eai-6871-rocm-dash-merge

Conversation

@michaelroy-amd

@michaelroy-amd michaelroy-amd commented Jun 14, 2026

Copy link
Copy Markdown
Member

The rocm-dash ↔ rocm-cli engineering merge — folds the standalone rocm-dash telemetry/dashboard product into the rocm binary as a unified TUI, adds a no-key ChatGPT-OAuth chat backend, and aligns with the upstream pip→wheel rename.

Stacked PR. Base is land-pr4-rebased-onto-main (PR #6 = the rebased #4). When #6 squash-merges to main and its branch is deleted, GitHub retargets this PR to main; I'll rebase this head onto the post-#4 main (rerere has the Cargo.lock + rocm-core/Cargo.toml resolutions recorded) so the final diff is purely this PR's content.

What's here

  • Substrate / consolidation (Phases 1–2): rocm-dash crates transplanted — rocm-dash-core (pure reducer: State::apply → Vec<SideEffect>, no tokio/ratatui/rocm-core at the boundary), rocm-dash-collectors (bollard Docker + vLLM/lemonade scrapers), rocm-dash-daemon, rocm-dash-tui. D5 rocm dash launch verb (apps/rocm/src/dash.rs) with embedded-daemon auto-start + the services→dashboard gen_tps wire. D6 config unify (config.json + legacy TOML→JSON migration).
  • Unified-TUI screens (Phase 3): serve wizard, model picker, engine manager, doctor, update, install manager, logs, onboarding, automations, command, config/provider, runtime manager — built on the Wave-0 primitives.
  • Chat (D3): no-key ChatGPT OAuth backend (agent.rs, the sole rig-naming file) as the dash Chat tab; chat api_key is ENV-ONLY (the OAuth path takes no key — never TOML/CLI/source/logs).
  • pip→wheel alignment: our install/onboarding screens emit --format wheel; the chat system prompt + install-validation messages aligned to wheel (matches Migrate Python environment management from pip to uv #3's InstallFormat = {Wheel, Tarball} rename).

Invariants honored

rocm-dash-core boundary free of tokio/ratatui/rocm-core; reducer pure; chat key env-only; agent.rs sole rig-namer; bollard collectors-only; ureq/reqwest HTTP partition intact (not unified). All verified by a 3-agent refute panel.

tui.rs retirement — DEFERRED (intentional)

The plan considered retiring the legacy chat-first apps/rocm/src/tui.rs and routing bare rocm/rocm chat to the new dash Chat tab. A capability review found the dash Chat tab is read-only telemetry chat and lacks tui.rs's agentic Ask/Act ROCm tool-calling (install/serve/inspect + in-chat approval), the /plan planner, 22 slash commands, and the Anthropic provider. Retiring now would regress bare rocm/rocm chat, so tui.rs is kept; retirement is gated on porting the Ask/Act path into the dash chat (follow-up).

Test status

cargo build --workspace --all-targets ✓ · cargo test --workspace -- --test-threads=11581 passed, 0 failed · cargo clippy -p rocm-dash-tui --all-targets -- -D warnings ✓ · workspace clippy ✓ · cargo fmt --all --check ✓. (Under default parallel cargo test, two tui.rs chat tests flake on headless contention but pass in isolation.)

…6871)

Land the four rocm-dash library crates (core, collectors, daemon, tui) as
first-class workspace members under crates/. Self-contained explicit deps,
held at edition 2021 for a behavior-identical green transplant (2024 migration
follows as a separate step). ratatui 0.30 + crossterm 0.28 stay confined to
rocm-dash-tui; the frozen apps/rocm tui.rs keeps ratatui 0.29 — both majors
coexist in the tree. reqwest partition: 0.12 (collectors) / 0.13 (tui+rig).

Vendor crates and the rocm-dash bin are intentionally NOT promoted (the bin is
the only vendor-API consumer; its fold into apps/rocm + the vendor->real-crate
reconcile is Phase 2).

Verified: 320 unit + 5 integration tests pass with exact parity to rocm-dash;
full 'cargo check --workspace' green (apps/rocm, rocmd, 6 engines + 4 new crates).
Flip the four transplanted crates to the workspace edition (2024) and satisfy
the rust-1.95 toolchain gates: cargo fix --edition needed zero source changes
(already 2024-clean); apply rustfmt 2024-style import grouping and clippy
--fix (collapsible_if -> let-chains, valid under 2024). fmt --check clean,
clippy -D warnings clean, tests pass with exact parity (core 46 / collectors
54 / daemon 19 / tui 201 + integration).
Add a coverage CI job scoped to the four rocm-dash crates (the clean anchor,
not the whole workspace per D8) with a fail-on-regression floor of 70% lines
(measured baseline 73.7%). Document the Phase-1 state and the three pre-existing
apps/rocm test failures (2 parallelism-flaky tui chat tests that pass single-
threaded; 1 environmental therock python-launcher test) so they are not mistaken
for merge regressions.

Phase 1 (foundation) complete: 4 libs transplanted + edition 2024 + green build/
test + clippy/fmt clean + coverage gate.
… migration (EAI-6871)

Add a canonical rocm-dash dashboard sub-config nested under RocmCliConfig and a
one-shot legacy-TOML→JSON migration shim, per decision D6.

- RocmCliConfig.dashboard: DashboardConfig (DashboardDaemonConfig +
  DashboardTuiConfig), every field #[serde(default)] so the section is fully
  optional and rebases cleanly. Distinct axis from the rocm-cli telemetry policy.
- Pure with_*() immutable transforms scoped to the dashboard sub-config only;
  rocm-cli's in-place &mut mutation convention is untouched.
- migrate_legacy_dashboard_toml{,_from}: detect ~/.config/rocm-dash/config.toml,
  map daemon/tui knobs into dashboard and default_engine/engines onto the
  canonical fields, write ~/.rocm/config.json once, leave the TOML untouched.
  One-shot + never clobbers an existing config.json. Wired into apps/rocm main()
  with a 'migrated' stderr notice (additive; dispatch call sites unchanged).
- AppPaths::telemetry_state_dir() + daemon_log_path() (methods, not fields);
  ensure() also creates the telemetry dir. Unifies the dual-dir split onto ~/.rocm.
- rocm-core gains a toml dep for legacy parsing.
- 7 new rocm-core tests (round-trip, optional section, immutable transforms,
  tick accessors, AppPaths paths, migration map + one-shot, no-legacy noop).

Includes a pre-existing install_sdk fmt reformat to satisfy cargo fmt --all --check
(the baseline already failed fmt there; not a semantic change).

Adversarially reviewed by a 3-agent refute panel (2 ACCEPT, 1 WARNING; findings
addressed: connect-assertion added, daemon_log_path location documented).
…y (EAI-6871)

Build the load-bearing D7 deliverable: the rocm-cli managed-service registry
feeds the rocm-dash collector pipeline, so a model served via 'rocm serve'
appears in the dashboard with live gen_tps without Docker discovery.

- New rocm-dash-daemon::registry module: reads ManagedServiceRecord JSON from
  AppPaths::services_dir() via a minimal drift-tolerant ServiceRecord mirror
  (every field #[serde(default)], unknown fields ignored) — keeps the async
  daemon decoupled from rocm-core's sync/ureq surface. Converts live records
  (ready/running/starting) into the existing DiscoveredService shape; port comes
  from the registry record (authority), never a hardcoded default.
- run_loop wiring: RunnerOptions.services_dir (off by default). On the discovery
  cadence the daemon upserts managed services as instances + emits Discovered/
  Gone; vLLM ones flow through the existing Prometheus scrape on the registry
  port → gen_tps. Non-vLLM managed engines are tracked + excluded from the vLLM
  scrape (engine-kind seam picks the right parser).
- Port-authority demotion (D7d): documented EngineKind::default_port,
  docker DEFAULT_VLLM_PORT, lemonade LEMONADE_PORT as fallback-only for
  unmanaged/external discovery; the registry is authoritative for managed svcs.
- gen_tps_from_delta promoted to pub(crate) for the e2e data-path test.
- 5 daemon tests incl. a deterministic registry→scrape→gen_tps end-to-end
  (Phase-2 acceptance criterion 3, test-level — no ROCm GPU on this host).

Note: rocm-core was deliberately NOT added as a daemon dep — doing so pulls
rocm-core's 9 pre-existing dead-code warnings into the dash -D warnings clippy
gate. The mirror struct avoids that and the heavy edge.
… (EAI-6871)

Address the adversarial review panel (1 BLOCK + findings):
- BLOCK: discovered_from_record now rejects port==0 (serde-default u16 from a
  missing/partial record) — it would otherwise poll 127.0.0.1:0 forever. +test.
- Extract the run_loop managed-discovery block into a pure, tested
  discover_managed_services() (record→instance + vLLM/non-vLLM classification),
  raising daemon coverage and thinning the async loop. +2 tests.
- Document the co-located scrape limitation (record.host honored only for the
  local daemon, consistent with the existing Docker path) and that managed
  non-vLLM (Lemonade) gen_tps scraping + Healthcheck-first precedence remain
  D7 follow-ups (the registry seam is the plan's sequenced-first deliverable).

Combined 4-crate llvm-cov 74.02% lines (>=70 gate). clippy -D warnings + fmt clean.
…s (EAI-6871)

Mechanically modularize a representative pair of per-command handlers out of the
20k-line main.rs, demonstrating the D5 fn-move pattern with byte-identical
dispatch call sites (powderluv upstream-sync mergeability):

- Move fn automations() → apps/rocm/src/automations.rs (pub(crate) fn automations)
- Move fn uninstall()   → apps/rocm/src/uninstall.rs   (pub(crate) fn uninstall)
- Re-import each via 'use crate::<mod>::<fn>;' so 'dispatch()' arms stay verbatim
  ('=> automations(command)', 'uninstall(UninstallOptions { .. })'). The moved
  fns reach the crate root's private render/plan/policy helpers via crate:: (root
  items are visible to descendant modules), so NO helper had to be re-exported.

main.rs diff is +8 (mod/use) / -80 (the two fn bodies); zero dispatch call-site
changes. Bin builds; apps/rocm tests 748 passed, 4 failed — all 4 pre-existing
(2 flaky chat tests pass single-threaded; 2 env python-launcher tests, verified
failing on pre-D5 HEAD). Corrected the merge-status doc: the honest pre-existing
count is 4, not 3 (the 2nd python-launcher env test was previously unnamed).

Scope note: this is the representative 'cheap 80%' fn-move proving the pattern +
call-site invariant (Phase-2 criterion 6). The full handler sweep + the rocm-dash
launch-verb bin-fold remain D5 follow-ups (see handoff).
…itives (EAI-6871)

Build the four Wave-0 primitives every operational screen reuses, on the
pure rocm-dash-core reducer + an async effect layer in rocm-dash-tui:

- core/state.rs: SideEffect::SpawnJob{id,cmd,args,cancel} + StateEvent
  StartJob/JobLine/JobDone/JobErr/CancelJob + per-job JobState with a
  bounded output ring (JOB_OUTPUT_RING_CAP). Reducer stays pure/sync and
  tokio/ratatui-free; cancel via std Arc<AtomicBool>. +7 unit tests.
- tui/jobs.rs: async job-bridge runtime — tokio::process child, streams
  stdout+stderr as JobLine, drains both pipes before JobDone, cancel
  observed within CANCEL_POLL. Replaces the legacy thread+mpsc+try_recv.
- tui/ui/approval.rs: approval render+event seam (ApprovalRequest/Choice/
  Verdict + pure approval_key). Logic stays CLI-side; no chat-seam breach.
- tui/ui/folder_browser.rs: reusable drive/parent/child/new-child picker.
- tui/ui/job_console.rs + modal.rs draw_scrollable_lines: shared chrome.
- tests/wave0_job_bridge.rs: exit gate — real long job stream+cancel,
  missing-binary fail, short-job drain, + TestBackend snapshots.

Additive only (no exhaustive SideEffect/StateEvent match outside core;
daemon discards apply() results). Adversarial 3-reviewer panel: unanimous
fail-to-refute. core 53 tests, tui 212 lib + 5 wave0; clippy -D warnings
clean on the 4 dash crates; fmt clean.
…I-6871)

Fold the rocm-dash dashboard launch into the rocm binary and wire the
serve→dashboard telemetry loop:

- apps/rocm depends on rocm-dash-daemon + rocm-dash-tui + tokio; the two
  ratatui majors (0.29 tui.rs, 0.30 rocm-dash-tui) and crossterm 0.28/0.29
  coexist, each crate-confined.
- apps/rocm/src/dash.rs: `rocm dash` builds a tokio runtime, auto-starts an
  embedded telemetry daemon when none is listening, and runs the ratatui
  dashboard TUI. runner_options() sets services_dir = AppPaths::services_dir()
  (the load-bearing wire: the daemon discovers ManagedServiceRecords that
  `rocm serve --managed` writes there → vLLM scrape → live gen_tps via the
  D7 seam) and persist_dir = telemetry_state_dir().
- chat api key stays ENV-ONLY (ROCMDASH_CHAT_API_KEY→AMD_LLM_API_KEY→
  OPENAI_API_KEY); endpoint/model/header from config.dashboard.tui.
- main.rs: additive `mod dash` + `Command::Dash` + one dispatch arm; no
  existing call sites changed. HTTP partition (ureq vs reqwest) preserved.

With this, `rocm serve <model> --managed` (CLI) → `rocm dash` shows the
served model's live gen_tps in the dashboard end-to-end. +2 unit tests
(services_dir→registry wire; args-from-config). Adversarial 3-reviewer
panel: unanimous fail-to-refute (medium/low/low; the flagged unused direct
rocm-dash-core dep removed). Workspace build --all-targets exit 0;
test --workspace 750 passed / 4 pre-existing apps/rocm failures (untouched
tui.rs/therock.rs, verified by stash); clippy -p rocm-dash-tui -D warnings
clean.
…tives (EAI-6871)

The first operational screen rebuilt on the unified reducer base, proving
the Wave-0 primitives compose into a screen:

- ui/services_manager.rs: lists managed inference services (model · port ·
  status · live gen_tps) from the daemon-surfaced instances; stop/restart
  routed through the approval gate → job-bridge (`rocm services stop|restart
  <id> --yes` via current_exe), never inline. Renders list / approval modal /
  job console. 9 tests incl. 2 TestBackend snapshots + the approval-gate +
  q-escape contracts.
- app.rs: AppState gains `jobs: rocm_dash_core::state::State` (the pure job
  model) + `services` overlay; event_loop adds a job-bridge StateEvent
  channel + select arm (apply → run_effects) and routes keys to the overlay
  when open; `s` opens it from the Instances tab. Zero std::thread::spawn /
  try_recv — all async via the Wave-0 job-bridge.
- ui/mod.rs: overlay draw dispatch (above modals).

Per-screen contract met: renders on the reducer, mutating actions gated by
the approval seam, TestBackend snapshot, zero thread::spawn. Chat read-only
seam untouched (no agent.rs/llm.rs/rig import). Adversarial 3-reviewer
panel: unanimous fail-to-refute (low/medium/low); fixes applied — q never
trapped mid-job, command resolved at approval time (no silent no-op), no
expect() panic path. tui 220 lib + 5 wave0 green; clippy -D warnings clean;
the 4 apps/rocm failures remain pre-existing (untouched files).
…s (EAI-6871)

Second operational screen rebuilt on the unified rocm-dash reducer base. A
compact serve form (model · engine · device · host · port · mode) that builds a
`rocm serve … --managed` invocation and runs it through the approval gate and
the job-bridge — never inline, zero std::thread::spawn/try_recv.

- ui/serve_wizard.rs: form state machine + pure build_args() + on_key seam
  mirroring services_manager; Tab on Model opens the Wave-0 FolderBrowser for a
  local model path; Launch → approval → job-bridge StartJob. Managed (default)
  surfaces the model in services_manager + dashboard gen_tps (D7 wire live).
  16 unit tests incl. 2 TestBackend snapshots.
- ui/exec.rs: shared resolve_exe()/exe_label() spawn helpers; services_manager
  refactored onto them (DRY, removes its private copies).
- app.rs: AppState.serve_wizard overlay + event_loop interception arm +
  OpenServeWizard (`w` from Overview/Instances); ui/mod.rs draws it + footer hint.

Gates: cargo build --workspace --all-targets exit 0; clippy -p rocm-dash-tui
--all-targets -D warnings clean; rocm-dash-tui 238 lib + 5 wave0 green. Workspace
test 750 passed / 4 pre-existing apps/rocm failures (untouched tui.rs chat +
therock.rs python-launcher; PR #4-owned, not regressions — diff is dash-tui only).
Three findings from the 3× refute panel (HIGH/HIGH/MEDIUM), all fixed:
- HIGH: spawn_serve set active_job even when the reducer no-op'd a StartJob for
  an already-running id (idempotent reducer returns no SpawnJob) → silent missed
  launch pointing at a stale console. Now guards on empty effects, surfaces
  "a job for X is already running", leaves the form. +test.
- HIGH: footer hint said "Enter launch" but Enter advances fields except on the
  Launch row → now "Enter next/launch".
- MEDIUM: port 0 parsed as valid u16 despite the "1–65535" message → now
  rejected (p > 0). +test.
- Also: cargo fmt violations in the prior commit's app.rs/serve_wizard.rs blocks
  (the panel caught that  includes fmt; clippy alone had passed).
- +test: Esc ignored while job Running, dismisses console when terminal.

rocm-dash-tui 241 lib + 5 wave0 green; fmt + clippy -D warnings clean.
…ves (EAI-6871)

Third operational screen on the unified reducer base. Lists the serving-engine
catalog (lemonade/pytorch/llama.cpp/vllm/sglang/atom) and runs use/install/
reinstall through the approval gate + job-bridge — never inline, zero
thread::spawn/try_recv. Installs stream into the shared job console.

- ui/engine_manager.rs: list + approval + active_job seam mirroring
  services_manager. u → `rocm config set-default-engine <e>`; i → `rocm engines
  install <e>`; r → `rocm engines install <e> --reinstall`. Job ids sanitize
  dotted names (llama.cpp → engine-reinstall-llama-cpp). Reuses the serve_wizard
  no-op-launch guard (already-running id surfaces a message, no stale console).
  10 unit tests incl. 2 TestBackend snapshots.
- app.rs: AppState.engine_manager overlay + event_loop arm + OpenEngineManager
  (`e` from Overview/Instances); ui/mod.rs draws it (mutually-exclusive else-if
  chain) + footer hint.

Gates: build --workspace --all-targets exit 0; clippy -p rocm-dash-tui
--all-targets -D warnings clean; fmt clean; rocm-dash-tui 251 lib + 5 wave0
green. (apps/rocm OsStr warning + 4 chat/therock test failures are pre-existing,
PR-owned; diff is dash-tui only.)
3× refute panel found no CRITICAL/HIGH correctness bugs (CLI argv verified exact
vs clap: config set-default-engine <e>, engines install <e> [--reinstall];
mutual-exclusion structurally safe; no key collisions; no regressions). Applied
the worthwhile findings:
- MEDIUM (×2): apply_action OpenServices/OpenServeWizard/OpenEngineManager now
  defensively clear sibling overlays so no future open path can leave two Some
  at once (draw/route chains would disagree silently). +test.
- HIGH: added app.rs tests for the operational open keys — s/w/e tab-scoping
  (Instances-only / Overview+Instances / Nothing elsewhere incl. Chat) and the
  mutual-exclusion invariant.
- LOW: engine_manager no-op-launch guard now has a regression test; em.message
  cleared when dismissing the console; lemonade description aligned verbatim to
  engine_inventory().
- Double-Enter-approves is a pre-existing template trait (Wave-0 approval.rs,
  shared with services_manager) — left as-is; noted for a cross-screen follow-up.

rocm-dash-tui 254 lib + 5 wave0 green; fmt + clippy -D warnings clean.
Completes the Wave 1 constructive set. The serve wizard's Model field now opens a
filterable recipe picker (Enter on Model), feeding the built-in model recipes;
free-text typing and Tab-browse (local path) remain as fallbacks. Choosing a
recipe fills the model id and pre-selects the recipe's preferred engine.

- ui/model_picker.rs: TUI-local ModelRecipeSummary + pure ModelPicker
  (case-insensitive id/alias filter, clamped cursor) + draw_model_picker.
  9 unit tests incl. a TestBackend snapshot.
- serve_wizard: picker sub-step (priority picker → browser → approval →
  active_job → form); on_key/draw take &[ModelRecipeSummary]; recipe choice
  fills model + preferred engine. +3 integration tests.
- Plumbing (no rocm-core dep at the dash layer — the bin adapts): ResolvedArgs
  + AppState gain model_recipes; dash.rs maps rocm-core builtin_model_recipes()
  → summaries (canonical_model_id/aliases/task/preferred_engines.first). +2
  dash tests.

Gates: build --workspace --all-targets exit 0; clippy -p rocm-dash-tui
--all-targets -D warnings clean; fmt clean; rocm-dash-tui 265 lib + 5 wave0
green; apps/rocm dash::tests 3/3. Wave 1 constructive screens complete
(serve_wizard · engine_manager · model_picker · services_manager).
3× refute panel: 2 fail-to-refute (invariants/plumbing/struct-evolution all
clean; reviewer confirmed this commit also FIXED a pre-existing streaming-cancel
failure), 1 REFUTED with test-quality/UX gaps (no behavioral bugs). Applied:
- HIGH: added the silent-fallback contract test — a recipe whose preferred_engine
  is not in ENGINES leaves engine_idx untouched (model still filled), guarding
  future ENGINES vs rocm-core divergence.
- MEDIUM: Model-field placeholder now mentions Enter-to-pick when recipes exist
  (field_line takes has_recipes); resolves the placeholder/hint mismatch.
- MEDIUM: the picker now opens pre-filtered by any text already typed in the
  Model field (typed 'qwen' → Qwen recipes); +test.
- MEDIUM: dash adapter test strengthened — asserts no recipes dropped, aliases/
  task/preferred_engine forwarded, first summary mirrors its record.
- LOW: corrected the stale 'when we ship it' comment (engine pre-select ships).

rocm-dash-tui 267 lib + 5 wave0 green; fmt + clippy -D warnings clean; dash 3/3.
…tives (EAI-6871)

Two operational screens establishing the remaining archetypes on the unified
reducer base, both zero thread::spawn/try_recv, all async via the job-bridge:
- ui/doctor_manager.rs: read-only-report archetype. Enter runs `rocm doctor`
  through the job-bridge into the shared console; no approval (read-only). r
  re-runs after a result. 5 tests incl. snapshot.
- ui/update_manager.rs: report-with-gated-apply archetype. Check (`rocm update`)
  and Preview (`--apply --dry-run`) are read-only (no gate); Apply
  (`--apply`) and Apply-and-activate (`--apply --activate`) route through the
  approval gate → job-bridge. 8 tests incl. snapshot.
- app.rs: doctor_manager/update_manager overlays + event_loop arms +
  OpenDoctor/OpenUpdate (`d`/`u` from Overview/Instances). Refactored the
  per-open sibling-clear into AppState::close_overlays() (DRY across 5 overlays;
  mutual-exclusion invariant). ui/mod.rs draws them + footer hints. +tests for
  d/u tab-scoping and the extended mutual-exclusion set.

Gates: build --workspace --all-targets exit 0; clippy -p rocm-dash-tui
--all-targets -D warnings clean; fmt clean; rocm-dash-tui 280 lib + 5 wave0
green. (apps/rocm pre-existing chat/therock failures unchanged; diff is dash-tui
+ app.rs only.)
…on (EAI-6871)

3x refute panel on the Wave-2 doctor/update screens: 2 fail-to-refute
(CLI argv exact, invariants/regression/clippy clean), 1 REFUTED with a HIGH DRY
finding + test gaps (no behavioral bugs). Applied:
- HIGH (DRY): extracted the per-screen active_job console-key block (Ctrl+C
  cancel / q close / Esc-Enter dismiss-when-terminal) into
  job_console::on_console_key -> ConsoleOutcome, and refactored ALL five
  operational screens (services/serve/engine/doctor/update) onto it. ~80 lines
  of near-duplicate logic collapsed to one tested seam (+helper test).
- MEDIUM (systemic key-trap): approval_key now treats q/Q as Cancel (not just
  Esc), so q is never silently swallowed while any approval modal is up. Help
  text -> 'Esc/q cancel'; wave0 approval snapshot assertion updated. +test.
- Tests added: update relaunch-while-running no-op guard (highest-value gap),
  doctor r-rerun-after-terminal + r-at-idle.
- Polish: doctor no-op now documents the intentional re-attach (vs distinct-id
  screens); doctor footer 'Enter/r run'; update tag '(gated)'->'(needs approval)'.

rocm-dash-tui 284 lib + 5 wave0 green; fmt + clippy -D warnings clean;
workspace --all-targets builds. Diff is dash-tui only.
…AI-6871)

Two more operational screens on the unified reducer base + shared console seam:
- ui/install_manager.rs (Wave 2): `rocm install sdk` form (channel · format ·
  folder · mode). Defaults to dry-run (read-only → spawns straight through the
  job-bridge); a real install is mutating → approval gate first. Folder picked
  with the Wave-0 FolderBrowser (Tab on the Folder field). Distinct dry-run vs
  install job ids. 10 tests incl. snapshot.
- ui/logs_view.rs (Wave 3): `rocm logs [--search WORDS]` — read-only browser
  with an optional search box; no approval. 6 tests incl. snapshot.
- Both consume job_console::on_console_key (the consolidated seam).
- app.rs: install_manager/logs_view overlays + event_loop arms + OpenInstall
  (`i`)/OpenLogs (`l`) from Overview/Instances; close_overlays() extended;
  ui/mod.rs draws them + footer hints. Tests: i is ChatFocus on Chat (guarded
  from OpenInstall), l/d/u/w/e Nothing on Chat, extended mutual-exclusion set.

Gates: build --workspace --all-targets exit 0; clippy -p rocm-dash-tui
--all-targets -D warnings clean; fmt clean; rocm-dash-tui 300 lib + 5 wave0
green. Diff is dash-tui + app.rs only.

Wave coverage now: serve_wizard · model_picker · engine_manager · services_manager
(W1) + doctor · update · install (W2) + logs (W3). Remaining for the unified TUI:
runtime/onboarding (W2), automations/command/config/provider (W3), the D3 chat
split (rig-core chatgpt OAuth), then tui.rs retirement. Phase 4 deferred behind
upstream PR #4 (it also touches ci.yml + the single-exe/universal-binary scripts).
3x refute panel: 2 fail-to-refute (CLI argv exact incl. InstallFormat lowercase
+ logs --search num_args=1..; no shell injection; invariants/struct-evolution/
mutual-exclusion across all 7 overlays/regression/footer-panic clean), 1 REFUTED.
The 2 HIGH (q-at-idle) are the form-archetype trait already accepted for
serve_wizard: text fields (Channel/Prefix, logs search box) capture q as input,
Esc closes — not a trap, consistent with the form screens (not the menu screens
doctor/update). Documented as such; applied the valuable MEDIUM fixes:
- install: added the no-op-guard test (relaunch-while-running surfaces a message,
  no stale console) mirroring update/engine — the highest-value gap.
- logs: added the second-Enter re-attach test (read-only re-attach like doctor).
- install: '←→ change' hint corrected to '←→ cycle (Format/Mode)' (it only acts
  on the cycle fields).
- Documented the deliberate Esc-closes / text-field-q-capture form behavior in
  both screens.

rocm-dash-tui 302 lib + 5 wave0 green; fmt + clippy -D warnings clean.
… plumbing (EAI-6871)

list/activate/rollback/uninstall/adopt/import ROCm runtimes on the Wave-0
primitives. Runtime list plumbed bin→ResolvedArgs→AppState like model_recipes
(dash.rs adapts therock::load_runtime_manifests + config active/rollback status;
no rocm-core dep in the TUI crate). Refresh read-only; mutating verbs gated by
the approval seam; adopt via FolderBrowser, import via typed-path prompt. Zero
thread::spawn/try_recv. 15 tests incl TestBackend snapshots + no-op guard.
rocm-dash-tui 317 lib + 5 wave0 green; core 53; clippy -D warnings clean.
…-6871)

Adversarial panel LOW finding: opening_an_overlay_closes_the_others stopped at
OpenLogs. Extend it to assert OpenRuntimes clears the prior overlay too, so a
future close_overlays regression on that arm is caught.
…t) (EAI-6871)

First-run setup rebuilt on the Wave-0 primitives: Welcome → Choose
{Install ROCm SDK (release·pip) | Adopt existing folder} → approval → job-bridge
→ Done. Install is a one-shot gated 'install sdk'; adopt picks an env root via
FolderBrowser then approves 'runtimes adopt' (cfg!-derived python). Both gated;
zero thread::spawn/try_recv. Reinstall/uninstall/show-log sub-modals are
documented fast-follows. 'n' opens it from Overview/Instances. 10 tests incl
TestBackend snapshots (welcome+choose) + no-op guard. 327 lib + 5 wave0 green;
clippy -D warnings clean.
Panel HIGH: the wizard advanced to 'Done — you're ready to go' on ANY terminal
console dismiss, so a FAILED 'rocm install sdk' falsely claimed success. Now only
a clean exit (code 0) reaches Done; failure/cancel returns to Choose with an
honest, retry-able message. +test for the code:1 path.
Panel MEDIUM: 'Esc skip' welcome hint actually closed → 'Esc close'.
Panel MEDIUM: documented the post-install engine-preference reconciliation +
first-run persistence as explicit fast-follows in the module doc.
… screens (EAI-6871)

Three operational overlays on the Wave-0 primitives, completing the screen waves:
- automations_manager: list/enable/disable background checks. AutomationSummary
  plumbed bin→ResolvedArgs→AppState (dash.rs adapts builtin_watchers + config
  enabled/mode). Refresh read-only; toggles gated. 'a' opens.
- command_screen: general 'rocm <args>' runner; EVERY freeform command routes
  through approval (safe default for unclassifiable subcommands) → job-bridge.
  'c' opens.
- config_manager: folds config + provider. Show read-only; provider
  enable/disable gated. API keys are NEVER entered/stored here — env-only per the
  chat invariant, with a persistent notice + a structural test asserting no
  set-provider-key argv is ever produced. 'p' opens.
All reducer-only (zero thread::spawn/try_recv); TestBackend snapshots + no-op
guards; close_overlays mutual-exclusion extended (+test). rocm-dash-tui 355 lib +
5 wave0 green; clippy -D warnings clean; workspace build --all-targets exit 0.
Panel MEDIUM (env-only hardening): command_screen is the one place the TUI takes
freeform text, so a user could type 'config set-provider-key …' and leak a secret
through argv/job logs. Add looks_secret_bearing() → the approval body now warns
when an argv looks secret-bearing (key/token/secret/password), steering keys back
to the environment. config_manager still structurally refuses key entry.
Panel MEDIUM (coverage): add command_screen q-escapes-while-job test + automations
toggle relaunch-while-running no-op-guard test.
Panel LOW: automations snapshot now asserts the on/off badges render.
rocm-dash-tui 358 lib + 5 wave0 green; clippy -D warnings clean.
Wire rig-core 0.38.1 native chatgpt provider as a new AgentClient
(ChatGptAgentClient) behind the existing swappable seam in agent.rs — the no-key
default that restores the ChatGPT device-login the vendored Codex path provided.
- Builds via Client::builder().oauth().on_device_code(handler); authorize() runs
  the device-code flow on first complete() and the provider caches the token.
- Takes NO api_key: the env-only key invariant is structurally preserved (this
  path authenticates by OAuth, not a key) — asserted by a construction test.
- Same six READ-ONLY telemetry/skills tools as RigAgentClient; mutating CLI tier
  stays behind the approval seam (command_screen) — full chat→approval wiring is
  a documented fast-follow.
- app.rs selects it as the no-key default ONLY when no local endpoint is
  reachable AND no api_key/url is configured; the device-code URL+code surface in
  the chat tab via the existing ClientMsg::ChatReply plumbing.
- Live interactive round-trip test is #[ignore] (network + browser login).
agent.rs stays the only file naming rig types. 360 lib + 5 wave0 green; clippy
-D warnings clean; workspace build --all-targets exit 0.
Panel LOW: add AgentError::Auth so a failed/declined ChatGPT sign-in surfaces as
'chat sign-in failed: …' (not the misleading 'failed to build chat client'); the
token is never embedded. Tighten chatgpt_oauth_client_defaults_model_when_none to
assert the exact GPT_5_3_CODEX default rather than mere non-emptiness.
After rebasing onto #1-4, #3's InstallFormat rename (pip->wheel; valid = {wheel,
tarball}) makes our hardcoded 'pip' invalid. Sweep:
- rocm-dash-tui install_manager.rs: FORMATS = ["wheel","tarball"]; default +
  build_args + tests emit --format wheel.
- rocm-dash-tui onboarding.rs: install argv + doc + test --format pip -> wheel.
- apps/rocm main.rs: chat system prompt argv and the install-validation error
  messages (Windows guard + version/build-date guard) referenced the now-invalid
  'pip' though the logic already requires wheel -> aligned to wheel.
- rocm-core lib.rs: regenerated Cargo.lock + fixed a field_reassign_with_default
  clippy lint in a dashboard-daemon test (struct-literal init).

Workspace build/test/clippy -D warnings + fmt all green.
@michaelroy-amd

Copy link
Copy Markdown
Member Author

squash merge but leaving the branch so the commits can be audited

@michaelroy-amd
michaelroy-amd merged commit d3e4b18 into land-pr4-rebased-onto-main Jun 14, 2026
@rominf
rominf deleted the eai-6871-rocm-dash-merge branch June 17, 2026 07:23
@rominf rominf changed the title EAI-6871: rocm-dash ↔ rocm-cli engineering merge (unified TUI + chat OAuth; pip→wheel; tui.rs retirement deferred) rocm-dash ↔ rocm-cli engineering merge (unified TUI + chat OAuth; pip→wheel; tui.rs retirement deferred) Jun 17, 2026
michaelroy-amd added a commit that referenced this pull request Jun 29, 2026
… scroll, doc fixes

- #3: guard the Esc menu on a pure menu_fits() helper so it only renders
  when the logo AND all three items (Options/Help/Quit) fit; the old
  inner.height < 6 guard painted the logo with no reachable items at
  heights 6..=10. New unit test exercises the broken range + boundary.
- #4: remove the dead 't theme' launcher footer hint (no t handler).
- #5: global Help jump-to-tab now reads '1 .. 5' (5 tabs), matching draw_help.
- #6: scroll-offset casts use u16::try_from(..).unwrap_or(u16::MAX) instead
  of 'as u16' (console v/h, dock logs, bench detail, chat) — no wrap >65535.
- #7: split misattributed doc comments so apply_action and clamp_selectors
  each carry their own doc (and drop a stale gpu_scroll line).

Signed-off-by: Michael Roy <michael.roy@amd.com>
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.

1 participant