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
This commit is contained in:
+164
@@ -0,0 +1,164 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user