Ordo
Initial public release
6425ec7
import { useEffect, useMemo, useState } from 'react';
import {
Activity,
AlertTriangle,
Bot,
CheckCircle2,
CirclePause,
CirclePlay,
ClipboardCheck,
ExternalLink,
RefreshCw,
ShieldCheck,
Sparkles,
TerminalSquare,
} from 'lucide-react';
const API_BASE = import.meta.env.VITE_SESSION_AMPLIFIER_BASE_URL || '/session-amplifier';
const POLL_MS = 15000;
async function fetchJson(path) {
const response = await fetch(API_BASE + path);
if (!response.ok) {
throw new Error(response.status + ' ' + response.statusText);
}
return response.json();
}
function formatAge(value) {
if (!value) return 'unknown';
const at = new Date(value);
if (Number.isNaN(at.getTime())) return 'unknown';
const seconds = Math.max(0, Math.floor((Date.now() - at.getTime()) / 1000));
if (seconds < 60) return seconds + 's';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return minutes + 'm';
const hours = Math.floor(minutes / 60);
if (hours < 36) return hours + 'h';
return Math.floor(hours / 24) + 'd';
}
function compactText(value, limit = 110) {
const text = String(value || '').replace(/\s+/g, ' ').trim();
if (!text) return '';
return text.length > limit ? text.slice(0, limit - 1).trimEnd() + '…' : text;
}
function looksLikeOpaqueId(value) {
return /^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(String(value || '').trim());
}
function firstMeaningfulEvent(events = []) {
return events.find((event) => {
const text = event?.details || event?.summary || event?.clean_text || event?.preview || '';
return text && text !== 'assistant' && (event.role === 'user' || event.event_type === 'user_message');
}) || events.find((event) => {
const text = event?.details || event?.summary || event?.clean_text || event?.preview || '';
return text && text !== 'assistant';
});
}
function sessionTextLabel(event) {
if (!event) return '';
const raw = event.details || event.summary || event.clean_text || event.preview || '';
const firstLine = String(raw).split('\n').find((line) => line.trim()) || '';
const cronMatch = firstLine.match(/^\[cron:[^\s\]]+\s+([^\]]+)\]\s*(.*)$/);
if (cronMatch) {
return compactText(cronMatch[1] + ': ' + cronMatch[2], 96);
}
return compactText(firstLine.replace(/^#+\s*/, ''), 96);
}
function quotedHint(event) {
if (!event) return '';
const raw = event.details || event.summary || event.clean_text || event.preview || '';
const quote = String(raw).match(/["“']([^"”']{8,130})["”']/);
if (quote?.[1]) return compactText('"' + quote[1] + '"', 120);
const label = sessionTextLabel(event);
return label ? compactText('"' + label + '"', 120) : '';
}
function shortSessionId(session) {
return String(session.session_id || '').slice(0, 8) || 'unknown';
}
function pickTitle(session, events = []) {
const configuredTitle = (
session.display_title ||
session.displayTitle ||
session.display_name ||
session.displayName ||
session.origin_label
);
if (configuredTitle && !looksLikeOpaqueId(configuredTitle)) {
return { title: configuredTitle, hint: quotedHint(firstMeaningfulEvent(events)) };
}
const firstEvent = firstMeaningfulEvent(events);
const derivedTitle = sessionTextLabel(firstEvent);
return {
title: derivedTitle || 'Session ' + shortSessionId(session),
hint: quotedHint(firstEvent),
};
}
function deriveState(session, events = []) {
if (session.derived_state) return session.derived_state;
if (session.health === 'error') return 'error';
const latest = [...events].reverse().find(Boolean);
if (!latest) return 'idle';
if (latest.is_error) return 'error';
if (latest.tool_name) return 'active';
if ((latest.preview || latest.clean_text || '').toLowerCase().includes('permission')) return 'waiting';
return latest.role === 'assistant' || latest.role === 'user' ? 'active' : 'idle';
}
function stateMeta(state, needsPermission) {
if (needsPermission || state === 'waiting') {
return { label: 'Waiting', tone: 'waiting', Icon: CirclePause };
}
if (state === 'active') return { label: 'Active', tone: 'active', Icon: Activity };
if (state === 'error') return { label: 'Error', tone: 'error', Icon: AlertTriangle };
return { label: 'Idle', tone: 'idle', Icon: CheckCircle2 };
}
function MetricCard({ icon: Icon, label, value, sublabel, tone = 'neutral' }) {
return (
<section className={'metric metric--' + tone}>
<div className="metric__icon"><Icon size={18} /></div>
<div>
<div className="metric__value">{value}</div>
<div className="metric__label">{label}</div>
{sublabel ? <div className="metric__sublabel">{sublabel}</div> : null}
</div>
</section>
);
}
function SessionCard({ session, events }) {
const state = deriveState(session, events);
const meta = stateMeta(state, session.needs_permission);
const latest = [...events].reverse().find(Boolean);
const currentTool = session.current_tool_name || latest?.tool_name || 'none';
const { title, hint } = pickTitle(session, events);
return (
<article className={'session-card session-card--' + meta.tone}>
<header className="session-card__head">
<div className="session-card__identity">
<span className={'status-pill status-pill--' + meta.tone}>
<meta.Icon size={14} />
{meta.label}
</span>
<h2 title={title}>{title}</h2>
{hint ? <p className="session-card__quote">{hint}</p> : null}
</div>
<span className="session-card__age">{formatAge(session.last_activity_at || session.updated_at || latest?.timestamp)}</span>
</header>
<div className="session-card__meta">
<span><Bot size={14} />{session.agent_id || 'unknown'}</span>
<span><TerminalSquare size={14} />{currentTool}</span>
<span><Activity size={14} />{shortSessionId(session)} · {events.length} events</span>
</div>
<p className="session-card__preview">
{latest?.preview || latest?.clean_text || session.active_reason || 'No recent transcript preview'}
</p>
<footer className="session-card__actions">
<button type="button" title="Open transcript when a route is wired" disabled>
<ExternalLink size={15} />
Open
</button>
<button type="button" title="Run control requires an action endpoint" disabled>
<CirclePlay size={15} />
Run
</button>
<button type="button" title="Pause/resume requires an action endpoint" disabled>
<CirclePause size={15} />
Pause
</button>
</footer>
</article>
);
}
function App() {
const [health, setHealth] = useState(null);
const [bulk, setBulk] = useState({ sessions: [], activity: {} });
const [skills, setSkills] = useState(null);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
async function refresh() {
setError('');
try {
const results = await Promise.allSettled([
fetchJson('/health'),
fetchJson('/sessions/active-bulk?limit=30&activity_limit=80'),
fetchJson('/review/skills'),
]);
const [healthResult, bulkResult, skillsResult] = results;
if (healthResult.status === 'fulfilled') setHealth(healthResult.value);
if (bulkResult.status === 'fulfilled') setBulk(bulkResult.value);
if (skillsResult.status === 'fulfilled') setSkills(skillsResult.value);
const rejected = [healthResult, bulkResult].find((result) => result.status === 'rejected');
if (rejected) throw rejected.reason;
} catch (err) {
setError(err.message || String(err));
} finally {
setLoading(false);
}
}
useEffect(() => {
refresh();
const timer = window.setInterval(refresh, POLL_MS);
return () => window.clearInterval(timer);
}, []);
const sessions = bulk.sessions || [];
const activity = bulk.activity || {};
const metrics = useMemo(() => {
const states = sessions.map((session) => deriveState(session, activity[session.session_id] || []));
const active = states.filter((state) => state === 'active').length;
const waiting = sessions.filter((session, index) => session.needs_permission || states[index] === 'waiting').length;
const errors = states.filter((state) => state === 'error').length;
const toolHeavy = sessions.filter((session) => (activity[session.session_id] || []).filter((row) => row.tool_name).length >= 5).length;
const candidateCount = Array.isArray(skills?.candidates) ? skills.candidates.length : Array.isArray(skills?.recommendations) ? skills.recommendations.length : 0;
return { active, waiting, errors, toolHeavy, candidateCount };
}, [sessions, activity, skills]);
return (
<main className="app-shell">
<header className="topbar">
<div>
<p className="eyebrow">OpenClaw Ops</p>
<h1>Action Dashboard</h1>
</div>
<button className="refresh-button" type="button" onClick={refresh}>
<RefreshCw size={16} className={loading ? 'spin' : ''} />
Refresh
</button>
</header>
{error ? (
<section className="alert">
<AlertTriangle size={16} />
<span>{error}</span>
</section>
) : null}
<section className="metrics-grid">
<MetricCard icon={ShieldCheck} label="Amplifier" value={health?.status || 'unknown'} sublabel={(health?.sessions ?? 0) + ' indexed sessions'} tone={health?.status === 'ok' ? 'good' : 'warn'} />
<MetricCard icon={Activity} label="Active" value={metrics.active} sublabel={sessions.length + ' recent sessions'} tone="info" />
<MetricCard icon={CirclePause} label="Waiting" value={metrics.waiting} sublabel="permission or stalled state" tone={metrics.waiting ? 'warn' : 'neutral'} />
<MetricCard icon={AlertTriangle} label="Errors" value={metrics.errors} sublabel="recent session state" tone={metrics.errors ? 'bad' : 'neutral'} />
<MetricCard icon={TerminalSquare} label="Tool Heavy" value={metrics.toolHeavy} sublabel="5+ tool events" tone="neutral" />
<MetricCard icon={ClipboardCheck} label="Skill Signals" value={metrics.candidateCount || 'review'} sublabel="from skill review API" tone="neutral" />
</section>
<section className="workspace-grid">
<section className="panel panel--wide">
<div className="panel__head">
<h2>Live Sessions</h2>
<span>{bulk.generated_at ? 'Updated ' + formatAge(bulk.generated_at) + ' ago' : 'Not loaded'}</span>
</div>
<div className="sessions-grid">
{sessions.length ? (
sessions.map((session) => (
<SessionCard
key={session.session_id}
session={session}
events={activity[session.session_id] || []}
/>
))
) : (
<div className="empty-state">
<Sparkles size={18} />
<span>No recent sessions returned.</span>
</div>
)}
</div>
</section>
<aside className="panel">
<div className="panel__head">
<h2>Promotion Lane</h2>
<span>advisory</span>
</div>
<div className="lane-list">
<div className="lane-row">
<ShieldCheck size={16} />
<span>Installed skills may be used automatically when the match is safe.</span>
</div>
<div className="lane-row">
<ClipboardCheck size={16} />
<span>New installs, agent allowlists, and config changes stay approval-gated.</span>
</div>
<div className="lane-row">
<ExternalLink size={16} />
<span>Approval packets are summarized by scripts/openclaw_skill_approval_status.py.</span>
</div>
</div>
</aside>
</section>
</main>
);
}
export default App;