feat: ship agent-first mission control v0.2.57
This commit is contained in:
@@ -0,0 +1,966 @@
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
Activity,
|
||||
ArrowRight,
|
||||
BadgeCheck,
|
||||
Bell,
|
||||
BookOpen,
|
||||
Bot,
|
||||
Boxes,
|
||||
Brain,
|
||||
CalendarDays,
|
||||
Check,
|
||||
CheckCircle2,
|
||||
ChevronRight,
|
||||
CircleDollarSign,
|
||||
Clock3,
|
||||
Code2,
|
||||
Cpu,
|
||||
FileCheck2,
|
||||
FolderKanban,
|
||||
History,
|
||||
KeyRound,
|
||||
LayoutDashboard,
|
||||
Link2,
|
||||
ListTodo,
|
||||
MessageSquareText,
|
||||
Network,
|
||||
Pause,
|
||||
Play,
|
||||
RotateCcw,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
SlidersHorizontal,
|
||||
Sparkles,
|
||||
Square,
|
||||
Terminal,
|
||||
TriangleAlert,
|
||||
UserRound,
|
||||
Wrench,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { Modal } from "./Modal.jsx";
|
||||
import {
|
||||
agents,
|
||||
approval,
|
||||
navGroups,
|
||||
runDetails,
|
||||
timelineEvents,
|
||||
} from "./data.js";
|
||||
|
||||
const iconMap = {
|
||||
dashboard: LayoutDashboard,
|
||||
runs: Activity,
|
||||
agents: Bot,
|
||||
projects: FolderKanban,
|
||||
tasks: ListTodo,
|
||||
memory: Brain,
|
||||
docs: BookOpen,
|
||||
models: SlidersHorizontal,
|
||||
activity: Activity,
|
||||
calendar: CalendarDays,
|
||||
security: ShieldCheck,
|
||||
incidents: TriangleAlert,
|
||||
notifications: Bell,
|
||||
iris: Sparkles,
|
||||
programmer: Code2,
|
||||
reviewer: FileCheck2,
|
||||
executor: Terminal,
|
||||
};
|
||||
|
||||
function Sidebar({ onPrototypeNav }) {
|
||||
return (
|
||||
<aside className="sidebar" aria-label="Nexus navigation">
|
||||
<div className="brand">
|
||||
<span className="brand-mark" aria-hidden="true">
|
||||
<Sparkles size={22} />
|
||||
</span>
|
||||
<span>
|
||||
<strong>NEXUS</strong>
|
||||
<small>Mission Control</small>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<nav className="navigation">
|
||||
{navGroups.map((group) => (
|
||||
<section className="nav-group" key={group.label}>
|
||||
<h2>{group.label}</h2>
|
||||
<div className="nav-list">
|
||||
{group.items.map((item) => {
|
||||
const Icon = iconMap[item.icon];
|
||||
return (
|
||||
<button
|
||||
className={`nav-item${item.active ? " is-active" : ""}`}
|
||||
type="button"
|
||||
key={item.id}
|
||||
aria-current={item.active ? "page" : undefined}
|
||||
onClick={() => onPrototypeNav(item.label, item.active)}
|
||||
>
|
||||
<Icon size={17} strokeWidth={1.8} aria-hidden="true" />
|
||||
<span>{item.label}</span>
|
||||
{item.count !== undefined ? (
|
||||
<span className={`nav-count${item.count > 0 ? " has-items" : ""}`}>
|
||||
{item.count}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="sidebar-footer">
|
||||
<button
|
||||
className="nav-item"
|
||||
type="button"
|
||||
onClick={() => onPrototypeNav("Settings", false)}
|
||||
>
|
||||
<Settings size={17} strokeWidth={1.8} aria-hidden="true" />
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
<button
|
||||
className="owner"
|
||||
type="button"
|
||||
onClick={() => onPrototypeNav("Owner profile", false)}
|
||||
>
|
||||
<span className="owner-avatar">B</span>
|
||||
<span>
|
||||
<strong>Bao</strong>
|
||||
<small>Owner</small>
|
||||
</span>
|
||||
<ChevronRight size={16} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function Topbar({ runState, onPause, onStop, onAskIris }) {
|
||||
const stateLabel =
|
||||
runState === "paused" ? "Paused" : runState === "stopped" ? "Stopped" : "Running";
|
||||
|
||||
return (
|
||||
<header className="topbar">
|
||||
<div className="run-identity">
|
||||
<span className={`runtime-dot runtime-dot--${runState}`} aria-hidden="true" />
|
||||
<span>
|
||||
<small>Run</small>
|
||||
<strong>{runDetails.title}</strong>
|
||||
</span>
|
||||
<span className={`run-state run-state--${runState}`}>{stateLabel}</span>
|
||||
</div>
|
||||
|
||||
<dl className="topbar-meta">
|
||||
<div>
|
||||
<dt>Elapsed</dt>
|
||||
<dd>{runDetails.elapsed}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Started</dt>
|
||||
<dd>{runDetails.started}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Session</dt>
|
||||
<dd>{runDetails.sessionId}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<div className="topbar-actions">
|
||||
<button
|
||||
className="button button--secondary"
|
||||
type="button"
|
||||
onClick={onPause}
|
||||
disabled={runState === "stopped"}
|
||||
>
|
||||
{runState === "paused" ? (
|
||||
<Play size={17} aria-hidden="true" />
|
||||
) : (
|
||||
<Pause size={17} aria-hidden="true" />
|
||||
)}
|
||||
{runState === "paused" ? "Resume" : "Pause"}
|
||||
</button>
|
||||
<button
|
||||
className="button button--danger"
|
||||
type="button"
|
||||
onClick={onStop}
|
||||
disabled={runState === "stopped"}
|
||||
>
|
||||
<Square size={15} aria-hidden="true" />
|
||||
Stop
|
||||
</button>
|
||||
<button className="button button--primary" type="button" onClick={onAskIris}>
|
||||
<Sparkles size={17} aria-hidden="true" />
|
||||
Ask Iris
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
function AgentNode({ agent, selected, approvalState, onSelect }) {
|
||||
const Icon = iconMap[agent.icon];
|
||||
const status =
|
||||
agent.id === "executor" && approvalState === "approved"
|
||||
? "Preparing artifacts"
|
||||
: agent.status;
|
||||
const progress =
|
||||
agent.id === "executor" && approvalState === "approved" ? 16 : agent.progress;
|
||||
const state =
|
||||
agent.id === "executor" && approvalState === "approved" ? "working" : agent.state;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`agent-node agent-node--${agent.id} state-${state}${
|
||||
selected ? " is-selected" : ""
|
||||
}`}
|
||||
type="button"
|
||||
aria-pressed={selected}
|
||||
aria-label={`Open ${agent.name} details`}
|
||||
onClick={() => onSelect(agent.id)}
|
||||
data-testid={`agent-${agent.id}`}
|
||||
>
|
||||
<span className="node-heading">
|
||||
<span className="agent-icon">
|
||||
<Icon size={18} strokeWidth={1.8} aria-hidden="true" />
|
||||
</span>
|
||||
<span className="node-title">
|
||||
<strong>{agent.name}</strong>
|
||||
<small>{agent.role}</small>
|
||||
</span>
|
||||
<span className={`status-dot status-dot--${state}`} aria-hidden="true" />
|
||||
</span>
|
||||
<span className="node-status">{status}</span>
|
||||
<span className="progress-track" aria-hidden="true">
|
||||
<span className="progress-value" style={{ width: `${progress}%` }} />
|
||||
</span>
|
||||
<span className="node-footer">
|
||||
<span>{state === "completed" ? "Completed" : state === "waiting" ? "Waiting" : "Active"}</span>
|
||||
<span>{progress}%</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function OrchestrationMap({
|
||||
selectedAgent,
|
||||
approvalState,
|
||||
runState,
|
||||
onSelectAgent,
|
||||
onOpenApproval,
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className={`orchestration-map run-${runState}`}
|
||||
aria-label="Live agent orchestration"
|
||||
data-testid="run-canvas"
|
||||
>
|
||||
<div className="connection connection--iris-programmer" aria-hidden="true" />
|
||||
<div className="connection connection--iris-reviewer" aria-hidden="true" />
|
||||
<div className="connection connection--iris-executor" aria-hidden="true" />
|
||||
<div className="connection connection--executor-approval" aria-hidden="true" />
|
||||
<div className="connection connection--approval-deploy" aria-hidden="true" />
|
||||
|
||||
{Object.values(agents).map((agent) => (
|
||||
<AgentNode
|
||||
agent={agent}
|
||||
key={agent.id}
|
||||
selected={selectedAgent === agent.id}
|
||||
approvalState={approvalState}
|
||||
onSelect={onSelectAgent}
|
||||
/>
|
||||
))}
|
||||
|
||||
<button
|
||||
className={`approval-node approval-node--${approvalState}`}
|
||||
type="button"
|
||||
onClick={onOpenApproval}
|
||||
data-testid="approval-node"
|
||||
>
|
||||
<span className="approval-kicker">
|
||||
{approvalState === "pending" ? "Pending approval" : "Approval granted"}
|
||||
</span>
|
||||
<strong>{approval.title}</strong>
|
||||
<small>
|
||||
{approvalState === "pending"
|
||||
? "Owner decision required before Executor continues."
|
||||
: "Executor may prepare versioned deployment artifacts."}
|
||||
</small>
|
||||
<span className="approval-meta">
|
||||
<span>{approval.requestedBy}</span>
|
||||
<span>{approvalState === "pending" ? "Review details" : "View decision"}</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div className={`deploy-node deploy-node--${approvalState}`}>
|
||||
<span>{approvalState === "pending" ? "Pending" : "In progress"}</span>
|
||||
<strong>Prepare deployment</strong>
|
||||
<small>
|
||||
{approvalState === "pending" ? "Awaiting owner approval" : "Executor started"}
|
||||
</small>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function EventRail({ approvalState, onOpenEvent }) {
|
||||
const visibleEvents = useMemo(() => {
|
||||
if (approvalState === "approved") {
|
||||
return [
|
||||
...timelineEvents,
|
||||
{
|
||||
id: "event-approved",
|
||||
time: "17:21:12",
|
||||
actor: "Bao",
|
||||
title: "Approval granted",
|
||||
detail: "Executor may prepare deployment artifacts.",
|
||||
type: "success",
|
||||
correlation: "evt_01JZR7K0B4",
|
||||
},
|
||||
];
|
||||
}
|
||||
return timelineEvents;
|
||||
}, [approvalState]);
|
||||
|
||||
return (
|
||||
<section className="event-rail" aria-labelledby="event-rail-title">
|
||||
<header>
|
||||
<div>
|
||||
<p className="eyebrow">Authoritative mock timeline</p>
|
||||
<h2 id="event-rail-title">Run events</h2>
|
||||
</div>
|
||||
<span>{visibleEvents.length} events</span>
|
||||
</header>
|
||||
<div className="event-list">
|
||||
{visibleEvents.map((event) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`event-row event-row--${event.type}`}
|
||||
key={event.id}
|
||||
onClick={() => onOpenEvent(event)}
|
||||
data-testid={`event-${event.id}`}
|
||||
>
|
||||
<time>{event.time}</time>
|
||||
<span className="event-marker" aria-hidden="true" />
|
||||
<span className="event-copy">
|
||||
<strong>{event.actor}</strong>
|
||||
<span>{event.title}</span>
|
||||
<small>{event.detail}</small>
|
||||
</span>
|
||||
<ChevronRight size={16} aria-hidden="true" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function DetailRow({ icon: Icon, label, value, actionLabel, onClick }) {
|
||||
const content = (
|
||||
<>
|
||||
<Icon size={15} strokeWidth={1.8} aria-hidden="true" />
|
||||
<span>{label}</span>
|
||||
<strong>{value}</strong>
|
||||
{onClick ? <ChevronRight size={15} aria-hidden="true" /> : null}
|
||||
</>
|
||||
);
|
||||
|
||||
if (onClick) {
|
||||
return (
|
||||
<button
|
||||
className="detail-row detail-row--action"
|
||||
type="button"
|
||||
aria-label={actionLabel}
|
||||
onClick={onClick}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="detail-row">{content}</div>;
|
||||
}
|
||||
|
||||
function DetailPanel({
|
||||
selectedAgentId,
|
||||
approvalState,
|
||||
runState,
|
||||
onSelectAgent,
|
||||
onOpenApproval,
|
||||
onOpenProvider,
|
||||
onOpenSession,
|
||||
onUndoApproval,
|
||||
onRestart,
|
||||
}) {
|
||||
const selectedAgent = agents[selectedAgentId];
|
||||
const Icon = iconMap[selectedAgent.icon];
|
||||
const executorApproved = selectedAgentId === "executor" && approvalState === "approved";
|
||||
const selectedState = executorApproved ? "working" : selectedAgent.state;
|
||||
|
||||
return (
|
||||
<aside className="detail-panel" aria-label={`${selectedAgent.name} details`}>
|
||||
<header className="detail-agent-header">
|
||||
<span className="detail-agent-icon">
|
||||
<Icon size={20} strokeWidth={1.8} aria-hidden="true" />
|
||||
</span>
|
||||
<span>
|
||||
<strong>{selectedAgent.name}</strong>
|
||||
<small>{executorApproved ? "Preparing artifacts" : selectedAgent.status}</small>
|
||||
</span>
|
||||
<span className={`status-dot status-dot--${selectedState}`} aria-hidden="true" />
|
||||
<button
|
||||
className="icon-button"
|
||||
type="button"
|
||||
aria-label="Return to Iris details"
|
||||
onClick={() => onSelectAgent("iris")}
|
||||
>
|
||||
<X size={17} aria-hidden="true" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<section className="detail-section">
|
||||
{selectedAgentId === "executor" ? (
|
||||
<>
|
||||
<p className="eyebrow">Step details</p>
|
||||
<span className={`step-badge step-badge--${approvalState}`}>
|
||||
<ShieldCheck size={13} aria-hidden="true" />
|
||||
{approvalState === "pending" ? "Pending approval" : "Approval granted"}
|
||||
</span>
|
||||
<h2>{approval.title}</h2>
|
||||
<p className="step-description">
|
||||
Assemble artifacts and configure release targets.
|
||||
</p>
|
||||
<dl className="agent-facts approval-facts">
|
||||
<div>
|
||||
<dt>Requested by</dt>
|
||||
<dd>{approval.requestedBy}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Requested at</dt>
|
||||
<dd>{approval.requestedAt}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Estimated duration</dt>
|
||||
<dd>{approval.duration}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Status</dt>
|
||||
<dd>{approvalState === "pending" ? "Waiting for approval" : "Preparing"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Approval needed</dt>
|
||||
<dd>Bao (you)</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="eyebrow">Agent details</p>
|
||||
<h2>{selectedAgent.task}</h2>
|
||||
<dl className="agent-facts">
|
||||
<div>
|
||||
<dt>Role</dt>
|
||||
<dd>{selectedAgent.role}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Started</dt>
|
||||
<dd>{selectedAgent.started}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Model policy</dt>
|
||||
<dd>{selectedAgent.model}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div className="tool-chips" aria-label="Allowed tools">
|
||||
{selectedAgent.tools.map((tool) => (
|
||||
<span key={tool}>{tool}</span>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="detail-section">
|
||||
<p className="eyebrow">Run context</p>
|
||||
<DetailRow icon={Boxes} label="Run" value={runDetails.title} />
|
||||
<DetailRow
|
||||
icon={Link2}
|
||||
label="Session"
|
||||
value={runDetails.sessionId}
|
||||
actionLabel="Open OpenClaw session details"
|
||||
onClick={onOpenSession}
|
||||
/>
|
||||
<DetailRow
|
||||
icon={Cpu}
|
||||
label="Provider"
|
||||
value={`${runDetails.provider} via OpenClaw`}
|
||||
actionLabel="Open provider and routing details"
|
||||
onClick={onOpenProvider}
|
||||
/>
|
||||
<DetailRow icon={Bot} label="Model" value="gpt-5.2-codex" />
|
||||
<DetailRow icon={FolderKanban} label="Project" value={runDetails.project} />
|
||||
<DetailRow icon={ShieldCheck} label="Environment" value={runDetails.environment} />
|
||||
</section>
|
||||
|
||||
<section className="detail-section usage-section">
|
||||
<p className="eyebrow">Usage (run total)</p>
|
||||
<DetailRow icon={Clock3} label="Duration" value={runDetails.elapsed} />
|
||||
<DetailRow icon={Activity} label="Tokens" value={runDetails.tokens} />
|
||||
<DetailRow icon={CircleDollarSign} label="Estimated cost" value={runDetails.cost} />
|
||||
<DetailRow icon={Wrench} label="Tool calls" value={String(runDetails.toolCalls)} />
|
||||
</section>
|
||||
|
||||
{selectedAgentId === "executor" ? (
|
||||
<section
|
||||
className={`decision-card decision-card--${approvalState}`}
|
||||
aria-live="polite"
|
||||
>
|
||||
{approvalState === "pending" ? (
|
||||
<>
|
||||
<p className="eyebrow">Pending approval</p>
|
||||
<h2>{approval.title}</h2>
|
||||
<p>{approval.impact}</p>
|
||||
<button
|
||||
className="button button--primary button--full"
|
||||
type="button"
|
||||
onClick={onOpenApproval}
|
||||
data-testid="review-approval"
|
||||
>
|
||||
<ShieldCheck size={17} aria-hidden="true" />
|
||||
Review approval
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="eyebrow">Decision recorded</p>
|
||||
<h2>Approval granted</h2>
|
||||
<p>Executor is preparing artifacts. No deployment was executed.</p>
|
||||
<button
|
||||
className="button button--secondary button--full"
|
||||
type="button"
|
||||
onClick={onUndoApproval}
|
||||
>
|
||||
<RotateCcw size={16} aria-hidden="true" />
|
||||
Undo mock decision
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{runState === "stopped" ? (
|
||||
<section className="decision-card decision-card--stopped">
|
||||
<p className="eyebrow">Run stopped</p>
|
||||
<h2>The mock run is no longer advancing.</h2>
|
||||
<button
|
||||
className="button button--primary button--full"
|
||||
type="button"
|
||||
onClick={onRestart}
|
||||
>
|
||||
<RotateCcw size={16} aria-hidden="true" />
|
||||
Restart mock run
|
||||
</button>
|
||||
</section>
|
||||
) : null}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function ApprovalDialog({ approvalState, onApprove, onUndo, onClose }) {
|
||||
return (
|
||||
<Modal
|
||||
title={approvalState === "pending" ? "Review deployment preparation" : "Approval decision"}
|
||||
eyebrow="Human approval"
|
||||
onClose={onClose}
|
||||
size="wide"
|
||||
>
|
||||
<div className="approval-layout">
|
||||
<section>
|
||||
<div className={`approval-summary approval-summary--${approvalState}`}>
|
||||
{approvalState === "pending" ? (
|
||||
<KeyRound size={22} aria-hidden="true" />
|
||||
) : (
|
||||
<CheckCircle2 size={22} aria-hidden="true" />
|
||||
)}
|
||||
<div>
|
||||
<strong>
|
||||
{approvalState === "pending" ? "Decision required" : "Decision recorded"}
|
||||
</strong>
|
||||
<p>{approval.impact}</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Requested scope</h3>
|
||||
<ul className="scope-list">
|
||||
{approval.scope.map((item) => (
|
||||
<li key={item}>
|
||||
<Check size={16} aria-hidden="true" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
<aside className="decision-context">
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Requested by</dt>
|
||||
<dd>{approval.requestedBy}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Requested at</dt>
|
||||
<dd>{approval.requestedAt}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Estimated duration</dt>
|
||||
<dd>{approval.duration}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Session</dt>
|
||||
<dd>{runDetails.sessionId}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<footer className="modal-actions">
|
||||
<button className="button button--ghost" type="button" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
{approvalState === "pending" ? (
|
||||
<>
|
||||
<button className="button button--secondary" type="button" onClick={onClose}>
|
||||
Request changes
|
||||
</button>
|
||||
<button
|
||||
className="button button--primary"
|
||||
type="button"
|
||||
onClick={onApprove}
|
||||
data-testid="confirm-approval"
|
||||
>
|
||||
<Check size={17} aria-hidden="true" />
|
||||
Approve and continue
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<button className="button button--secondary" type="button" onClick={onUndo}>
|
||||
<RotateCcw size={16} aria-hidden="true" />
|
||||
Undo mock decision
|
||||
</button>
|
||||
)}
|
||||
</footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function EventDialog({ event, onClose }) {
|
||||
return (
|
||||
<Modal title={event.title} eyebrow="Run event detail" onClose={onClose}>
|
||||
<div className="event-detail">
|
||||
<span className={`event-detail-icon event-detail-icon--${event.type}`}>
|
||||
<History size={20} aria-hidden="true" />
|
||||
</span>
|
||||
<p>{event.detail}</p>
|
||||
<dl className="detail-list">
|
||||
<div>
|
||||
<dt>Actor</dt>
|
||||
<dd>{event.actor}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Timestamp</dt>
|
||||
<dd>27 Jul 2026, {event.time}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Correlation ID</dt>
|
||||
<dd>{event.correlation}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Source</dt>
|
||||
<dd>OpenClaw session projection</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function RuntimeDialog({ type, onClose }) {
|
||||
const isProvider = type === "provider";
|
||||
return (
|
||||
<Modal
|
||||
title={isProvider ? "Provider routing" : "OpenClaw session"}
|
||||
eyebrow={isProvider ? "Runtime policy detail" : "Session detail"}
|
||||
onClose={onClose}
|
||||
size="wide"
|
||||
>
|
||||
<div className="runtime-detail-grid">
|
||||
<section className="runtime-overview">
|
||||
<span className="runtime-large-icon">
|
||||
{isProvider ? <Cpu size={26} aria-hidden="true" /> : <Link2 size={26} aria-hidden="true" />}
|
||||
</span>
|
||||
<p className="runtime-status">
|
||||
<span className="status-dot status-dot--active" aria-hidden="true" />
|
||||
Authored connected state
|
||||
</p>
|
||||
<h3>{isProvider ? "OpenAI through OpenClaw" : runDetails.sessionId}</h3>
|
||||
<p>
|
||||
{isProvider
|
||||
? "Nexus displays the effective OpenClaw routing policy. This prototype performs no provider request."
|
||||
: "This mock projection represents the authoritative OpenClaw session without duplicating runtime state."}
|
||||
</p>
|
||||
</section>
|
||||
<dl className="runtime-facts">
|
||||
<div>
|
||||
<dt>{isProvider ? "Primary provider" : "Session owner"}</dt>
|
||||
<dd>{isProvider ? runDetails.provider : "Iris"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{isProvider ? "Access path" : "Parent run"}</dt>
|
||||
<dd>{isProvider ? runDetails.access : runDetails.runId}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{isProvider ? "Model policy" : "Started"}</dt>
|
||||
<dd>{isProvider ? runDetails.modelPolicy : runDetails.started}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{isProvider ? "Secret visibility" : "Freshness"}</dt>
|
||||
<dd>{isProvider ? "Hidden · reference only" : "Observed 8 seconds ago"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<footer className="modal-actions">
|
||||
<button className="button button--primary" type="button" onClick={onClose}>
|
||||
Done
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function AskIrisDialog({ onClose }) {
|
||||
const [message, setMessage] = useState("");
|
||||
const [reply, setReply] = useState("");
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
const trimmed = message.trim();
|
||||
if (!trimmed) return;
|
||||
setReply(
|
||||
"Mock response: I would keep the approval paused until you review the requested deployment-preparation scope.",
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title="Ask Iris" eyebrow="Interactive mockup · no backend calls" onClose={onClose}>
|
||||
<div className="iris-dialog">
|
||||
<div className="iris-message">
|
||||
<span className="detail-agent-icon">
|
||||
<Sparkles size={18} aria-hidden="true" />
|
||||
</span>
|
||||
<p>
|
||||
I am supervising <strong>{runDetails.title}</strong>. Ask about the current
|
||||
plan, agents, or pending approval.
|
||||
</p>
|
||||
</div>
|
||||
{reply ? (
|
||||
<div className="iris-message iris-message--reply" aria-live="polite">
|
||||
<span className="detail-agent-icon">
|
||||
<Sparkles size={18} aria-hidden="true" />
|
||||
</span>
|
||||
<p>{reply}</p>
|
||||
</div>
|
||||
) : null}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label htmlFor="iris-message">Message</label>
|
||||
<textarea
|
||||
id="iris-message"
|
||||
value={message}
|
||||
onChange={(event) => setMessage(event.target.value)}
|
||||
placeholder="What is blocking this run?"
|
||||
rows={3}
|
||||
/>
|
||||
<button className="button button--primary" type="submit">
|
||||
<MessageSquareText size={17} aria-hidden="true" />
|
||||
Send mock message
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const [selectedAgent, setSelectedAgent] = useState("executor");
|
||||
const [approvalState, setApprovalState] = useState("pending");
|
||||
const [runState, setRunState] = useState("running");
|
||||
const [modal, setModal] = useState(null);
|
||||
const [toast, setToast] = useState("");
|
||||
const toastTimer = useRef(null);
|
||||
|
||||
const announce = (message) => {
|
||||
window.clearTimeout(toastTimer.current);
|
||||
setToast(message);
|
||||
toastTimer.current = window.setTimeout(() => setToast(""), 3200);
|
||||
};
|
||||
|
||||
const closeModal = () => setModal(null);
|
||||
|
||||
const approve = () => {
|
||||
setApprovalState("approved");
|
||||
setRunState("running");
|
||||
setSelectedAgent("executor");
|
||||
closeModal();
|
||||
announce("Mock approval recorded. Executor is preparing artifacts.");
|
||||
};
|
||||
|
||||
const undoApproval = () => {
|
||||
setApprovalState("pending");
|
||||
setSelectedAgent("executor");
|
||||
closeModal();
|
||||
announce("Mock approval restored to pending.");
|
||||
};
|
||||
|
||||
const togglePause = () => {
|
||||
const nextState = runState === "paused" ? "running" : "paused";
|
||||
setRunState(nextState);
|
||||
announce(nextState === "paused" ? "Mock run paused." : "Mock run resumed.");
|
||||
};
|
||||
|
||||
const stopRun = () => {
|
||||
setRunState("stopped");
|
||||
closeModal();
|
||||
announce("Mock run stopped. You can restart it from the detail panel.");
|
||||
};
|
||||
|
||||
const restart = () => {
|
||||
setRunState("running");
|
||||
setApprovalState("pending");
|
||||
setSelectedAgent("executor");
|
||||
announce("Mock run restarted at the pending approval.");
|
||||
};
|
||||
|
||||
const handlePrototypeNav = (label, active) => {
|
||||
if (active) {
|
||||
announce("You are already viewing Run Control.");
|
||||
return;
|
||||
}
|
||||
announce(`${label} is outside this focused mockup.`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="prototype-root">
|
||||
<div className="desktop-gate">
|
||||
<Network size={30} aria-hidden="true" />
|
||||
<h1>Desktop mockup</h1>
|
||||
<p>This selected concept intentionally has no mobile surface.</p>
|
||||
</div>
|
||||
|
||||
<div className="app-shell">
|
||||
<Sidebar onPrototypeNav={handlePrototypeNav} />
|
||||
<div className="application">
|
||||
<Topbar
|
||||
runState={runState}
|
||||
onPause={togglePause}
|
||||
onStop={() => setModal({ type: "stop" })}
|
||||
onAskIris={() => setModal({ type: "ask" })}
|
||||
/>
|
||||
|
||||
<main className="run-workspace">
|
||||
<section className="workspace-stage">
|
||||
<div className="status-strip" aria-label="Run status summary">
|
||||
<span>
|
||||
<i className="status-dot status-dot--active" />
|
||||
5 active
|
||||
</span>
|
||||
<span>
|
||||
<i className="status-dot status-dot--waiting" />
|
||||
{approvalState === "pending" ? "1 waiting" : "0 waiting"}
|
||||
</span>
|
||||
<span>
|
||||
<i className="status-dot status-dot--idle" />0 blocked
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => announce("Auto-advance remains on for reversible mock steps.")}
|
||||
>
|
||||
Auto-advance <strong>On</strong>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<OrchestrationMap
|
||||
selectedAgent={selectedAgent}
|
||||
approvalState={approvalState}
|
||||
runState={runState}
|
||||
onSelectAgent={setSelectedAgent}
|
||||
onOpenApproval={() => setModal({ type: "approval" })}
|
||||
/>
|
||||
|
||||
<EventRail
|
||||
approvalState={approvalState}
|
||||
onOpenEvent={(event) => setModal({ type: "event", event })}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<DetailPanel
|
||||
selectedAgentId={selectedAgent}
|
||||
approvalState={approvalState}
|
||||
runState={runState}
|
||||
onSelectAgent={setSelectedAgent}
|
||||
onOpenApproval={() => setModal({ type: "approval" })}
|
||||
onOpenProvider={() => setModal({ type: "provider" })}
|
||||
onOpenSession={() => setModal({ type: "session" })}
|
||||
onUndoApproval={undoApproval}
|
||||
onRestart={restart}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{modal?.type === "approval" ? (
|
||||
<ApprovalDialog
|
||||
approvalState={approvalState}
|
||||
onApprove={approve}
|
||||
onUndo={undoApproval}
|
||||
onClose={closeModal}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{modal?.type === "event" ? (
|
||||
<EventDialog event={modal.event} onClose={closeModal} />
|
||||
) : null}
|
||||
|
||||
{modal?.type === "provider" || modal?.type === "session" ? (
|
||||
<RuntimeDialog type={modal.type} onClose={closeModal} />
|
||||
) : null}
|
||||
|
||||
{modal?.type === "ask" ? <AskIrisDialog onClose={closeModal} /> : null}
|
||||
|
||||
{modal?.type === "stop" ? (
|
||||
<Modal title="Stop this mock run?" eyebrow="Reversible prototype action" onClose={closeModal}>
|
||||
<div className="stop-dialog">
|
||||
<span className="event-detail-icon event-detail-icon--approval">
|
||||
<Square size={20} aria-hidden="true" />
|
||||
</span>
|
||||
<p>
|
||||
The run will enter a stopped state. No backend or OpenClaw action is
|
||||
performed, and the mock state can be restarted immediately.
|
||||
</p>
|
||||
</div>
|
||||
<footer className="modal-actions">
|
||||
<button className="button button--ghost" type="button" onClick={closeModal}>
|
||||
Keep running
|
||||
</button>
|
||||
<button className="button button--danger" type="button" onClick={stopRun}>
|
||||
<Square size={15} aria-hidden="true" />
|
||||
Stop mock run
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
) : null}
|
||||
|
||||
<div className={`toast${toast ? " is-visible" : ""}`} role="status" aria-live="polite">
|
||||
<BadgeCheck size={17} aria-hidden="true" />
|
||||
<span>{toast}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useEffect, useId, useRef } from "react";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
export function Modal({ title, eyebrow, children, onClose, size = "medium" }) {
|
||||
const titleId = useId();
|
||||
const closeRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const previousFocus = document.activeElement;
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
document.body.classList.add("dialog-open");
|
||||
closeRef.current?.focus();
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
document.body.classList.remove("dialog-open");
|
||||
previousFocus?.focus?.();
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<div className="modal-layer" role="presentation">
|
||||
<button
|
||||
className="modal-backdrop"
|
||||
type="button"
|
||||
aria-label="Close detail view"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<section
|
||||
className={`modal-panel modal-panel--${size}`}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={titleId}
|
||||
>
|
||||
<header className="modal-header">
|
||||
<div>
|
||||
{eyebrow ? <p className="eyebrow">{eyebrow}</p> : null}
|
||||
<h2 id={titleId}>{title}</h2>
|
||||
</div>
|
||||
<button
|
||||
ref={closeRef}
|
||||
className="icon-button"
|
||||
type="button"
|
||||
aria-label="Close detail view"
|
||||
onClick={onClose}
|
||||
>
|
||||
<X size={18} aria-hidden="true" />
|
||||
</button>
|
||||
</header>
|
||||
<div className="modal-content">{children}</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
export const navGroups = [
|
||||
{
|
||||
label: "Operations",
|
||||
items: [
|
||||
{ id: "dashboard", label: "Dashboard", icon: "dashboard" },
|
||||
{ id: "runs", label: "Runs", icon: "runs", active: true },
|
||||
{ id: "agents", label: "Agents", icon: "agents" },
|
||||
{ id: "projects", label: "Projects", icon: "projects" },
|
||||
{ id: "tasks", label: "Task Board", icon: "tasks", count: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Knowledge",
|
||||
items: [
|
||||
{ id: "memory", label: "Memory", icon: "memory" },
|
||||
{ id: "docs", label: "Docs", icon: "docs" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Infrastructure",
|
||||
items: [
|
||||
{ id: "models", label: "Models", icon: "models" },
|
||||
{ id: "activity", label: "Activity", icon: "activity" },
|
||||
{ id: "calendar", label: "Calendar", icon: "calendar" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Governance",
|
||||
items: [
|
||||
{ id: "security", label: "Security", icon: "security" },
|
||||
{ id: "incidents", label: "Incidents", icon: "incidents", count: 0 },
|
||||
{ id: "notifications", label: "Notifications", icon: "notifications", count: 3 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const agents = {
|
||||
iris: {
|
||||
id: "iris",
|
||||
name: "Iris",
|
||||
initials: "IR",
|
||||
role: "Lead · Orchestrator",
|
||||
status: "Delegating work",
|
||||
state: "active",
|
||||
progress: 100,
|
||||
icon: "iris",
|
||||
model: "OpenAI primary",
|
||||
task: "Coordinates the release-validation plan and human approvals.",
|
||||
started: "17:02:13",
|
||||
tools: ["sessions_send", "tasks_delegate", "approvals_request"],
|
||||
},
|
||||
programmer: {
|
||||
id: "programmer",
|
||||
name: "Programmer",
|
||||
initials: "PR",
|
||||
role: "Code Engineer",
|
||||
status: "Validation complete",
|
||||
state: "completed",
|
||||
progress: 100,
|
||||
icon: "programmer",
|
||||
model: "OpenAI coding policy",
|
||||
task: "Validated the change set and completed 128 unit and integration checks.",
|
||||
started: "17:02:28",
|
||||
tools: ["workspace_read", "exec_sandboxed", "test_report"],
|
||||
},
|
||||
reviewer: {
|
||||
id: "reviewer",
|
||||
name: "Reviewer",
|
||||
initials: "RV",
|
||||
role: "Quality Gate",
|
||||
status: "Checking changes",
|
||||
state: "working",
|
||||
progress: 43,
|
||||
icon: "reviewer",
|
||||
model: "OpenAI review policy",
|
||||
task: "Reviews release notes, policy constraints, and validation evidence.",
|
||||
started: "17:06:10",
|
||||
tools: ["diff_read", "policy_check", "review_submit"],
|
||||
},
|
||||
executor: {
|
||||
id: "executor",
|
||||
name: "Executor",
|
||||
initials: "EX",
|
||||
role: "Release Engineer",
|
||||
status: "Waiting for approval",
|
||||
state: "waiting",
|
||||
progress: 0,
|
||||
icon: "executor",
|
||||
model: "OpenAI execution policy",
|
||||
task: "Will prepare deployment artifacts after explicit owner approval.",
|
||||
started: "Queued",
|
||||
tools: ["artifact_prepare", "release_validate", "deployment_stage"],
|
||||
},
|
||||
};
|
||||
|
||||
export const timelineEvents = [
|
||||
{
|
||||
id: "event-start",
|
||||
time: "17:02:13",
|
||||
actor: "Iris",
|
||||
title: "Run started",
|
||||
detail: "Created the execution plan for production release validation.",
|
||||
type: "system",
|
||||
correlation: "evt_01JZR6C90A",
|
||||
},
|
||||
{
|
||||
id: "event-delegate-programmer",
|
||||
time: "17:02:28",
|
||||
actor: "Iris",
|
||||
title: "Delegated to Programmer",
|
||||
detail: "Validate changes and run the complete test suite.",
|
||||
type: "delegation",
|
||||
correlation: "evt_01JZR6D2KR",
|
||||
},
|
||||
{
|
||||
id: "event-tests",
|
||||
time: "17:05:21",
|
||||
actor: "Programmer",
|
||||
title: "Validation complete",
|
||||
detail: "All 128 unit and integration checks passed.",
|
||||
type: "success",
|
||||
correlation: "evt_01JZR6M8QF",
|
||||
},
|
||||
{
|
||||
id: "event-review",
|
||||
time: "17:06:10",
|
||||
actor: "Reviewer",
|
||||
title: "Review started",
|
||||
detail: "Checking release notes, changed files, and policy evidence.",
|
||||
type: "working",
|
||||
correlation: "evt_01JZR6Q8HN",
|
||||
},
|
||||
{
|
||||
id: "event-review-ready",
|
||||
time: "17:18:34",
|
||||
actor: "Reviewer",
|
||||
title: "Quality gate clear",
|
||||
detail: "Release evidence is complete and the deployment step can be prepared.",
|
||||
type: "success",
|
||||
correlation: "evt_01JZR7C0PD",
|
||||
},
|
||||
{
|
||||
id: "event-approval",
|
||||
time: "17:20:55",
|
||||
actor: "Iris",
|
||||
title: "Approval requested",
|
||||
detail: "Executor needs permission to prepare deployment artifacts.",
|
||||
type: "approval",
|
||||
correlation: "evt_01JZR7J4SX",
|
||||
},
|
||||
];
|
||||
|
||||
export const runDetails = {
|
||||
title: "Production release validation",
|
||||
runId: "run_01JZR6C7Q8M9",
|
||||
sessionId: "oc_7f3a2d1e",
|
||||
started: "27 Jul 2026, 17:02:13",
|
||||
elapsed: "00:18:42",
|
||||
provider: "OpenAI",
|
||||
access: "OpenClaw Gateway",
|
||||
modelPolicy: "openai-primary / release-safe",
|
||||
project: "Nexus Platform",
|
||||
environment: "production",
|
||||
tokens: "1.42M in / 368k out",
|
||||
cost: "$0.186",
|
||||
toolCalls: 18,
|
||||
};
|
||||
|
||||
export const approval = {
|
||||
id: "approval_01JZR7J5B2",
|
||||
title: "Prepare deployment",
|
||||
requestedBy: "Iris",
|
||||
requestedAt: "27 Jul 2026, 17:20:55",
|
||||
duration: "~ 6 min",
|
||||
scope: [
|
||||
"Assemble versioned release artifacts",
|
||||
"Validate production configuration",
|
||||
"Prepare, but do not execute, the deployment",
|
||||
],
|
||||
impact: "No production mutation is included in this approval.",
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "@fontsource/manrope/400.css";
|
||||
import "@fontsource/manrope/500.css";
|
||||
import "@fontsource/manrope/600.css";
|
||||
import "@fontsource/manrope/700.css";
|
||||
import "@fontsource/space-grotesk/500.css";
|
||||
import "@fontsource/space-grotesk/600.css";
|
||||
import "@fontsource/space-grotesk/700.css";
|
||||
import "@fontsource/jetbrains-mono/400.css";
|
||||
import "@fontsource/jetbrains-mono/500.css";
|
||||
import { App } from "./App.jsx";
|
||||
import "./styles.css";
|
||||
|
||||
createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user