Files
orchestra/WEB_UI_PLAN.md
T
kami b57894b183 Add web UI and worker capture/approval command channel
Introduces the browser-facing surface and the worker-side protocol that
backs it:

- internal/ui: joined read model plus per-task lifecycle and approval
  controls, kept separate from the raw endpoints workers and harnesses
  depend on.
- internal/webui + web/: Vite/React app, build output embedded via
  go:embed and served as an SPA fallback.
- federation: per-(worker, task) captures with a monotonic revision that
  advances only when pane text actually changes, and a command queue
  restricted to grant_approval / deny_approval, each bound to the capture
  revision the operator acted on.
- orchestra-worker: publishes captures and executes commands only after
  re-reading the pane and confirming the revision still matches. Sends
  keystrokes only for a visible y/n prompt or OpenCode's fully labelled
  selector, and refuses to deny through that selector rather than guess
  at unobservable navigation.

This is the ownership boundary AUDIT.md's B14 and B17 call for: approval
becomes an explicit, revision-bound operation executed by the worker that
owns the pane, instead of a side effect of prompting over a
coordinator-driven remote socket.

Also ignores the web build inputs and outputs. node_modules ships vendored
Go packages, so go build and go test walk into it if it is merely
untracked; both node_modules and .node_modules are excluded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01535A3Y8RtkAi8wYuWhtkEd
2026-07-28 23:14:16 +04:00

165 lines
7.8 KiB
Markdown

