Skip to content
dina-karPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

Repository files navigation

Agent UI

A backend-agnostic agent canvas SDK. The transcript, tool timeline, approval gates, and workspace panel of a coding-agent UI, packaged so they can sit on top of any orchestrator — LangGraph, Mastra, a raw WebSocket, or a fixture replayer.

The visual and interaction target is the OpenHands agent canvas (MIT — see NOTICE). The architecture is not: OpenHands couples its UI directly to its own Python event stream, and the point of this package set is that nothing above the transport knows what is on the other end.

Packages

Package What it is
@agent-ui/protocol The normalized event model. Types only, zero runtime deps.
@agent-ui/core Timeline reducer, stores, and every piece of logic that must not belong to a framework.
@agent-ui/tokens Tailwind v4 design tokens and base styles.
@agent-ui/react React 19 bindings and components.
@agent-ui/angular Angular 22 standalone components and signal bindings.
@agent-ui/transport-mock Scripted fixture replayer — the development backend.

apps/react-playground and apps/angular-playground drive the whole thing off the same fixtures, with identical toolbars. Any visible difference between the two is a parity bug rather than a design choice.

The one rule

core contains all logic and imports no framework. React and Angular are thin subscribers. This is enforced by lint (@nx/enforce-module-boundaries over scope:* tags), not by discipline.

It is also what makes maintaining two framework packages tractable. Event reduction, action↔observation pairing, streaming-delta merging, panel resize maths, status derivation, and event→title formatting are written once and bound twice. When a behaviour cannot be pushed into core, that is a signal to redesign it rather than fork it.

transport → normalizer → core store (vanilla) → framework binding → components

Two decisions worth knowing about

Observations link to actions explicitly. ObservationEvent.actionId is required rather than inferred from adjacency. Under async multi-agent orchestration an action and its result are routinely separated by unrelated events from other agents, so adjacency is not a correlation strategy.

Titles are data, not markup. getEventContent emits typed segments (text / code / path / emphasis) plus an i18n key and params. React maps them to spans; Angular maps them to spans. The decision about what is a path lives in core, so the two renderers cannot drift.

Development

pnpm install
pnpm nx serve @org/react-playground     # the canvas, driven by fixtures
pnpm nx serve @org/angular-playground   # the same canvas, same fixtures (:4300)
pnpm check                              # lint + test + build + typecheck
pnpm nx sync                            # regenerate TS project references

Fixtures live in packages/transport-mock/src/lib/scripts.ts and cover a clean run, long streaming, a failing tool, an approval gate, a hung tool, a rate limit, artifacts, and a 60-step transcript. They are the primary development loop: every state that is awkward to provoke against a live agent is one dropdown away.

Testing

packages/react/src/lib/canvas/agent-canvas.spec.tsx and packages/angular/src/lib/agent-canvas.component.spec.ts drive the same fixtures through both implementations and assert the same observable behaviour. That pairing is the parity gate — a divergence there means one of the thin framework layers has drifted.

The React tests render under <StrictMode> deliberately. StrictMode double-invokes effects (mount → cleanup → mount) to expose components that cannot survive being torn down and rebuilt, and every real app runs under it. Rendering without it once hid a controller whose transport subscription could only be established once: after StrictMode's second pass it received no events at all, leaving an empty transcript and a permanently disabled composer. Keep the wrapper.

Known constraints

  • Angular and TypeScript project references do not mix. This workspace uses the TS-solution setup (composite, emitDeclarationOnly), which the Angular compiler rejects outright. packages/angular/tsconfig*.json override those flags locally. It works, but it is a seam to be aware of when upgrading Nx or Angular.
  • The Angular app needs three workspace-level accommodations, all in service of the same root cause as above. Its typecheck target is overridden because the inferred one runs tsc --build --emitDeclarationOnly, which an application cannot satisfy. Its tsconfig.app.json sets no rootDir, because customConditions: ["@org/source"] makes it compile SDK sources that legitimately sit outside src. And pnpm-workspace.yaml public-hoists @agent-ui/*, zustand and marked, because Angular's dev server serves from a synthetic .angular/vite-root/ at the workspace root where pnpm's strict layout exposes nothing — this affects nx serve only; the production build resolves correctly without it.
  • ESLint is pinned to 9.x. eslint-plugin-react declares peer support only through ^9.7; on ESLint 10 it crashes on removed APIs.
  • Markdown rendering differs by necessity: React uses react-markdown (never produces HTML), Angular compiles to HTML and relies on Angular's sanitizer for [innerHTML]. The Angular path must never be paired with bypassSecurityTrustHtml.

Not yet built

  • Workspace tab content beyond the artifact viewer: Monaco diff, xterm terminal, embedded browser.
  • Real transport adapters (WebSocket / SSE / ACP / Mastra). The mock transport proves the AgentTransport seam; a real adapter is mechanical from there.
  • Storybook, and virtualization for very long transcripts.
  • A Playwright smoke test driving each fixture to completion in both playgrounds. Parity is currently asserted at the component level; asserting it against the two running apps would close the loop.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages