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
7.8 KiB
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:embedand served at/;/v1/*remains API-only. The Go server servesindex.htmlas 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.
-
Add a structured
PendingApprovalmodel:type PendingApproval = { kind: "shell" | "edit" | "unknown"; summary: string; command?: string; diff?: string; paneId: string; captureRevision: number; detectedAt: string; }; -
Parse the current
pane.readtext worker-side. Preserve the exact shell command or proposed edit/diff shown by the harness. If parsing is uncertain, returnkind: "unknown"with the raw relevant excerpt and disable the grant button; do not guess. -
Make approval a real execution command.
grantsends the correctagent.send_keys/pane.send_keysconfirmation to the pane only if the samepane_id, permission kind, and capture revision are still pending.denysends the explicit reject key sequence. Record the correspondingApprovalGranted/ApprovalDeniedevent only after the herdr action is acknowledged. -
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_approvaladdressed 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.
- coordinator queues
-
Render approvals in an accessible modal and task-detail panel: visible command/diff, target pane, capture timestamp, keyboard focus trapped in the dialog,
ApproveandRejectbuttons, 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
PendingApprovalis current and actionable. - Workers: capacity/heartbeat table, active lease/session link, and visible offline/degraded reasons.
Delivery phases
- Contracts and read model — define Go UI DTOs, add overview/task-detail endpoints, tests, and structured local session/capture/approval parsing.
- React shell — scaffold Vite/React/TypeScript, embed production build, implement overview, task detail, workers, polling, loading, and error states.
- Evidence and controls — artifact viewer, task creation, action confirmation UX, and disabled-state explanations.
- Federated approvals — worker command queue, stale-revision protection, actual grant/deny execution, audit events, and UI result handling.
- 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.