15 KiB
Internal web UI — local development (#426)
Read-only MVP skeleton for the MCP Control Plane operator console. Gitea,
MCP capability gates, and skills/llm-project-workflow/ remain the source of
truth; this UI only provides route stubs and layout.
Prerequisites
- Python 3.11+ with project dependencies installed (
pip install -r requirements.txt) - No secrets in repo, config, or client bundle
Start the server
From the repository root (or an issue worktree):
./scripts/run-webui
Or directly:
python3 -m webui
Optional environment variables:
| Variable | Default | Purpose |
|---|---|---|
WEBUI_HOST |
127.0.0.1 |
Bind address (keep local for MVP) |
WEBUI_PORT |
8765 |
Listen port |
WEBUI_REPO_ROOT |
repository root | Prompt library workflow hash root |
WEBUI_PROJECT_REGISTRY |
packaged JSON | Project registry path |
GITEA_MCP_CONFIG |
unset | Server-side MCP profile config (never sent to browser) |
GITEA_MCP_PROFILE |
unset | Active MCP profile name (server-side only) |
See webui-deployment.md for internal-only serving, Cloudflare Access/WARP/VPN guidance, and unsafe bind overrides (#435).
See
architecture/webui-control-plane-console-architecture-adr.md
for the console architecture: layer and authority boundaries, the redaction
boundary, /api/v1/... versioning, the target page map, and the phase gates
that govern when a write path may open (#632, epic #631).
See webui-project-registry-api.md for the versioned project registry contract: registry schema versions 1 and 2, project status, onboarding checklist state, and the fail-closed error payloads (#635).
Routes (MVP)
| Path | Description |
|---|---|
/ |
Home / operator overview |
/health |
JSON liveness (status, service, mode, timestamp, uptime_seconds) |
/api/v1/system/health |
Structured read-only system health (#634) |
/queue |
Live PR and issue queue dashboard (#429) |
/api/queue |
JSON queue export with pagination metadata |
/projects |
Project registry list with status and onboarding progress (#427, #635) |
/projects/{id} |
Project detail + onboarding checklist |
/api/v1/projects |
Versioned JSON registry export (#635) |
/api/v1/projects/{id} |
Versioned JSON project detail (#635) |
/api/projects |
JSON registry export — unversioned Phase 1 alias of /api/v1/projects |
/prompts |
Prompt library with per-prompt copy buttons (#428) |
/api/prompts |
JSON prompt export with workflow hashes |
/runtime |
MCP runtime health and stale detection (#430) |
/api/runtime |
JSON runtime health export |
/audit |
Report audit paste + validator preview (#431) |
/api/audit |
JSON validator preview (POST report_text, optional task_kind) |
/worktrees |
Worktree hygiene dashboard (#432) |
/api/worktrees |
JSON worktree scan with classifications and anomalies |
/actions |
Gated write-action registry — all disabled in MVP (#434) |
/api/actions |
JSON action registry with capability metadata |
/api/actions/{id}/preview |
Mutation ledger preview (GET, read-only) |
/leases |
Lease and collision visibility (#433) |
/api/leases |
JSON lease/collision export |
/sessions |
Phase 1 shell stub — session inventory (backed by #636) |
/inventory |
Phase 1 shell stub — unified inventory (backed by #636) |
/timeline |
Phase 1 shell stub — workflow event timeline |
/policy |
Phase 1 shell stub — capability/role policy placeholder |
/insights |
Phase 1 shell stub — operational insights placeholder |
Most routes are GET-only. POST/PUT/PATCH/DELETE return 405 with
read-only-mvp, except /audit and /api/audit which accept POST for
local validator preview only (no Gitea mutations, no server-side storage).
System health API (#634)
GET /api/v1/system/health is the structured, read-only health surface for
automated readiness checks. It is the first console API under the /api/v1
prefix; the unversioned MVP exports remain as compatibility aliases.
/health is unchanged for existing consumers — every MVP key is still present
— and now also carries started_at, uptime_seconds, and a
system_health_api pointer. It stays deliberately cheap and runs no dependency
probe, because answering readiness costs real work.
Status codes. 200 when ready, 503 when a required dependency failed or
was never probed. Automation can branch on the code without parsing the body.
Query flags. The Gitea check is a network call, so it is opt-in:
GET /api/v1/system/health?deep=1 runs it and caches the result for
WEBUI_HEALTH_PROBE_TTL_SECONDS (default 15s) so dashboard polling does not
amplify into remote load. Without the flag that probe reports skipped.
Dependencies. control_plane_db and repository are required and drive
readiness. gitea is optional: when it fails the overall status degrades but
readiness.ready stays true, because local inventory is still serveable. Each
entry carries status, detail, required, and latency_ms.
Two honesty rules are worth knowing before reading the payload:
stale_runtime.mutation_safeis true only when the runtime, checkout, and remote-tracking commits are all known and equal. An unfetched remote is reported as indeterminate, never as safe.mcp_namespacesentries are alwaysunproven. A web process runs outside the IDE-managed MCP client and cannot invoke a namespace tool, so per #543 only aclient_namespaceprobe can prove that path.
Sample response (abridged, healthy):
{
"status": "ok",
"service": "mcp-control-plane-webui",
"mode": "read-only",
"api": "/api/v1/system/health",
"timestamp": "2026-07-22T11:04:18.512034+00:00",
"readiness": { "ready": true, "complete": true, "reasons": [] },
"version": {
"git_sha": "620ed6e9a9550b8da2ceb82d9ab8744e8920490f",
"git_describe": "v1.1.0-898-g620ed6e",
"control_plane_schema_version": 4,
"python_version": "3.14.5",
"known": true
},
"process": { "started_at": "2026-07-22T10:58:02.114+00:00", "uptime_seconds": 376.4 },
"deep_probes_requested": false,
"dependencies": [
{
"name": "control_plane_db",
"kind": "sqlite",
"status": "ok",
"detail": "schema v4 readable",
"required": true,
"healthy": true,
"latency_ms": 1.482,
"metadata": { "schema_version": 4, "active_leases": 3 }
},
{ "name": "repository", "kind": "git", "status": "ok", "required": true, "healthy": true },
{ "name": "gitea", "kind": "http", "status": "skipped", "required": false, "healthy": false }
],
"mcp_namespaces": [
{ "namespace": "gitea-author", "required_tool": "gitea_whoami", "status": "unproven" }
],
"stale_runtime": { "stale": false, "determinable": true, "mutation_safe": true, "reasons": [] },
"probe_errors": []
}
No restart, reload, or process-kill control is exposed here: those are Phase 2
at the earliest, and #630 forbids process-kill recovery outright. Every probe
opens its subject read-only — the control-plane database is opened through a
mode=ro URI so a health check can never create or migrate a schema.
Report audit (#431)
Paste an LLM final report at /audit or POST JSON to /api/audit. The UI
reuses final_report_validator and review schema checks to surface missing
proof fields, wrong validation vocabulary, mutation contradictions, and a
suggested next prompt or issue-comment draft. Task kind can be auto-detected
or selected explicitly.
Project registry (#427)
Versioned registry file: webui/data/projects.registry.json (schema version 1).
Override path with WEBUI_PROJECT_REGISTRY when operators keep a machine-local
copy outside git. The registry stores repo identity, remotes, profile names,
workflow/schema path references, and onboarding checklist steps — never tokens
or credentials.
Seed entry: Gitea-Tools on https://gitea.prgs.cc with prgs-author,
prgs-reviewer, and prgs-reconciler profiles.
Prompt library (#428)
Prompts are generated at load time from canonical workflow files under
skills/llm-project-workflow/workflows/. SHA-256 hashes are computed from
WEBUI_REPO_ROOT (defaults to the repository root). Prompt bodies are short
copy/paste starters; canonical workflow files remain the only full policy
source.
Live queue dashboard (#429)
/queue loads open PRs and issues for the default registry project (seed:
Gitea-Tools on https://gitea.prgs.cc) using existing gitea_auth read
credentials. The UI surfaces pagination proof (returned count, pages fetched,
has_more, inventory_complete) and classification badges (claimed,
blocked, in-review, duplicate) when evidence exists.
If credentials are missing or the fetch fails, the page shows an explicit error instead of an empty queue (fail closed).
Gated actions (#434)
/actions registers future write actions (claim, comment, review, merge,
delete branch, create PR/issue). Each entry declares the MCP tool, required
permission, and profile role from task_capability_map.py — aligned with
gitea_resolve_task_capability. Buttons are disabled; previews always render
a mutation ledger. Direct attempt_action calls fail closed without invoking
MCP tools.
Worktree hygiene (#432)
/worktrees scans local branches/ directories and registered git worktrees.
Each entry is classified (active-pr, active-issue, dirty, stale-clean,
detached-review, unsafe-unknown, orphan). Missing preserved worktrees
referenced by the issue lock file are flagged as anomalies (#404). The page
includes a copy/paste canonical cleanup prompt only — no deletion actions.
Override scan root with WEBUI_REPO_ROOT (defaults to repository root).
Lease visibility (#433)
/leases surfaces read-only lease and collision state: local issue lock file,
in-progress claim inventory (#268), reviewer PR lease comments when present
(<!-- mcp-review-lease:v1 -->, #407), duplicate open PRs per issue (#400),
and duplicate local branches per issue. Links to collision-history backend
issues (#267, #268, #400, #407) are included. No lease acquire/release from UI.
Runtime health (#430)
/runtime surfaces read-only MCP/runtime diagnostics for the default registry
project: active profile and role kind, authenticated identity (when credentials
are available), config model/mode, local vs remote master SHA sync, shell
health, workflow/schema SHA-256 hashes, and stale-runtime warnings when the
checkout is behind merged safety-gate changes. Restart guidance links to #420;
no tokens or MCP restart actions are exposed.
Application shell — Phase 1 (#638)
The console shell (webui/layout.py) renders a grouped navigation driven by a
single nav-config module, webui/nav.py. Nav groups follow the epic #631
Phase 1 information architecture: Health, Traffic, Runtime/Sessions,
Projects, Inventory, Timeline, Policy (placeholder), and Insights
(placeholder). Live views and Phase 1 placeholders (stub) are declared in one
place so the layout and the route table cannot drift.
The header carries two read-only status badges — an environment badge
(local for loopback binds, remote otherwise, derived from WEBUI_HOST) and
a mode: read-only badge — plus a Docs link to this document. No
privileged action controls are present in the Phase 1 shell.
Not-yet-implemented surfaces (/sessions, /inventory, /timeline,
/policy, /insights) resolve to graceful read-only stub pages instead of
404s; their backing views land in later child issues of #631 (the inventory
surfaces are backed by #636). Mutating methods on stub routes still fail closed
with read-only-mvp.
Deployment boundary (#435)
MVP serves on loopback by default. Binding 0.0.0.0 or :: is refused
unless WEBUI_ALLOW_PUBLIC_BIND=1. Non-loopback hosts log a warning unless
WEBUI_ALLOW_REMOTE_BIND=1. GET /health exposes deployment metadata.
Tests
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_gated_actions.py -q
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_audit.py tests/test_webui_worktree_hygiene.py -q
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_lease_visibility.py tests/test_webui_runtime_health.py -q
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_deployment_boundary.py -q
## Tests (#436)
Run the full hermetic web UI suite (all `test_webui_*.py` modules):
```bash
./scripts/test-webui
CI / Jenkins multibranch can call the path-filtered gate (runs only when the
diff touches webui/, tests/test_webui_*, or web UI docs/scripts):
./scripts/ci-webui-check
WEBUI_CI_FORCE=1 ./scripts/ci-webui-check # always run
scripts/test-webui sets WEBUI_TEST_OFFLINE=1 by default. In that mode the
queue, lease, and runtime routes use empty offline snapshots instead of Gitea
credentials, so CI can run without MCP daemon credential access. Set
WEBUI_TEST_OFFLINE=0 only when deliberately validating live fetch behavior.
Or invoke unittest directly:
python3 -m unittest discover -s tests -p 'test_webui_*.py' -q
Lease visibility (#433)
/leases surfaces read-only lease and collision state: local issue lock file,
in-progress claim inventory (#268), reviewer PR lease comments when present
(<!-- mcp-review-lease:v1 -->, #407), duplicate open PRs per issue (#400),
and duplicate local branches per issue. Links to collision-history backend
issues (#267, #268, #400, #407) are included. No lease acquire/release from UI.
Runtime health (#430)
/runtime surfaces read-only MCP/runtime diagnostics for the default registry
project: active profile and role kind, authenticated identity (when credentials
are available), config model/mode, local vs remote master SHA sync, shell
health, workflow/schema SHA-256 hashes, and stale-runtime warnings when the
checkout is behind merged safety-gate changes. Restart guidance links to #420;
no tokens or MCP restart actions are exposed.
Tests
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_audit.py tests/test_webui_worktree_hygiene.py -q
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_lease_visibility.py tests/test_webui_runtime_health.py -q