feat(agent-team): WebUI makeover — branching pipeline map + click-through task history #55

Merged
amoussa1229 merged 7 commits from feature/agent-team-webui-makeover into main 2026-06-23 23:50:17 +00:00
amoussa1229 commented 2026-06-23 21:20:14 +00:00 (Migrated from github.com)

What

A full makeover of the agent-team status dashboard (the LAN page on R720 :8770), delivering three asks:

  1. Click a task to see its history through each pipeline node — a per-task timeline (entry/exit timestamps, per-node duration, per-stage cost, Q&A, verdicts, plan).
  2. Easy to add new agent nodes — the map is auto-laid-out from the real LangGraph; adding a node in graph.py + one NODE_META entry makes it appear, no coordinates.
  3. Several trees branching off intake — nodes group into tree processes; the model supports multiple trees off the intake coordinator.

…plus an overall visual makeover: a React/Vite SPA replacing the inline HTML/SVG string.

How

Backend

  • db/schema.py: new task_transitions table (schema v3, additive migration + assert_task_transitions_ready startup check).
  • db/transitions.py: fail-soft, idempotent TransitionRecorder (one open row per thread; no double-count under LangGraph resume replay; terminal-close fills exited_at).
  • graph.py: build_graph gains an injected transition_recorder; a functools.wraps'd _instrument wrapper records each node entry while preserving the node signature so LangGraph still injects RunnableConfig (guards the prior config-injection bug). Wired at coordinator.py.
  • topology.py: derives the map from compiled.get_graph() + a NODE_META display sidecar (label/agent/tree/kind/gated); classifies edges spine/branch/loopback.
  • dashboard.py: new read-only FastAPI app (0.0.0.0:8770) serving /api/state (contract preserved + per-node live state), /api/topology, /api/task/{thread_id} (validated ^[A-Za-z0-9_-]{1,64}$, timeline+cost join, partial fallback), and the built SPA. Kept separate from the authed api.py to preserve that security boundary.
  • status_page.py: trimmed to the /api/state data layer; inline HTML/SVG renderer + stdlib server retired.

Frontend (web/, new): React 18 + Vite 5 + TS + React Flow + dagre (all pinned, no CDN). Dark three-pane layout: summary top bar, filterable task list, auto-laid pipeline map, task-history drawer. Polls /api/state every 4s. dist/ gitignored — built on the Mac, rsynced (box Node is too old for Vite 5+).

Ops: agent-team-status.service ExecStart → dashboard.serve (uvicorn).

Tests

  • Backend: 1204 pass, ruff clean. New test_transitions.py (idempotency, terminal close, fail-soft, B4 signature-preservation, end-to-end graph drive), test_topology.py (meta coverage, trees, edge kinds), test_dashboard.py (endpoints, validation, partial, cost join). test_status_page.py reworked to the data layer.
  • Frontend: 11 pass (Vitest + RTL: layout, TaskList filters/selection, TaskDrawer timeline).

Gates

  • ✅ GPT-4.1 cross-review (orchestrator cross_reviewer) on the _instrument wrapper, db/transitions.py, and /api/task — no BLOCKs; FIX/NIT findings applied (commit 6951bf2).
  • ✅ Pre-push deterministic scanners — PASS (pre-existing findings only, none in new files).
  • ⏳ /sh-security-review (mandatory agentic pass for the new untrusted-input surface) — outstanding, run before merge.
  • ⏳ /sh-deploy-r720 — deploy-before-merge: Hyper-V snapshot → ledger backup BEFORE rsync → whole-package rsync + web/dist → restart → assert task_transitions exists → post-deploy asset check. Confluence (host 20054017 + runbook 22609921) + project memory updates land with the deploy.

