Agent View Frontend
React components to support the development of the Agentic MATs initiative.
The UI is designed to work against the AI agent service — not only the local mock
server. Hosts compose full endpoint URLs from their service origin (see the demo).
AgentMonitorKanban lists/creates agents from those URLs; AgentView can be mounted
on its own and fetches checklist, resources, and chat for a single agent. Both talk to
the bundled AI service SDK (AgentServiceClient). The demo ships with presets for
mock, local (localhost:8001), and staging backends; see
Wiring the UI to the AI service.
Setup
Requirements: Node.js and Yarn.
yarn install
make demo-with-mock
Open http://localhost:4174/. The demo starts in Backend mode and loads agents from the local mock agent service automatically.
To run the demo UI only (no backend — use Debug options to switch to Mock data, Sparse, or Empty):
make demo
Or run the mock server and demo in separate terminals:
# Terminal 1
make mock-server
# Terminal 2
make demo
If you change mock-server code, restart it so the demo picks up API changes.
Use Debug options in the toolbar to switch backends:
| Preset | Service URL | Notes |
|---|---|---|
| Mock server | /api/ai-agent-service | Local seed data via the demo proxy. Bearer token optional. |
| Local AI service | http://localhost:8001 | Real AI agent service on your machine. Bearer token required. |
| Staging AI service | https://ai-agent-service.qa.arbor.engineering | QA environment. Bearer token required. |
You can also paste any service origin into the Service URL field (the demo
appends /api/v1/… when building endpoint URL props).
You can also configure the demo without opening the debug panel:
# Via URL query params (token is stripped from the address bar after load)
http://localhost:4174/?serviceUrl=http://localhost:3001&token=your-bearer-token
# Via environment variables (or copy `demo/.env.example` → `demo/.env`)
AGENT_SERVICE_URL=https://ai-agent-service.qa.arbor.engineering \
AGENT_SERVICE_TOKEN=your-bearer-token \
make demo
| Variable | Default | Purpose |
|---|---|---|
MOCK_SERVER_PORT | 3001 | Port for the mock agent service |
AGENT_SERVICE_PROXY_TARGET | http://localhost:3001 | Where the demo proxies /api/ai-agent-service |
AGENT_SERVICE_URL | /api/ai-agent-service | Initial service URL for the demo (overridable in Debug options) |
AGENT_SERVICE_TOKEN | (empty) | Initial bearer token for the demo |
PUSHER_REPOLL | auto if key+cluster set | true/false — initial state of the Use Pusher to repoll Debug toggle |
PUSHER_KEY | (empty) | Public Pusher app key (client subscribe) |
PUSHER_CLUSTER | (empty) | Pusher cluster (e.g. eu) |
PUSHER_CHANNEL_PREFIX | arbor-agent- | Channel prefix; full channel is {prefix}{agent.id} |
PUSHER_SECRET | (empty) | Not exposed to the browser — use only in local trigger scripts |
PUSHER_APP_ID | (empty) | Same as above; for server-side pusher package scripts |
Copy demo/.env.example to demo/.env and fill Pusher credentials. The Debug panel only exposes a Use Pusher to repoll toggle, which overrides PUSHER_REPOLL for the current session; the key, cluster and channel prefix are configured via env only and passed into AgentMonitorKanban as pusherKey / pusherCluster / pusherChannelPrefix. AgentView owns the Pusher client when those props are set (hosts may still inject subscribeToPusher to reuse an existing client). Vite loads env from the demo root; only an allowlisted set of prefixes is exposed to the client (PUSHER_SECRET is excluded).
Scripts
| Command | What it does |
|---|---|
yarn demo / make demo | Start the demo app with hot reload |
yarn demo:with-mock / make demo-with-mock | Start mock server + demo together |
yarn mock-server / make mock-server | Start the local AI agent service mock server |
yarn dev / make dev | Alias for demo |
yarn build / make build | Library build → dist/ |
yarn test / make test | Run Vitest test suite |
yarn test:watch / make test-watch | Vitest in watch mode |
yarn check-types / make typecheck | TypeScript check (no emit) |
yarn lint / make lint | ESLint check for TS/TSX |
yarn lint:fix | ESLint with auto-fix |
yarn style-lint / make style-lint | Stylelint check for SCSS |
make check | typecheck + lint + style-lint + test + build |
make clean | Remove dist and Vite cache |
Wiring the UI to the AI service
Library components take full endpoint URLs (no baseUrl prop). The host — e.g.
the demo — resolves a service origin and builds each URL.
AgentMonitorKanban— lists agents/definitions, card checklists, creates agents.AgentView— standalone workspace; fetches checklist, resources, and chat itself. Renaming the open agent (header title) usesPATCHon the existingagentUrl— no extra URL prop. Kanban card titles refresh on the next agents list reload.
In the demo — open Debug options, pick a backend, paste a bearer token, and
click Reload board. See demo/App.tsx for how baseUrl +
/api/v1 become the URL props.
In your app:
import { AgentMonitorKanban, AgentView } from 'agent-view-frontend';
import 'agent-view-frontend/style.css';
import '@arbor-education/design-system.components/dist/index.css';
const api = 'https://ai-agent-service.qa.arbor.engineering/api/v1';
<AgentMonitorKanban
jwt={jwt}
headers={{ 'X-Arbor-Application': 'agent-view' }}
agentDefinitionsUrl={`${api}/agent-definitions`}
agentsUrl={`${api}/agents`}
agentUrl={`${api}/agents/:invocationId`}
agentChecklistUrl={`${api}/agents/:invocationId/checklist`}
agentResourcesUrl={`${api}/agents/:invocationId/resources`}
agentTimelineUrl={`${api}/agents/:invocationId/agent-timeline`}
agentChatUrl={`${api}/agents/:invocationId/chat`}
agentConversationUrl={`${api}/conversations/:invocationId`}
/>;
// Or mount the workspace alone:
<AgentView
agent={agent}
invocationId={agent.invocation_id}
open
onClose={onClose}
jwt={jwt}
agentUrl={`${api}/agents/:invocationId`}
agentChecklistUrl={`${api}/agents/:invocationId/checklist`}
agentResourcesUrl={`${api}/agents/:invocationId/resources`}
agentTimelineUrl={`${api}/agents/:invocationId/agent-timeline`}
agentChatUrl={`${api}/agents/:invocationId/chat`}
agentConversationUrl={`${api}/conversations/:invocationId`}
/>;
Parameterised routes substitute the :invocationId placeholder. See the
AI service SDK section for the underlying client.
Hosting outside MIS (school picker)
The agent service resolves the school from X-Arbor-Application-Url, then
Referer, then Origin. A host that is not a MIS page (for example the portal)
has an Origin that matches no school, so the board must send the header itself.
Pass getMisInstances, an async getter like getToken, that returns a page of
the schools the user can open:
<AgentMonitorKanban
getToken={getToken}
getMisInstances={async ({pageNumber, pageSize}) => ({
items: [{name: 'Ash Grove Primary', url: 'https://ash-grove.uk.arbor.sc'}],
totalRecords: 1,
})}
{...endpointUrls}
/>;
pageNumberis 1-based. The board requests pages of 100 untiltotalRecordsis reached, so a host that does not paginate can ignore the args and return everything withtotalRecords: items.length.urlis the school's Suite DirectoryhomeUrl; it is sent asX-Arbor-Application-Urlon every request.- One school: it is used straight away and no dropdown is shown.
- Several schools: a School dropdown is shown in the board header and nothing is requested from the agent service until one is chosen. The choice is saved in
localStorageper user and can be changed from the same dropdown, which remounts the board for the new school. - No schools, or
getMisInstancesrejects: an empty or error state (with Retry) is shown and no agent-service requests are made. - Omit
getMisInstances(as MIS does) for the existing behaviour. It is also skipped ifheadersalready containsX-Arbor-Application-Url.
Standalone NewAgentModal
NewAgentModal normally renders as a child of AgentMonitorKanban (the board
supplies agentDefinitions and an onStart handler). It can also be mounted on
its own — e.g. as a server-rendered new-agent-modal widget — by passing
endpoint URLs instead. In this mode the modal loads its own definitions, creates
the agent, and navigates the page on close/success.
import { NewAgentModal } from 'agent-view-frontend';
const api = 'https://ai-agent-service.qa.arbor.engineering/api/v1';
<NewAgentModal
open
authToken={jwt}
headers={{ 'X-Arbor-Application': 'agent-view' }}
agentDefinitionsUrl={`${api}/agent-definitions`}
agentsUrl={`${api}/agents`}
closeUrl="/dashboard"
viewAgentUrlPrefix="/?/agent-ui/index/agent-id"
preselectAgent="school_trip_agents"
/>;
When hosting inside an MIS ui:window (or any shell that already provides a
dialog), pass embedded so the content renders inline instead of opening a
second design-system Modal (which portals to document.body):
<NewAgentModal open embedded {...standaloneProps} />
| Prop | Attribute | Required | Purpose |
|---|---|---|---|
agentDefinitionsUrl | agent-definitions-url | yes | Full URL to GET the agent definitions list. |
agentsUrl | agents-url | yes | Full URL to POST a new agent. |
authToken | auth-token | no | Bearer token attached to every request. |
headers | headers | no | Extra headers merged into every request. |
open | open | no | Whether the modal is open. |
embedded | — | no | Render inline without the design-system Modal portal. |
closeUrl | close-url | no | Page navigated to when the modal is closed. |
successUrl | success-url | no | Page navigated to after a successful start when viewAgentUrlPrefix is unset. |
viewAgentUrlPrefix | view-agent-url-prefix | no | After create, navigate to {prefix}/{id} using the new agent id. |
preselectAgent | preselect-agent | no | Pre-select a definition. Temporary: matches definition ref, which is mapped from API name. Switch to API code when the backend adds it. When matched, the select step shows Start the {display_name} instead of the dropdown. |
agentContext | — | no | Opaque object posted unchanged as agent_context on POST /agents. AgentMonitorKanban accepts the same prop and forwards it. |
Navigation uses window.location.assign. closeUrl/successUrl/viewAgentUrlPrefix are optional —
omit them (and pass onClose/onStart) to keep the modal fully controlled.
onStart receives (payload, { id }) after a successful create.
Publishing an alpha version
Ensure you are logged in to npm (npm whoami) and are a member of the @arbor-education npm organisation, then run:
npm run publish:alpha
This bumps the prerelease version (0.4.x-alpha.0, 0.4.x-alpha.1, …) and publishes under the alpha dist-tag. Must be run with npm run, not yarn — yarn intercepts the publish step and will prompt for yarn credentials instead.
The version bump modifies package.json — do not commit this change. Revert it before raising or merging your PR.
To install the published alpha in another project:
yarn add @arbor-education/agent-view-frontend@alpha
NPM usage
Install the package and its peer dependencies:
yarn add agent-view-frontend @arbor-education/design-system.components react react-dom
Import the component and styles:
import { AgentMonitorKanban } from 'agent-view-frontend';
import 'agent-view-frontend/style.css';
import '@arbor-education/design-system.components/dist/index.css';
function Page() {
const api = '/api/ai-agent-service/api/v1';
return (
<AgentMonitorKanban
agentsUrl={`${api}/agents`}
agentDefinitionsUrl={`${api}/agent-definitions`}
agentUrl={`${api}/agents/:invocationId`}
agentChecklistUrl={`${api}/agents/:invocationId/checklist`}
agentResourcesUrl={`${api}/agents/:invocationId/resources`}
agentTimelineUrl={`${api}/agents/:invocationId/agent-timeline`}
agentChatUrl={`${api}/agents/:invocationId/chat`}
agentConversationUrl={`${api}/conversations/:invocationId`}
/>
);
}
AI service SDK
The package ships a framework-agnostic client for the AI agent service
/api/v1/agents/* endpoints (works against the local mock server or the real AI service).
import {
AgentServiceClient,
createHttpChatConnectorFactory,
mapApiAgent,
mapApiChecklistResponse,
} from 'agent-view-frontend';
const client = new AgentServiceClient({
baseUrl: '/api/ai-agent-service', // or 'http://localhost:3001'
token: jwt, // Bearer token; "Bearer " prefix optional
});
// List / detail (mapped into the UI's Agent + checklist shapes)
const agents = (await client.listAgents()).map(mapApiAgent);
const detail = await client.getAgent(invocationId);
const checklist = mapApiChecklistResponse(invocationId, detail);
// Lifecycle
await client.createAgent('school_trip_agents');
await client.updateAgent(agentId, { invocationName: 'Year 6 trip' });
await client.pauseAgent(agentId);
await client.archiveAgent(agentId);
// Streaming chat (SSE)
const connectorFor = createHttpChatConnectorFactory(client);
const connector = connectorFor(agents[0]);
const reply = await connector.sendMessage('What have you done so far?');
AgentServiceClient also accepts headers (merged into every request) and per-operation
urls overrides — the same options AgentMonitorKanban forwards from its headers and
urls props.
client.chat(agentId, request, { onEvent }) resolves with the final SSE event
and invokes onEvent per frame. Non-2xx responses and event: error frames
throw an AgentServiceError carrying status and the parsed detail.
The demo wires Mock, Local, and Staging backends through this SDK — see
demo/backendApi.ts and
Wiring the UI to the AI service.