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

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: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:

    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.