Notes

  • Branched off main (verified: prior dashboard/#47-#49 already merged; the feat/ws-activation-wiring assumption in project memory was stale).
  • Playwright e2e left as a follow-up NIT (Vitest+RTL covers the components).
## What A full makeover of the agent-team status dashboard (the LAN page on R720 `:8770`), delivering three asks: 1. **Click a task to see its history through each pipeline node** — a per-task timeline (entry/exit timestamps, per-node duration, per-stage cost, Q&A, verdicts, plan). 2. **Easy to add new agent nodes** — the map is **auto-laid-out from the real LangGraph**; adding a node in `graph.py` + one `NODE_META` entry makes it appear, no coordinates. 3. **Several trees branching off intake** — nodes group into `tree` processes; the model supports multiple trees off the `intake` coordinator. …plus an overall visual makeover: a React/Vite SPA replacing the inline HTML/SVG string. ## How **Backend** - `db/schema.py`: new **`task_transitions`** table (schema v3, additive migration + `assert_task_transitions_ready` startup check). - `db/transitions.py`: fail-soft, idempotent `TransitionRecorder` (one open row per thread; no double-count under LangGraph resume replay; terminal-close fills `exited_at`). - `graph.py`: `build_graph` gains an injected `transition_recorder`; a `functools.wraps`'d **`_instrument`** wrapper records each node entry while **preserving the node signature** so LangGraph still injects `RunnableConfig` (guards the prior config-injection bug). Wired at `coordinator.py`. - `topology.py`: derives the map from `compiled.get_graph()` + a `NODE_META` display sidecar (label/agent/tree/kind/gated); classifies edges spine/branch/loopback. - `dashboard.py`: **new read-only FastAPI app** (`0.0.0.0:8770`) serving `/api/state` (contract preserved + per-node live state), `/api/topology`, `/api/task/{thread_id}` (validated `^[A-Za-z0-9_-]{1,64}$`, timeline+cost join, partial fallback), and the built SPA. Kept **separate** from the authed `api.py` to preserve that security boundary. - `status_page.py`: trimmed to the `/api/state` data layer; inline HTML/SVG renderer + stdlib server retired. **Frontend** (`web/`, new): React 18 + Vite 5 + TS + React Flow + dagre (all pinned, no CDN). Dark three-pane layout: summary top bar, filterable task list, auto-laid pipeline map, task-history drawer. Polls `/api/state` every 4s. `dist/` gitignored — built on the Mac, rsynced (box Node is too old for Vite 5+). **Ops**: `agent-team-status.service` ExecStart → `dashboard.serve` (uvicorn). ## Tests - Backend: **1204 pass**, ruff clean. New `test_transitions.py` (idempotency, terminal close, fail-soft, **B4 signature-preservation**, end-to-end graph drive), `test_topology.py` (meta coverage, trees, edge kinds), `test_dashboard.py` (endpoints, validation, partial, cost join). `test_status_page.py` reworked to the data layer. - Frontend: **11 pass** (Vitest + RTL: layout, TaskList filters/selection, TaskDrawer timeline). ## Gates - ✅ **GPT-4.1 cross-review** (orchestrator `cross_reviewer`) on the `_instrument` wrapper, `db/transitions.py`, and `/api/task` — no BLOCKs; FIX/NIT findings applied (commit `6951bf2`). - ✅ Pre-push deterministic scanners — PASS (pre-existing findings only, none in new files). - ⏳ **`/sh-security-review`** (mandatory agentic pass for the new untrusted-input surface) — **outstanding**, run before merge. - ⏳ **`/sh-deploy-r720`** — deploy-before-merge: Hyper-V snapshot → ledger backup BEFORE rsync → whole-package rsync + `web/dist` → restart → assert `task_transitions` exists → post-deploy asset check. Confluence (host 20054017 + runbook 22609921) + project memory updates land with the deploy. ## Notes - Branched off `main` (verified: prior dashboard/#47-#49 already merged; the `feat/ws-activation-wiring` assumption in project memory was stale). - Playwright e2e left as a follow-up NIT (Vitest+RTL covers the components).
github-advanced-security[bot] (Migrated from github.com) reviewed 2026-06-23 21:21:12 +00:00
@ -0,0 +266,4 @@
"edges": [],
"error": type(exc).__name__,
}
return JSONResponse(payload, headers={"Cache-Control": "no-store"})
github-advanced-security[bot] (Migrated from github.com) commented 2026-06-23 21:21:11 +00:00

CodeQL / Information exposure through an exception

Stack trace information flows to this location and may be exposed to an external user.

Show more details

## CodeQL / Information exposure through an exception [Stack trace information](1) flows to this location and may be exposed to an external user. [Show more details](https://github.com/Sea-Haven-Industries/orchestrator/security/code-scanning/6)
amoussa1229 commented 2026-06-23 21:28:59 +00:00 (Migrated from github.com)

/sh-security-review — PASS (no block)

High-recall detector fan-out (injection, authz, secrets/data-exposure, web-client/XSS, logic — 5 fresh-context adversarial detectors) + a proof-or-kill verifier over the new surface (dashboard.py, db/transitions.py, graph._instrument, db/schema.py, topology.py, status_page.py, and the React SPA).

