The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit c95a8380814fCommit detailsView latest

apps/dashboard/src/main.tsx 185 lines · 6.1 KB · tsx
1
import React, { useEffect, useState } from 'react';
2
import { createRoot } from 'react-dom/client';
3
import './styles.css';
4
5
type DashboardData = {
6
  agents: any[];
7
  events: any[];
8
  conflicts: any[];
9
  receipts: any[];
10
  profiles: any[];
11
};
12
const empty: DashboardData = { agents: [], events: [], conflicts: [], receipts: [], profiles: [] };
13
14
function App() {
15
  const [data, setData] = useState(empty);
16
  const [offline, setOffline] = useState(false);
17
  useEffect(() => {
18
    const apiBase = location.hostname === 'localhost' ? 'http://localhost:3100' : location.origin;
19
    fetch(`${apiBase}/v0.1/dashboard`)
20
      .then((response) => response.json())
21
      .then(setData)
22
      .catch(() => setOffline(true));
23
  }, []);
24
  const bilateral = data.receipts.filter((receipt) => !receipt.unilateral).length;
25
  return (
26
    <main>
27
      <header>
28
        <div className="mark">OC</div>
29
        <div>
30
          <p className="eyebrow">ASSURANCE LAYER / V0.1</p>
31
          <h1>OpenClasp</h1>
32
        </div>
33
        <span className={offline ? 'status offline' : 'status'}>
34
          {offline ? 'API OFFLINE' : 'LOCAL NETWORK'}
35
        </span>
36
      </header>
37
      <section className="hero">
38
        <div>
39
          <p className="eyebrow">AGENT COORDINATION</p>
40
          <h2>
41
            Trust is contextual.
42
            <br />
43
            <em>Evidence makes it useful.</em>
44
          </h2>
45
          <p>
46
            Verify identities, watch agreements, surface clues, and learn from signed
47
            outcomes—without owning the conversation.
48
          </p>
49
        </div>
50
        <div className="signal">
51
          <span>Current posture</span>
52
          <strong>{data.profiles.length ? 'LEARNING' : 'AWAITING EVIDENCE'}</strong>
53
          <small>Raw messages remain local</small>
54
        </div>
55
      </section>
56
      <section className="metrics">
57
        <Metric label="Verified agents" value={data.agents.length} />
58
        <Metric label="Structured events" value={data.events.length} />
59
        <Metric label="Bilateral receipts" value={bilateral} />
60
        <Metric label="Contextual profiles" value={data.profiles.length} />
61
      </section>
62
      <section className="grid">
63
        <Panel title="Agent lineage" subtitle="Identity, operator, and version">
64
          {data.agents.length ? (
65
            data.agents.map((agent) => (
66
              <article className="row" key={agent.agentId}>
67
                <span className="dot" />
68
                <div>
69
                  <strong>{agent.agentId}</strong>
70
                  <small>
71
                    {agent.operatorRef} · v{agent.agentVersion}
72
                  </small>
73
                </div>
74
                <b>{agent.revoked ? 'REVOKED' : 'VERIFIED'}</b>
75
              </article>
76
            ))
77
          ) : (
78
            <Empty text="Run pnpm demo or register an agent through the API." />
79
          )}
80
        </Panel>
81
        <Panel title="Behavioural context" subtitle="Task-specific, never universal">
82
          {data.profiles.length ? (
83
            data.profiles.map((profile) => (
84
              <article className="profile" key={`${profile.agentId}${profile.taskCategory}`}>
85
                <div>
86
                  <strong>{profile.agentId}</strong>
87
                  <small>
88
                    {profile.taskCategory} · {profile.sampleSize} signed outcome(s)
89
                  </small>
90
                </div>
91
                <Meter value={profile.completion} label="completion" />
92
                <Meter value={profile.scope} label="scope" />
93
              </article>
94
            ))
95
          ) : (
96
            <Empty text="Profiles grow only from verified receipts and eligible feedback." />
97
          )}
98
        </Panel>
99
        <Panel title="Interaction timeline" subtitle="Attributable structured events">
100
          {data.events.length ? (
101
            data.events
102
              .slice(-6)
103
              .reverse()
104
              .map((event) => (
105
                <article className="row" key={event.eventId}>
106
                  <span className={`dot ${event.eventType}`} />
107
                  <div>
108
                    <strong>{event.eventType.replaceAll('_', ' ')}</strong>
109
                    <small>
110
                      {event.agentId} · {new Date(event.timestamp).toLocaleTimeString()}
111
                    </small>
112
                  </div>
113
                  <b>{event.visibility}</b>
114
                </article>
115
              ))
116
          ) : (
117
            <Empty text="No structured events recorded yet." />
118
          )}
119
        </Panel>
120
        <Panel title="Mediation and receipts" subtitle="Shared only with explicit consent">
121
          <div className="split">
122
            <div>
123
              <strong>{data.conflicts.filter((item) => item.status === 'resolved').length}</strong>
124
              <small>resolved conflicts</small>
125
            </div>
126
            <div>
127
              <strong>{data.receipts.length}</strong>
128
              <small>signed receipts</small>
129
            </div>
130
          </div>
131
          <p className="note">
132
            Unilateral receipts are labelled separately and never treated as bilateral proof.
133
          </p>
134
        </Panel>
135
      </section>
136
    </main>
137
  );
138
}
139
140
function Metric({ label, value }: { label: string; value: number }) {
141
  return (
142
    <div>
143
      <span>{label}</span>
144
      <strong>{String(value).padStart(2, '0')}</strong>
145
    </div>
146
  );
147
}
148
function Panel({
149
  title,
150
  subtitle,
151
  children,
152
}: React.PropsWithChildren<{ title: string; subtitle: string }>) {
153
  return (
154
    <section className="panel">
155
      <div className="panelHead">
156
        <div>
157
          <h3>{title}</h3>
158
          <p>{subtitle}</p>
159
        </div>
160
        <span>↗</span>
161
      </div>
162
      {children}
163
    </section>
164
  );
165
}
166
function Empty({ text }: { text: string }) {
167
  return <div className="empty">{text}</div>;
168
}
169
function Meter({ value, label }: { value: number; label: string }) {
170
  return (
171
    <div className="meter">
172
      <span>{label}</span>
173
      <i>
174
        <b style={{ width: `${value * 100}%` }} />
175
      </i>
176
      <strong>{Math.round(value * 100)}%</strong>
177
    </div>
178
  );
179
}
180
181
createRoot(document.getElementById('root')!).render(
182
  <React.StrictMode>
183
    <App />
184
  </React.StrictMode>,
185
);