b57894b183
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
165 lines
7.8 KiB
Markdown
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.
|