@lelu-auth/lelu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,670 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var zod = require('zod');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ var AuthRequestSchema = zod.z.object({
8
+ userId: zod.z.string().min(1, "userId is required"),
9
+ action: zod.z.string().min(1, "action is required"),
10
+ resource: zod.z.record(zod.z.string()).optional()
11
+ });
12
+ var AgentContextSchema = zod.z.object({
13
+ /** LLM confidence score — 0.0 to 1.0 */
14
+ confidence: zod.z.number().min(0).max(1),
15
+ /** User the agent is acting on behalf of */
16
+ actingFor: zod.z.string().optional(),
17
+ /** Requested agent scope */
18
+ scope: zod.z.string().optional()
19
+ });
20
+ var AgentAuthRequestSchema = zod.z.object({
21
+ actor: zod.z.string().min(1, "actor is required"),
22
+ action: zod.z.string().min(1, "action is required"),
23
+ resource: zod.z.record(zod.z.string()).optional(),
24
+ context: AgentContextSchema
25
+ });
26
+ var MintTokenRequestSchema = zod.z.object({
27
+ scope: zod.z.string().min(1),
28
+ actingFor: zod.z.string().optional(),
29
+ ttlSeconds: zod.z.number().int().positive().optional()
30
+ });
31
+ var DelegateScopeRequestSchema = zod.z.object({
32
+ delegator: zod.z.string().min(1, "delegator is required"),
33
+ delegatee: zod.z.string().min(1, "delegatee is required"),
34
+ scopedTo: zod.z.array(zod.z.string().min(1)).optional(),
35
+ ttlSeconds: zod.z.number().int().positive().optional(),
36
+ confidence: zod.z.number().min(0).max(1).optional(),
37
+ actingFor: zod.z.string().optional(),
38
+ tenantId: zod.z.string().optional()
39
+ });
40
+ var AuthEngineError = class extends Error {
41
+ constructor(message, status, details) {
42
+ super(message);
43
+ this.status = status;
44
+ this.details = details;
45
+ this.name = "AuthEngineError";
46
+ }
47
+ };
48
+
49
+ // src/client.ts
50
+ var LeluClient = class {
51
+ baseUrl;
52
+ timeoutMs;
53
+ apiKey;
54
+ constructor(cfg = {}) {
55
+ this.baseUrl = (cfg.baseUrl ?? "http://localhost:8080").replace(/\/$/, "");
56
+ this.timeoutMs = cfg.timeoutMs ?? 5e3;
57
+ this.apiKey = cfg.apiKey;
58
+ }
59
+ // ── Human authorization ────────────────────────────────────────────────────
60
+ /**
61
+ * Checks whether a human user is permitted to perform an action.
62
+ */
63
+ async authorize(req) {
64
+ const validated = AuthRequestSchema.parse(req);
65
+ const body = {
66
+ user_id: validated.userId,
67
+ action: validated.action,
68
+ resource: validated.resource
69
+ };
70
+ const data = await this.post("/v1/authorize", body);
71
+ return {
72
+ allowed: data.allowed,
73
+ reason: data.reason,
74
+ traceId: data.trace_id
75
+ };
76
+ }
77
+ // ── Agent authorization ────────────────────────────────────────────────────
78
+ /**
79
+ * Checks whether an AI agent is permitted to perform an action, taking the
80
+ * confidence score into account (Confidence-Aware Auth ★).
81
+ */
82
+ async agentAuthorize(req) {
83
+ const validated = AgentAuthRequestSchema.parse(req);
84
+ const body = {
85
+ actor: validated.actor,
86
+ action: validated.action,
87
+ resource: validated.resource,
88
+ confidence: validated.context.confidence,
89
+ acting_for: validated.context.actingFor,
90
+ scope: validated.context.scope
91
+ };
92
+ const data = await this.post("/v1/agent/authorize", body);
93
+ return {
94
+ allowed: data.allowed,
95
+ reason: data.reason,
96
+ traceId: data.trace_id,
97
+ downgradedScope: data.downgraded_scope,
98
+ requiresHumanReview: data.requires_human_review,
99
+ confidenceUsed: data.confidence_used
100
+ };
101
+ }
102
+ // ── JIT Token minting ──────────────────────────────────────────────────────
103
+ /**
104
+ * Mints a scoped JWT for an agent with an optional TTL.
105
+ * Default TTL is 60 seconds.
106
+ */
107
+ async mintToken(req) {
108
+ const validated = MintTokenRequestSchema.parse(req);
109
+ const body = {
110
+ scope: validated.scope,
111
+ acting_for: validated.actingFor,
112
+ ttl_seconds: validated.ttlSeconds ?? 60
113
+ };
114
+ const data = await this.post("/v1/tokens/mint", body);
115
+ return {
116
+ token: data.token,
117
+ tokenId: data.token_id,
118
+ expiresAt: new Date(data.expires_at * 1e3)
119
+ };
120
+ }
121
+ // ── Token revocation ───────────────────────────────────────────────────────
122
+ /**
123
+ * Immediately revokes a JIT token by its ID.
124
+ */
125
+ async revokeToken(tokenId) {
126
+ const data = await this.delete(
127
+ `/v1/tokens/${encodeURIComponent(tokenId)}`
128
+ );
129
+ return { success: data.success };
130
+ }
131
+ // ── Multi-agent delegation ─────────────────────────────────────────────────
132
+ /**
133
+ * Delegates a constrained sub-scope from one agent to another.
134
+ *
135
+ * Validates the delegation rule in the loaded policy, caps the TTL to the
136
+ * policy maximum, and mints a child JIT token scoped to the granted actions.
137
+ *
138
+ * The delegator's `confidence` score is checked against the policy's
139
+ * `require_confidence_above` before delegation is granted.
140
+ */
141
+ async delegateScope(req) {
142
+ const validated = DelegateScopeRequestSchema.parse(req);
143
+ const body = {
144
+ delegator: validated.delegator,
145
+ delegatee: validated.delegatee,
146
+ scoped_to: validated.scopedTo ?? [],
147
+ ttl_seconds: validated.ttlSeconds ?? 60,
148
+ confidence: validated.confidence ?? 1,
149
+ acting_for: validated.actingFor ?? "",
150
+ tenant_id: validated.tenantId ?? ""
151
+ };
152
+ const data = await this.post("/v1/agent/delegate", body);
153
+ return {
154
+ token: data.token,
155
+ tokenId: data.token_id,
156
+ expiresAt: new Date(data.expires_at * 1e3),
157
+ delegator: data.delegator,
158
+ delegatee: data.delegatee,
159
+ grantedScopes: data.granted_scopes,
160
+ traceId: data.trace_id
161
+ };
162
+ }
163
+ // ── Health check ───────────────────────────────────────────────────────────
164
+ /**
165
+ * Returns true if the engine is reachable and healthy.
166
+ */
167
+ async isHealthy() {
168
+ try {
169
+ const data = await this.get("/healthz");
170
+ return data.status === "ok";
171
+ } catch {
172
+ return false;
173
+ }
174
+ }
175
+ // ── HTTP helpers ───────────────────────────────────────────────────────────
176
+ headers() {
177
+ const h = { "Content-Type": "application/json" };
178
+ if (this.apiKey) {
179
+ h["Authorization"] = `Bearer ${this.apiKey}`;
180
+ }
181
+ return h;
182
+ }
183
+ async post(path, body) {
184
+ const ctrl = new AbortController();
185
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
186
+ try {
187
+ const res = await fetch(`${this.baseUrl}${path}`, {
188
+ method: "POST",
189
+ headers: this.headers(),
190
+ body: JSON.stringify(body),
191
+ signal: ctrl.signal
192
+ });
193
+ return this.parseResponse(res);
194
+ } finally {
195
+ clearTimeout(timer);
196
+ }
197
+ }
198
+ async delete(path) {
199
+ const ctrl = new AbortController();
200
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
201
+ try {
202
+ const res = await fetch(`${this.baseUrl}${path}`, {
203
+ method: "DELETE",
204
+ headers: this.headers(),
205
+ signal: ctrl.signal
206
+ });
207
+ return this.parseResponse(res);
208
+ } finally {
209
+ clearTimeout(timer);
210
+ }
211
+ }
212
+ async get(path) {
213
+ const ctrl = new AbortController();
214
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
215
+ try {
216
+ const res = await fetch(`${this.baseUrl}${path}`, {
217
+ method: "GET",
218
+ headers: this.headers(),
219
+ signal: ctrl.signal
220
+ });
221
+ return this.parseResponse(res);
222
+ } finally {
223
+ clearTimeout(timer);
224
+ }
225
+ }
226
+ async parseResponse(res) {
227
+ const json = await res.json();
228
+ if (!res.ok) {
229
+ throw new AuthEngineError(
230
+ json["error"] ?? "engine error",
231
+ res.status,
232
+ json
233
+ );
234
+ }
235
+ return json;
236
+ }
237
+ };
238
+ var SearchIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
239
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
240
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
241
+ ] });
242
+ var CheckIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) });
243
+ var XIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
244
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
245
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
246
+ ] });
247
+ var ChevronDownIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "6 9 12 15 18 9" }) });
248
+ var ChevronUpIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "18 15 12 9 6 15" }) });
249
+ var LeluApprovalUI = ({
250
+ apiBaseUrl = "http://localhost:8080",
251
+ onApprove,
252
+ onDeny,
253
+ pollIntervalMs = 5e3
254
+ }) => {
255
+ const [requests, setRequests] = react.useState([]);
256
+ const [loading, setLoading] = react.useState(true);
257
+ const [error, setError] = react.useState(null);
258
+ const [searchQuery, setSearchQuery] = react.useState("");
259
+ const [expandedId, setExpandedId] = react.useState(null);
260
+ const [processingId, setProcessingId] = react.useState(null);
261
+ const fetchRequests = async () => {
262
+ try {
263
+ const response = await fetch(`${apiBaseUrl}/v1/reviews/pending`);
264
+ if (!response.ok) throw new Error("Failed to fetch pending reviews");
265
+ const data = await response.json();
266
+ setRequests(data.requests || []);
267
+ setError(null);
268
+ } catch (err) {
269
+ setError(err.message);
270
+ } finally {
271
+ setLoading(false);
272
+ }
273
+ };
274
+ react.useEffect(() => {
275
+ fetchRequests();
276
+ const interval = setInterval(fetchRequests, pollIntervalMs);
277
+ return () => clearInterval(interval);
278
+ }, [apiBaseUrl, pollIntervalMs]);
279
+ const handleAction = async (request, approved) => {
280
+ setProcessingId(request.id);
281
+ try {
282
+ const response = await fetch(`${apiBaseUrl}/v1/reviews/${request.id}/resolve`, {
283
+ method: "POST",
284
+ headers: { "Content-Type": "application/json" },
285
+ body: JSON.stringify({ approved })
286
+ });
287
+ if (!response.ok) throw new Error("Failed to resolve review");
288
+ setRequests((prev) => prev.filter((r) => r.id !== request.id));
289
+ if (approved && onApprove) onApprove(request);
290
+ if (!approved && onDeny) onDeny(request);
291
+ } catch (err) {
292
+ setError(err.message);
293
+ } finally {
294
+ setProcessingId(null);
295
+ }
296
+ };
297
+ const filteredRequests = react.useMemo(() => {
298
+ if (!searchQuery) return requests;
299
+ const lowerQuery = searchQuery.toLowerCase();
300
+ return requests.filter(
301
+ (req) => req.agentId.toLowerCase().includes(lowerQuery) || req.action.toLowerCase().includes(lowerQuery)
302
+ );
303
+ }, [requests, searchQuery]);
304
+ if (loading && requests.length === 0) {
305
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "animate-pulse space-y-4 p-6 bg-white rounded-xl shadow-sm border border-gray-200", children: [
306
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 bg-gray-200 rounded w-1/3 mb-6" }),
307
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-32 bg-gray-100 rounded-lg border border-gray-200" }),
308
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-32 bg-gray-100 rounded-lg border border-gray-200" })
309
+ ] });
310
+ }
311
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden flex flex-col max-h-[800px] font-sans", children: [
312
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-5 border-b border-gray-200 bg-gray-50/50 flex flex-col sm:flex-row sm:items-center justify-between gap-4", children: [
313
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
314
+ /* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "text-xl font-semibold text-gray-900 flex items-center gap-2", children: [
315
+ "Human-in-the-Loop Review",
316
+ requests.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bg-blue-100 text-blue-700 text-xs font-bold px-2.5 py-0.5 rounded-full", children: [
317
+ requests.length,
318
+ " Pending"
319
+ ] })
320
+ ] }),
321
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1", children: "Review and authorize agent actions that fell below confidence thresholds." })
322
+ ] }),
323
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
324
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400", children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, {}) }),
325
+ /* @__PURE__ */ jsxRuntime.jsx(
326
+ "input",
327
+ {
328
+ type: "text",
329
+ placeholder: "Search agents or actions...",
330
+ className: "pl-10 pr-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-full sm:w-64 transition-shadow",
331
+ value: searchQuery,
332
+ onChange: (e) => setSearchQuery(e.target.value)
333
+ }
334
+ )
335
+ ] })
336
+ ] }),
337
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-6 mt-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded-lg text-sm flex items-center gap-2", children: [
338
+ /* @__PURE__ */ jsxRuntime.jsx(XIcon, {}),
339
+ " ",
340
+ error
341
+ ] }),
342
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 overflow-y-auto flex-1 bg-gray-50/30", children: filteredRequests.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [
343
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-16 h-16 bg-green-50 rounded-full flex items-center justify-center mb-4 text-green-600", children: /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) }),
344
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-medium text-gray-900", children: "All caught up!" }),
345
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 mt-1 max-w-sm", children: searchQuery ? "No requests match your search criteria." : "There are no pending agent actions requiring human approval at this time." })
346
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: filteredRequests.map((req) => {
347
+ const isExpanded = expandedId === req.id;
348
+ const isProcessing = processingId === req.id;
349
+ const confidenceColor = req.confidence < 0.5 ? "text-red-700 bg-red-50 border-red-200" : req.confidence < 0.8 ? "text-yellow-700 bg-yellow-50 border-yellow-200" : "text-green-700 bg-green-50 border-green-200";
350
+ return /* @__PURE__ */ jsxRuntime.jsxs(
351
+ "div",
352
+ {
353
+ className: `bg-white border rounded-xl shadow-sm transition-all duration-200 ${isProcessing ? "opacity-50 pointer-events-none" : "hover:shadow-md border-gray-200"}`,
354
+ children: [
355
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row md:items-start justify-between gap-4", children: [
356
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
357
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-wrap mb-2", children: [
358
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-gray-900 text-base", children: req.agentId }),
359
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 text-sm", children: "requested" }),
360
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-2.5 py-1 bg-gray-100 text-gray-800 text-xs font-mono font-medium rounded-md border border-gray-200", children: req.action })
361
+ ] }),
362
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-600 leading-relaxed", children: [
363
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-700", children: "Reason:" }),
364
+ " ",
365
+ req.reason
366
+ ] }),
367
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 flex items-center gap-4 text-xs text-gray-500", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
368
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
369
+ new Date(req.timestamp).toLocaleString()
370
+ ] }) })
371
+ ] }),
372
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-end gap-3 min-w-[140px]", children: [
373
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `px-3 py-1.5 rounded-full border text-xs font-bold flex items-center gap-1.5 ${confidenceColor}`, children: [
374
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M13 10V3L4 14h7v7l9-11h-7z" }) }),
375
+ (req.confidence * 100).toFixed(1),
376
+ "% Confidence"
377
+ ] }),
378
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 w-full", children: [
379
+ /* @__PURE__ */ jsxRuntime.jsxs(
380
+ "button",
381
+ {
382
+ onClick: () => handleAction(req, false),
383
+ disabled: isProcessing,
384
+ className: "flex-1 flex justify-center items-center gap-1 bg-white border border-gray-300 hover:bg-red-50 hover:text-red-700 hover:border-red-200 text-gray-700 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
385
+ children: [
386
+ /* @__PURE__ */ jsxRuntime.jsx(XIcon, {}),
387
+ " Deny"
388
+ ]
389
+ }
390
+ ),
391
+ /* @__PURE__ */ jsxRuntime.jsxs(
392
+ "button",
393
+ {
394
+ onClick: () => handleAction(req, true),
395
+ disabled: isProcessing,
396
+ className: "flex-1 flex justify-center items-center gap-1 bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded-lg text-sm font-medium transition-colors shadow-sm",
397
+ children: [
398
+ /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}),
399
+ " Approve"
400
+ ]
401
+ }
402
+ )
403
+ ] })
404
+ ] })
405
+ ] }) }),
406
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-gray-100 bg-gray-50/50 rounded-b-xl", children: [
407
+ /* @__PURE__ */ jsxRuntime.jsxs(
408
+ "button",
409
+ {
410
+ onClick: () => setExpandedId(isExpanded ? null : req.id),
411
+ className: "w-full px-5 py-2.5 flex items-center justify-between text-xs font-medium text-gray-500 hover:text-gray-700 transition-colors",
412
+ children: [
413
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "View Resource Payload" }),
414
+ isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(ChevronUpIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, {})
415
+ ]
416
+ }
417
+ ),
418
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 pb-5 pt-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-gray-900 rounded-lg p-4 overflow-x-auto shadow-inner", children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs text-green-400 font-mono leading-relaxed", children: JSON.stringify(req.resource, null, 2) }) }) })
419
+ ] })
420
+ ]
421
+ },
422
+ req.id
423
+ );
424
+ }) }) })
425
+ ] });
426
+ };
427
+ var RefreshIcon = ({ className = "" }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.59-9.21l5.25 4.24" }) });
428
+ var ShieldIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) });
429
+ var AlertTriangleIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
430
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
431
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
432
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
433
+ ] });
434
+ var SearchIcon2 = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
435
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
436
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
437
+ ] });
438
+ var AgentReputationDashboard = ({
439
+ apiBaseUrl = "http://localhost:8080",
440
+ refreshIntervalMs = 1e4
441
+ }) => {
442
+ const [stats, setStats] = react.useState([]);
443
+ const [loading, setLoading] = react.useState(true);
444
+ const [isRefreshing, setIsRefreshing] = react.useState(false);
445
+ const [error, setError] = react.useState(null);
446
+ const [searchQuery, setSearchQuery] = react.useState("");
447
+ const [sortConfig, setSortConfig] = react.useState({
448
+ key: "hallucinationScore",
449
+ direction: "desc"
450
+ });
451
+ const fetchStats = async (showRefreshState = false) => {
452
+ if (showRefreshState) setIsRefreshing(true);
453
+ try {
454
+ const response = await fetch(`${apiBaseUrl}/v1/analytics/agents`);
455
+ if (!response.ok) throw new Error("Failed to fetch agent analytics");
456
+ const data = await response.json();
457
+ setStats(data.stats || []);
458
+ setError(null);
459
+ } catch (err) {
460
+ setError(err.message);
461
+ } finally {
462
+ setLoading(false);
463
+ setIsRefreshing(false);
464
+ }
465
+ };
466
+ react.useEffect(() => {
467
+ fetchStats();
468
+ const interval = setInterval(() => fetchStats(false), refreshIntervalMs);
469
+ return () => clearInterval(interval);
470
+ }, [apiBaseUrl, refreshIntervalMs]);
471
+ const handleSort = (key) => {
472
+ setSortConfig((prev) => ({
473
+ key,
474
+ direction: prev.key === key && prev.direction === "desc" ? "asc" : "desc"
475
+ }));
476
+ };
477
+ const processedStats = react.useMemo(() => {
478
+ let result = [...stats];
479
+ if (searchQuery) {
480
+ const lowerQuery = searchQuery.toLowerCase();
481
+ result = result.filter((agent) => agent.agentId.toLowerCase().includes(lowerQuery));
482
+ }
483
+ result.sort((a, b) => {
484
+ if (a[sortConfig.key] < b[sortConfig.key]) return sortConfig.direction === "asc" ? -1 : 1;
485
+ if (a[sortConfig.key] > b[sortConfig.key]) return sortConfig.direction === "asc" ? 1 : -1;
486
+ return 0;
487
+ });
488
+ return result;
489
+ }, [stats, searchQuery, sortConfig]);
490
+ const totalAgents = stats.length;
491
+ const totalActions = stats.reduce((sum, a) => sum + a.totalActions, 0);
492
+ const avgHallucination = stats.length ? stats.reduce((sum, a) => sum + a.hallucinationScore, 0) / stats.length : 0;
493
+ const highRiskAgents = stats.filter((a) => a.hallucinationScore > 50).length;
494
+ if (loading && stats.length === 0) {
495
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "animate-pulse space-y-4 p-6 bg-white rounded-xl shadow-sm border border-gray-200", children: [
496
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 bg-gray-200 rounded w-1/4 mb-6" }),
497
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-4 mb-6", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-24 bg-gray-100 rounded-lg" }, i)) }),
498
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-64 bg-gray-100 rounded-lg" })
499
+ ] });
500
+ }
501
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden font-sans", children: [
502
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-5 border-b border-gray-200 bg-gray-50/50 flex flex-col sm:flex-row sm:items-center justify-between gap-4", children: [
503
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
504
+ /* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "text-xl font-semibold text-gray-900 flex items-center gap-2", children: [
505
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-blue-600", children: /* @__PURE__ */ jsxRuntime.jsx(ShieldIcon, {}) }),
506
+ "Agent Reputation & Hallucination Index"
507
+ ] }),
508
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1", children: "Real-time monitoring of autonomous agent behavior and policy compliance." })
509
+ ] }),
510
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
511
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
512
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400", children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon2, {}) }),
513
+ /* @__PURE__ */ jsxRuntime.jsx(
514
+ "input",
515
+ {
516
+ type: "text",
517
+ placeholder: "Search agents...",
518
+ className: "pl-10 pr-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-full sm:w-48 transition-shadow",
519
+ value: searchQuery,
520
+ onChange: (e) => setSearchQuery(e.target.value)
521
+ }
522
+ )
523
+ ] }),
524
+ /* @__PURE__ */ jsxRuntime.jsx(
525
+ "button",
526
+ {
527
+ onClick: () => fetchStats(true),
528
+ className: "p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors border border-gray-200 bg-white",
529
+ title: "Refresh data",
530
+ children: /* @__PURE__ */ jsxRuntime.jsx(RefreshIcon, { className: isRefreshing ? "animate-spin" : "" })
531
+ }
532
+ )
533
+ ] })
534
+ ] }),
535
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-6 mt-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded-lg text-sm flex items-center gap-2", children: [
536
+ /* @__PURE__ */ jsxRuntime.jsx(AlertTriangleIcon, {}),
537
+ " ",
538
+ error
539
+ ] }),
540
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 p-6 bg-gray-50/30 border-b border-gray-200", children: [
541
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white p-4 rounded-xl border border-gray-200 shadow-sm", children: [
542
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium text-gray-500 mb-1", children: "Active Agents" }),
543
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-2xl font-bold text-gray-900", children: totalAgents })
544
+ ] }),
545
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white p-4 rounded-xl border border-gray-200 shadow-sm", children: [
546
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium text-gray-500 mb-1", children: "Total Actions Evaluated" }),
547
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-2xl font-bold text-gray-900", children: totalActions.toLocaleString() })
548
+ ] }),
549
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white p-4 rounded-xl border border-gray-200 shadow-sm", children: [
550
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium text-gray-500 mb-1", children: "Avg Hallucination Score" }),
551
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl font-bold text-gray-900 flex items-baseline gap-2", children: [
552
+ avgHallucination.toFixed(1),
553
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-normal text-gray-500", children: "/ 100" })
554
+ ] })
555
+ ] }),
556
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white p-4 rounded-xl border border-gray-200 shadow-sm", children: [
557
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium text-gray-500 mb-1", children: "High Risk Agents" }),
558
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl font-bold text-red-600 flex items-center gap-2", children: [
559
+ highRiskAgents,
560
+ highRiskAgents > 0 && /* @__PURE__ */ jsxRuntime.jsx(AlertTriangleIcon, {})
561
+ ] })
562
+ ] })
563
+ ] }),
564
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "min-w-full divide-y divide-gray-200", children: [
565
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: [
566
+ { key: "agentId", label: "Agent ID" },
567
+ { key: "totalActions", label: "Total Actions" },
568
+ { key: "allowedActions", label: "Allowed" },
569
+ { key: "deniedActions", label: "Denied" },
570
+ { key: "averageConfidence", label: "Avg Confidence" },
571
+ { key: "hallucinationScore", label: "Hallucination Risk" }
572
+ ].map((col) => /* @__PURE__ */ jsxRuntime.jsx(
573
+ "th",
574
+ {
575
+ onClick: () => handleSort(col.key),
576
+ className: "px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none group",
577
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
578
+ col.label,
579
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity ${sortConfig.key === col.key ? "opacity-100 text-blue-600" : ""}`, children: sortConfig.key === col.key ? sortConfig.direction === "asc" ? "\u2191" : "\u2193" : "\u2195" })
580
+ ] })
581
+ },
582
+ col.key
583
+ )) }) }),
584
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: processedStats.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsxs("td", { colSpan: 6, className: "px-6 py-12 text-center", children: [
585
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center justify-center w-12 h-12 rounded-full bg-gray-100 mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(ShieldIcon, {}) }),
586
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-gray-900", children: "No agents found" }),
587
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1", children: searchQuery ? "Try adjusting your search query." : "No agent activity has been recorded yet." })
588
+ ] }) }) : processedStats.map((agent) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "hover:bg-gray-50/80 transition-colors", children: [
589
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
590
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 w-8 rounded-full bg-blue-100 text-blue-700 flex items-center justify-center font-bold text-xs mr-3", children: agent.agentId.substring(0, 2).toUpperCase() }),
591
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
592
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold text-gray-900", children: agent.agentId }),
593
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-gray-500", children: [
594
+ "Last active: ",
595
+ new Date(agent.lastActive || Date.now()).toLocaleTimeString()
596
+ ] })
597
+ ] })
598
+ ] }) }),
599
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-600 font-medium", children: agent.totalActions.toLocaleString() }),
600
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800", children: agent.allowedActions.toLocaleString() }) }),
601
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${agent.deniedActions > 0 ? "bg-red-100 text-red-800" : "bg-gray-100 text-gray-800"}`, children: agent.deniedActions.toLocaleString() }) }),
602
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-600", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
603
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-16 bg-gray-200 rounded-full h-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(
604
+ "div",
605
+ {
606
+ className: "bg-blue-500 h-1.5 rounded-full",
607
+ style: { width: `${agent.averageConfidence * 100}%` }
608
+ }
609
+ ) }),
610
+ (agent.averageConfidence * 100).toFixed(1),
611
+ "%"
612
+ ] }) }),
613
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
614
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-24 bg-gray-200 rounded-full h-2.5 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
615
+ "div",
616
+ {
617
+ className: `h-full transition-all duration-500 ${agent.hallucinationScore > 50 ? "bg-red-500" : agent.hallucinationScore > 20 ? "bg-yellow-400" : "bg-green-500"}`,
618
+ style: { width: `${agent.hallucinationScore}%` }
619
+ }
620
+ ) }),
621
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm font-bold ${agent.hallucinationScore > 50 ? "text-red-600" : agent.hallucinationScore > 20 ? "text-yellow-600" : "text-green-600"}`, children: agent.hallucinationScore.toFixed(0) })
622
+ ] }) })
623
+ ] }, agent.agentId)) })
624
+ ] }) })
625
+ ] });
626
+ };
627
+
628
+ // src/react/index.ts
629
+ function useAgentPermission(actor, action, confidence = 1, opts = {}) {
630
+ const [state, setState] = react.useState({
631
+ canExecute: false,
632
+ loading: true,
633
+ reason: "",
634
+ decision: ""
635
+ });
636
+ react.useEffect(() => {
637
+ let cancelled = false;
638
+ setState({ canExecute: false, loading: true, reason: "", decision: "" });
639
+ const clientConfig = {
640
+ baseUrl: opts.baseUrl ?? "http://localhost:8080"
641
+ };
642
+ if (opts.apiKey !== void 0) {
643
+ clientConfig.apiKey = opts.apiKey;
644
+ }
645
+ const client = new LeluClient(clientConfig);
646
+ client.agentAuthorize({ actor, action, context: { confidence, scope: opts.scope } }).then((res) => {
647
+ if (cancelled) return;
648
+ setState({
649
+ canExecute: res.allowed,
650
+ loading: false,
651
+ reason: res.reason ?? (res.allowed ? "allowed" : "denied"),
652
+ decision: res.allowed ? "allowed" : res.requiresHumanReview ? "human_review" : "denied"
653
+ });
654
+ }).catch((err) => {
655
+ if (cancelled) return;
656
+ const msg = err instanceof Error ? err.message : String(err);
657
+ setState({ canExecute: false, loading: false, reason: msg, decision: "denied" });
658
+ });
659
+ return () => {
660
+ cancelled = true;
661
+ };
662
+ }, [actor, action, confidence, opts.baseUrl, opts.apiKey, opts.scope]);
663
+ return state;
664
+ }
665
+
666
+ exports.AgentReputationDashboard = AgentReputationDashboard;
667
+ exports.LeluApprovalUI = LeluApprovalUI;
668
+ exports.useAgentPermission = useAgentPermission;
669
+ //# sourceMappingURL=index.js.map
670
+ //# sourceMappingURL=index.js.map