# Orchestra Web UI Plan
## Goal
Build a proper TypeScript single-page application for local operation of
Orchestra. It must make live task/session state observable, make every
supported control actionable, and render harness approval requests as the
actual pending decision rather than as a vague `blocked` badge. Authentication
is deliberately out of scope for this first local-only version; the API
contract must still keep a clean boundary where auth can be added later.
## Product scope
### Monitoring
- Task board with queued, leased, blocked, completed, and failed states.
- Task detail with description, lifecycle timeline, current worker/harness,
lease expiry, handoff/report references, and Git/worktree facts when a
session is live.
- Worker view: online/offline state, capacity, last heartbeat, active task,
and harness identity.
- Session view: agent status, occupancy, turn-boundary health, recent pane
capture, extracted blocker, and update timestamp.
- Artifact view for handoffs and reports, with JSON formatting for canonical
handoffs and plain-text/Markdown rendering for reports.
### Controls
- Create a task with all currently accepted task fields.
- Request a handoff/rotation through the existing task-control path.
- Grant or deny a currently displayed harness permission.
- Explicitly release, block, or complete a task only where the backend can
validate the required evidence.
- Refresh and polling controls; no control is rendered merely as decoration.
## Architecture
### Frontend
Create `web/` as a Vite + React + TypeScript application.
- React Router routes: `/`, `/tasks/:taskID`, `/workers`, and `/artifacts/:ref`.
- TanStack Query for polling, cache invalidation after controls, retry/error
state, and optimistic UI only after the server returns success.
- A small component layer (CSS modules or Tailwind, chosen during setup) with
accessible dialogs, keyboard-focus handling, and responsive layouts.
- Build output is embedded into the Go binary with `go:embed` and served at
`/`; `/v1/*` remains API-only. The Go server serves `index.html` as the
SPA fallback and static assets with cache headers.
- A typed API client generated from hand-maintained TypeScript interfaces in
`web/src/api/types.ts`; those interfaces are exercised against Go JSON
response tests so frontend/backend field drift fails CI.
### Backend API additions
Keep existing endpoints working. Add a UI-oriented read model rather than
forcing the browser to join unrelated raw endpoints itself.
| Endpoint | Purpose |
|---|---|
| `GET /v1/ui/overview` | Tasks, workers, orchestration health, and active-session summaries in one pollable response. |
| `GET /v1/ui/tasks/{id}` | Full task detail, ordered lifecycle events, active session, blocker, artifact refs, and available actions. |
| `GET /v1/ui/tasks/{id}/capture?source=recent` | Current pane capture plus revision/timestamp; never silently returns stale text as live. |
| `GET /v1/artifacts/{ref}` | Existing artifact fetch, with content type supplied by the UI read model where known. |
| `POST /v1/ui/tasks` | Create task; validates the same fields as `POST /v1/tasks`. |
| `POST /v1/ui/tasks/{id}/actions/{action}` | Narrow action wrapper for handoff request, approval grant/deny, release, block, and completion; returns the resulting task/session state. |
The raw lifecycle endpoints stay available for harness/worker use. The UI
wrapper is responsible for describing whether an action is currently allowed,
why it is unavailable, and what evidence/body it needs.
## Correct approval design
The approval UI must reflect the real permission prompt, not the existing
event-only `ApprovalGranted`/`ApprovalDenied` records.
1. Add a structured `PendingApproval` model:
```ts
type PendingApproval = {
kind: "shell" | "edit" | "unknown";
summary: string;
command?: string;
diff?: string;
paneId: string;
captureRevision: number;
detectedAt: string;
};
```
2. Parse the current `pane.read` text worker-side. Preserve the exact shell
command or proposed edit/diff shown by the harness. If parsing is
uncertain, return `kind: "unknown"` with the raw relevant excerpt and
disable the grant button; do not guess.
3. Make approval a real execution command. `grant` sends the correct
`agent.send_keys`/`pane.send_keys` confirmation to the pane only if the
same `pane_id`, permission kind, and capture revision are still pending.
`deny` sends the explicit reject key sequence. Record the corresponding
`ApprovalGranted`/`ApprovalDenied` event only after the herdr action is
acknowledged.
4. The coordinator cannot directly control a workpc-owned Unix herdr socket.
Extend the federation worker protocol with a durable, worker-pulled
control-command stream (or command queue):
- coordinator queues `grant_approval` / `deny_approval` addressed to a
worker and task;
- worker validates its local current capture revision and sends the key;
- worker posts an acknowledged result or a stale/rejected result;
- the UI polls the final result and removes/enables controls accordingly.
This is required before rendering remote approval buttons as enabled.
A local coordinator-owned session may use the same command contract via a
local implementation, so UI behavior is identical across machines.
5. Render approvals in an accessible modal and task-detail panel: visible
command/diff, target pane, capture timestamp, keyboard focus trapped in
the dialog, `Approve` and `Reject` buttons, a stale-state warning, and no
implicit approval on refresh or Enter outside the focused button.
## UI layout
- **Overview:** summary counters, worker health strip, active/blocked session
cards, and the task board.
- **Task detail:** immutable task instruction, timeline, session telemetry,
live capture, handoff/report evidence, and the contextual action panel.
- **Session capture:** terminal-style, selectable text with an explicit
source label (`recent`, `scrollback`, etc.), refresh time, and truncation
indicator.
- **Approval dialog:** command or edit preview first, then consequence and
worker/pane metadata; approval controls only when `PendingApproval` is
current and actionable.
- **Workers:** capacity/heartbeat table, active lease/session link, and
visible offline/degraded reasons.
## Delivery phases
1. **Contracts and read model** — define Go UI DTOs, add overview/task-detail
endpoints, tests, and structured local session/capture/approval parsing.
2. **React shell** — scaffold Vite/React/TypeScript, embed production build,
implement overview, task detail, workers, polling, loading, and error
states.
3. **Evidence and controls** — artifact viewer, task creation, action
confirmation UX, and disabled-state explanations.
4. **Federated approvals** — worker command queue, stale-revision protection,
actual grant/deny execution, audit events, and UI result handling.
5. **Verification** — Go API tests, TypeScript unit/component tests, Playwright
flows, and a live workpc OpenCode E2E covering: task visible → permission
rendered with exact command → explicit approval → pane advances → action
result and lifecycle update visible.
## Acceptance criteria
- `npm run build`, `npm run lint`, TypeScript checks, Go build/vet/tests, and
UI browser tests pass in CI.
- Every rendered active session has a real source endpoint and a visible
degraded/error state if capture is unavailable.
- Every enabled control reaches a tested backend path; no placeholder buttons.
- A remote approval cannot be granted against a different or stale prompt.
- The approval panel displays the exact pending command or edit preview from
the current pane capture.
- The writer/reviewer workflow can be monitored from task creation through
completion without SSH or raw JSON-RPC inspection.