Gate: confirmed_critical=0, confirmed_high=0 → PASS.

Confirmed (1, low) — FIXED in a632a7d:

  • SEC-DASH-001 (CWE-209): /api/state embedded str(exc) of a sqlite/OS error into the payload, leaking the absolute DB path / table names to the unauthenticated LAN surface. Now returns type(exc).__name__ only (matching the task_detail hardening). Applied the same to /api/topology (SEC-DASH-003).

Verified safe (refuted / not request-reachable):

  • SQL injection — every query parameterized; thread_id validated ^[A-Za-z0-9_-]{1,64}$, anchored, used only as a bind param.
  • Path traversal — thread_id never touches the filesystem; StaticFiles mount is operator-configured + starlette realpath-guarded.
  • XSS — SPA renders all fetched task/Q&A/plan/verdict content as React text children; no dangerouslySetInnerHTML/innerHTML; encodeURIComponent on the id.
  • AuthZ — every route GET + mode=ro; the writable TransitionRecorder is never imported by the dashboard (RO-dashboard / RW-coordinator separation holds); /api/task IDOR is by-design within the documented LAN-only/unauth threat model (no privilege tier, secrets excluded).
  • Live-coordinator safety — _instrument adds no new crash path (recorder calls are fail-soft; GraphInterrupt propagates correctly).

Unverified / accepted tech-debt (downgraded by the verifier — latent, not request-reachable):

  • AT-TR-001 (recorder read-modify-write not BEGIN IMMEDIATE): latent race; no concurrent same-thread writer exists in the current wiring (ResumeWorker single-flight; start_task completes before any resume). Tracked as hardening.
  • AT-TR-002 (in-memory recorder conn): test-only path; production is file-backed + per-call close.
  • AT-SCHEMA-003 (migrate tail not atomic): self-heals on next startup; effect is a transient unindexed read.
  • INJ-01 / SEC-DASH-002 (db_path in URI/payload): operator-controlled, not request-reachable; design-consistent disclosure on the LAN surface.
## `/sh-security-review` — PASS (no block) High-recall detector fan-out (injection, authz, secrets/data-exposure, web-client/XSS, logic — 5 fresh-context adversarial detectors) + a proof-or-kill verifier over the new surface (`dashboard.py`, `db/transitions.py`, `graph._instrument`, `db/schema.py`, `topology.py`, `status_page.py`, and the React SPA). **Gate: `confirmed_critical=0`, `confirmed_high=0` → PASS.** **Confirmed (1, low) — FIXED** in `a632a7d`: - **SEC-DASH-001** (CWE-209): `/api/state` embedded `str(exc)` of a sqlite/OS error into the payload, leaking the absolute DB path / table names to the unauthenticated LAN surface. Now returns `type(exc).__name__` only (matching the `task_detail` hardening). Applied the same to `/api/topology` (SEC-DASH-003). **Verified safe (refuted / not request-reachable):** - SQL injection — every query parameterized; thread_id validated `^[A-Za-z0-9_-]{1,64}$`, anchored, used only as a bind param. - Path traversal — thread_id never touches the filesystem; StaticFiles mount is operator-configured + starlette realpath-guarded. - XSS — SPA renders all fetched task/Q&A/plan/verdict content as React text children; no `dangerouslySetInnerHTML`/`innerHTML`; `encodeURIComponent` on the id. - AuthZ — every route GET + `mode=ro`; the writable `TransitionRecorder` is never imported by the dashboard (RO-dashboard / RW-coordinator separation holds); `/api/task` IDOR is by-design within the documented LAN-only/unauth threat model (no privilege tier, secrets excluded). - Live-coordinator safety — `_instrument` adds no new crash path (recorder calls are fail-soft; `GraphInterrupt` propagates correctly). **Unverified / accepted tech-debt (downgraded by the verifier — latent, not request-reachable):** - AT-TR-001 (recorder read-modify-write not `BEGIN IMMEDIATE`): latent race; no concurrent same-thread writer exists in the current wiring (ResumeWorker single-flight; start_task completes before any resume). Tracked as hardening. - AT-TR-002 (in-memory recorder conn): test-only path; production is file-backed + per-call close. - AT-SCHEMA-003 (migrate tail not atomic): self-heals on next startup; effect is a transient unindexed read. - INJ-01 / SEC-DASH-002 (db_path in URI/payload): operator-controlled, not request-reachable; design-consistent disclosure on the LAN surface.
This repo is archived. You cannot comment on pull requests.
No description provided.