agent-state-machine 1.4.2 → 2.0.1
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.
- package/README.md +2 -6
- package/bin/cli.js +4 -19
- package/package.json +4 -2
- package/vercel-server/api/events/[token].js +119 -0
- package/vercel-server/api/history/[token].js +51 -0
- package/vercel-server/api/session/[token].js +49 -0
- package/vercel-server/api/submit/[token].js +94 -0
- package/vercel-server/api/ws/cli.js +186 -0
- package/vercel-server/local-server.js +30 -329
- package/vercel-server/public/index.html +17 -5
- package/{lib → vercel-server}/ui/index.html +339 -114
- package/lib/ui/server.js +0 -150
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>
|
|
7
|
+
<title>{{WORKFLOW_NAME}} - Remote Follow</title>
|
|
8
8
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
9
|
<script>
|
|
10
10
|
tailwind.config = {
|
|
@@ -38,14 +38,36 @@
|
|
|
38
38
|
.dark ::-webkit-scrollbar-thumb:hover {
|
|
39
39
|
background: #3f3f46;
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
@keyframes pulse {
|
|
43
|
+
|
|
44
|
+
0%,
|
|
45
|
+
100% {
|
|
46
|
+
opacity: 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
50% {
|
|
50
|
+
opacity: .7;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.animate-pulse-slow {
|
|
55
|
+
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
56
|
+
}
|
|
41
57
|
</style>
|
|
42
58
|
</head>
|
|
43
59
|
|
|
44
60
|
<body>
|
|
45
61
|
<div id="root"></div>
|
|
46
62
|
|
|
63
|
+
<script>
|
|
64
|
+
// Placeholders replaced by server
|
|
65
|
+
window.SESSION_TOKEN = '{{SESSION_TOKEN}}';
|
|
66
|
+
window.WORKFLOW_NAME_TEMPLATE = '{{WORKFLOW_NAME}}';
|
|
67
|
+
</script>
|
|
68
|
+
|
|
47
69
|
<script type="text/babel">
|
|
48
|
-
const { useState, useEffect } = React;
|
|
70
|
+
const { useState, useEffect, useRef } = React;
|
|
49
71
|
|
|
50
72
|
// Icons
|
|
51
73
|
const SunIcon = () => (
|
|
@@ -72,6 +94,25 @@
|
|
|
72
94
|
</svg>
|
|
73
95
|
);
|
|
74
96
|
|
|
97
|
+
function StatusBadge({ status }) {
|
|
98
|
+
const colors = {
|
|
99
|
+
connected: 'bg-green-500',
|
|
100
|
+
disconnected: 'bg-red-500',
|
|
101
|
+
connecting: 'bg-yellow-500 animate-pulse-slow',
|
|
102
|
+
};
|
|
103
|
+
const labels = {
|
|
104
|
+
connected: 'Live',
|
|
105
|
+
disconnected: 'Offline',
|
|
106
|
+
connecting: 'Connecting...',
|
|
107
|
+
};
|
|
108
|
+
return (
|
|
109
|
+
<div className="flex items-center gap-2">
|
|
110
|
+
<div className={`w-2 h-2 rounded-full ${colors[status] || colors.disconnected}`}></div>
|
|
111
|
+
<span className="text-[10px] uppercase tracking-wider text-zinc-500 font-bold">{labels[status] || status}</span>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
75
116
|
function CopyButton({ text, className }) {
|
|
76
117
|
const [copied, setCopied] = useState(false);
|
|
77
118
|
|
|
@@ -101,8 +142,7 @@
|
|
|
101
142
|
const rawContent = isObject ? JSON.stringify(data, null, 2) : String(data);
|
|
102
143
|
const lineBreak = '';
|
|
103
144
|
|
|
104
|
-
//
|
|
105
|
-
// Headers render in CAPS, BOLD, and BLUE.
|
|
145
|
+
// "clean" view loops through ALL top-level keys.
|
|
106
146
|
let cleanParts = null;
|
|
107
147
|
if (isObject) {
|
|
108
148
|
const keys = Object.keys(data);
|
|
@@ -117,7 +157,7 @@
|
|
|
117
157
|
<div className="text-[11px] font-extrabold uppercase tracking-wider text-blue-600 dark:text-blue-400 mb-2">
|
|
118
158
|
{k}
|
|
119
159
|
</div>
|
|
120
|
-
<div className="whitespace-pre-wrap">
|
|
160
|
+
<div className="whitespace-pre-wrap leading-relaxed">
|
|
121
161
|
{prettyVal}
|
|
122
162
|
</div>
|
|
123
163
|
</div>
|
|
@@ -126,9 +166,7 @@
|
|
|
126
166
|
}
|
|
127
167
|
}
|
|
128
168
|
|
|
129
|
-
|
|
130
|
-
const rawContentUnescaped = rawContent; // keep raw exact
|
|
131
|
-
|
|
169
|
+
const rawContentUnescaped = rawContent;
|
|
132
170
|
const hasToggle = isObject || rawContent.includes('\\n');
|
|
133
171
|
|
|
134
172
|
if (onTop) {
|
|
@@ -193,107 +231,272 @@
|
|
|
193
231
|
);
|
|
194
232
|
}
|
|
195
233
|
|
|
234
|
+
function InteractionForm({ interaction, onSubmit, disabled }) {
|
|
235
|
+
const [response, setResponse] = useState('');
|
|
236
|
+
const [submitting, setSubmitting] = useState(false);
|
|
237
|
+
|
|
238
|
+
const handleSubmit = async (e) => {
|
|
239
|
+
e.preventDefault();
|
|
240
|
+
if (!response.trim() || submitting) return;
|
|
241
|
+
setSubmitting(true);
|
|
242
|
+
try {
|
|
243
|
+
await onSubmit(interaction.slug, interaction.targetKey, response.trim());
|
|
244
|
+
setResponse('');
|
|
245
|
+
} finally {
|
|
246
|
+
setSubmitting(false);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div className="bg-yellow-100/50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700/50 rounded-2xl p-6 mb-8 transition-all hover:border-yellow-300 dark:hover:border-yellow-600/50">
|
|
252
|
+
<div className="text-[10px] font-black uppercase tracking-[0.2em] text-yellow-600 dark:text-yellow-500 mb-3 flex items-center gap-2">
|
|
253
|
+
<span className="w-2 h-2 rounded-full bg-yellow-500 animate-pulse"></span>
|
|
254
|
+
Input Required
|
|
255
|
+
</div>
|
|
256
|
+
<div className="text-sm text-yellow-900 dark:text-yellow-100/90 mb-5 whitespace-pre-wrap leading-relaxed italic">
|
|
257
|
+
{interaction.question || 'Please provide your input.'}
|
|
258
|
+
</div>
|
|
259
|
+
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
260
|
+
<textarea
|
|
261
|
+
value={response}
|
|
262
|
+
onChange={(e) => setResponse(e.target.value)}
|
|
263
|
+
className="w-full p-4 bg-white dark:bg-zinc-800 border border-yellow-200 dark:border-zinc-700 rounded-xl text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-1 focus:ring-yellow-500 transition-all min-h-[100px]"
|
|
264
|
+
placeholder="Type your response here..."
|
|
265
|
+
disabled={submitting || disabled}
|
|
266
|
+
/>
|
|
267
|
+
<div className="flex justify-end items-center gap-4">
|
|
268
|
+
{disabled && <span className="text-[10px] uppercase font-bold text-red-500 tracking-wider">CLI Offline</span>}
|
|
269
|
+
<button
|
|
270
|
+
type="submit"
|
|
271
|
+
disabled={submitting || disabled || !response.trim()}
|
|
272
|
+
className="px-6 py-2.5 bg-yellow-500 hover:bg-yellow-600 text-white font-bold text-xs uppercase tracking-[0.15em] rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition-all shadow-sm"
|
|
273
|
+
>
|
|
274
|
+
{submitting ? 'Submitting...' : 'Submit Response'}
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
</form>
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
196
282
|
function App() {
|
|
197
283
|
const [history, setHistory] = useState([]);
|
|
198
284
|
const [loading, setLoading] = useState(true);
|
|
199
285
|
const [error, setError] = useState(null);
|
|
200
|
-
const [
|
|
286
|
+
const [status, setStatus] = useState('connecting');
|
|
287
|
+
const [workflowName, setWorkflowName] = useState(window.WORKFLOW_NAME_TEMPLATE || '');
|
|
201
288
|
const [theme, setTheme] = useState('dark');
|
|
202
|
-
const [
|
|
289
|
+
const [sortNewest, setSortNewest] = useState(true);
|
|
290
|
+
const [pendingInteraction, setPendingInteraction] = useState(null);
|
|
291
|
+
|
|
292
|
+
const token = window.SESSION_TOKEN && window.SESSION_TOKEN !== '{{' + 'SESSION_TOKEN' + '}}' ? window.SESSION_TOKEN : null;
|
|
293
|
+
|
|
294
|
+
// Build API URLs
|
|
295
|
+
const historyUrl = token ? `/api/history/${token}` : '/api/history';
|
|
296
|
+
const eventsUrl = token ? `/api/events/${token}` : '/api/events';
|
|
297
|
+
const submitUrl = token ? `/api/submit/${token}` : '/api/submit';
|
|
203
298
|
|
|
299
|
+
// Detect pending interactions
|
|
204
300
|
useEffect(() => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
301
|
+
if (history.length === 0) {
|
|
302
|
+
setPendingInteraction(null);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const resolvedSlugs = new Set();
|
|
307
|
+
let pending = null;
|
|
308
|
+
|
|
309
|
+
for (const entry of history) {
|
|
310
|
+
const isResolution = entry.event === 'INTERACTION_RESOLVED' ||
|
|
311
|
+
entry.event === 'PROMPT_ANSWERED' ||
|
|
312
|
+
entry.event === 'INTERACTION_SUBMITTED';
|
|
313
|
+
const isRequest = entry.event === 'INTERACTION_REQUESTED' ||
|
|
314
|
+
entry.event === 'PROMPT_REQUESTED';
|
|
315
|
+
|
|
316
|
+
if (isResolution && entry.slug) {
|
|
317
|
+
resolvedSlugs.add(entry.slug);
|
|
318
|
+
}
|
|
218
319
|
|
|
219
|
-
|
|
320
|
+
if (isRequest && entry.slug && !resolvedSlugs.has(entry.slug) && !pending) {
|
|
321
|
+
pending = {
|
|
322
|
+
slug: entry.slug,
|
|
323
|
+
targetKey: entry.targetKey || `_interaction_${entry.slug}`,
|
|
324
|
+
question: entry.question,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
setPendingInteraction(pending);
|
|
330
|
+
}, [history]);
|
|
331
|
+
|
|
332
|
+
const fetchData = async () => {
|
|
333
|
+
try {
|
|
334
|
+
const res = await fetch(historyUrl);
|
|
335
|
+
const data = await res.json();
|
|
336
|
+
if (data.entries) setHistory(data.entries);
|
|
337
|
+
if (data.workflowName) setWorkflowName(data.workflowName);
|
|
338
|
+
|
|
339
|
+
// In remote mode, the API also tells us connectivity
|
|
340
|
+
if (token && data.cliConnected !== undefined) {
|
|
341
|
+
setStatus(data.cliConnected ? 'connected' : 'disconnected');
|
|
342
|
+
} else if (!token) {
|
|
343
|
+
// Local mode is always "connected" if the page loads
|
|
344
|
+
setStatus('connected');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
setLoading(false);
|
|
348
|
+
return true;
|
|
349
|
+
} catch (err) {
|
|
350
|
+
setError(err.message);
|
|
351
|
+
setLoading(false);
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
useEffect(() => {
|
|
220
357
|
fetchData();
|
|
221
358
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
359
|
+
let eventSource = null;
|
|
360
|
+
let reconnectTimeout = null;
|
|
361
|
+
let pollInterval = null;
|
|
362
|
+
let reconnectAttempts = 0;
|
|
363
|
+
|
|
364
|
+
const connect = () => {
|
|
365
|
+
if (eventSource) eventSource.close();
|
|
366
|
+
eventSource = new EventSource(eventsUrl);
|
|
367
|
+
|
|
368
|
+
eventSource.onopen = () => {
|
|
369
|
+
setStatus('connected');
|
|
370
|
+
reconnectAttempts = 0;
|
|
226
371
|
fetchData();
|
|
227
|
-
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
eventSource.onerror = () => {
|
|
375
|
+
setStatus('disconnected');
|
|
376
|
+
eventSource.close();
|
|
377
|
+
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 10000);
|
|
378
|
+
reconnectAttempts++;
|
|
379
|
+
reconnectTimeout = setTimeout(connect, delay);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
eventSource.onmessage = (e) => {
|
|
383
|
+
try {
|
|
384
|
+
if (e.data === 'update') {
|
|
385
|
+
fetchData();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const data = JSON.parse(e.data);
|
|
390
|
+
switch (data.type) {
|
|
391
|
+
case 'status':
|
|
392
|
+
setStatus(data.cliConnected ? 'connected' : 'disconnected');
|
|
393
|
+
if (data.workflowName) setWorkflowName(data.workflowName);
|
|
394
|
+
break;
|
|
395
|
+
case 'history': setHistory(data.entries || []); break;
|
|
396
|
+
case 'event':
|
|
397
|
+
setHistory(prev => {
|
|
398
|
+
if (data.event === 'INTERACTION_SUBMITTED' && data.slug) {
|
|
399
|
+
const hasDupe = prev.some(e =>
|
|
400
|
+
e.event === 'INTERACTION_SUBMITTED' && e.slug === data.slug
|
|
401
|
+
);
|
|
402
|
+
if (hasDupe) return prev;
|
|
403
|
+
}
|
|
404
|
+
return [data, ...prev];
|
|
405
|
+
});
|
|
406
|
+
break;
|
|
407
|
+
case 'cli_connected':
|
|
408
|
+
case 'cli_reconnected': setStatus('connected'); break;
|
|
409
|
+
case 'cli_disconnected': setStatus('disconnected'); break;
|
|
410
|
+
}
|
|
411
|
+
} catch (err) { /* Not JSON or update ping */ }
|
|
412
|
+
};
|
|
228
413
|
};
|
|
229
414
|
|
|
415
|
+
connect();
|
|
416
|
+
pollInterval = setInterval(fetchData, 10000);
|
|
417
|
+
|
|
230
418
|
return () => {
|
|
231
|
-
eventSource.close();
|
|
419
|
+
if (eventSource) eventSource.close();
|
|
420
|
+
if (reconnectTimeout) clearTimeout(reconnectTimeout);
|
|
421
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
232
422
|
};
|
|
233
423
|
}, []);
|
|
234
424
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
425
|
+
const handleSubmit = async (slug, targetKey, response) => {
|
|
426
|
+
const optimisticEvent = {
|
|
427
|
+
timestamp: new Date().toISOString(),
|
|
428
|
+
event: 'INTERACTION_SUBMITTED',
|
|
429
|
+
slug,
|
|
430
|
+
targetKey,
|
|
431
|
+
answer: response.substring(0, 200) + (response.length > 200 ? '...' : ''),
|
|
432
|
+
source: 'remote',
|
|
433
|
+
};
|
|
434
|
+
setHistory(prev => [optimisticEvent, ...prev]);
|
|
238
435
|
|
|
239
|
-
|
|
240
|
-
|
|
436
|
+
try {
|
|
437
|
+
const res = await fetch(submitUrl, {
|
|
438
|
+
method: 'POST',
|
|
439
|
+
headers: { 'Content-Type': 'application/json' },
|
|
440
|
+
body: JSON.stringify({ slug, targetKey, response }),
|
|
441
|
+
});
|
|
442
|
+
if (!res.ok) {
|
|
443
|
+
setHistory(prev => prev.filter(e => e !== optimisticEvent));
|
|
444
|
+
const error = await res.json();
|
|
445
|
+
throw new Error(error.error || 'Failed to submit');
|
|
446
|
+
}
|
|
447
|
+
setTimeout(fetchData, 1000);
|
|
448
|
+
} catch (err) {
|
|
449
|
+
setHistory(prev => prev.filter(e => e !== optimisticEvent));
|
|
450
|
+
alert(err.message);
|
|
451
|
+
}
|
|
241
452
|
};
|
|
242
453
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
<div className="flex items-center justify-center h-screen bg-gray-50 dark:bg-black text-gray-500 dark:text-zinc-500">
|
|
246
|
-
Loading history...
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
);
|
|
454
|
+
const toggleTheme = () => setTheme(prev => prev === 'dark' ? 'light' : 'dark');
|
|
455
|
+
const toggleSort = () => setSortNewest(prev => !prev);
|
|
250
456
|
|
|
251
|
-
if (
|
|
457
|
+
if (loading && !history.length) return (
|
|
252
458
|
<div className={theme}>
|
|
253
|
-
<div className="flex items-center justify-center h-screen bg-gray-50 dark:bg-black text-
|
|
254
|
-
|
|
459
|
+
<div className="flex items-center justify-center h-screen bg-gray-50 dark:bg-black text-zinc-500 uppercase tracking-widest text-[10px] font-black">
|
|
460
|
+
Opening Terminal...
|
|
255
461
|
</div>
|
|
256
462
|
</div>
|
|
257
463
|
);
|
|
258
464
|
|
|
259
|
-
|
|
260
|
-
// We only filter out nulls or malformed entries if any
|
|
261
|
-
let visibleEvents = history;
|
|
262
|
-
|
|
263
|
-
// Apply Sort
|
|
264
|
-
// History from API is "Newest First" (index 0 is latest)
|
|
265
|
-
if (sortOrder === 'oldest') {
|
|
266
|
-
visibleEvents = [...visibleEvents].reverse();
|
|
267
|
-
}
|
|
268
|
-
|
|
465
|
+
let visibleEvents = sortNewest ? history : [...history].reverse();
|
|
269
466
|
const formatTime = (ts) => new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
270
467
|
|
|
271
468
|
return (
|
|
272
469
|
<div className={theme}>
|
|
273
|
-
<div className="min-h-screen bg-gray-50 dark:bg-black transition-colors duration-
|
|
274
|
-
<div className="max-w-
|
|
470
|
+
<div className="min-h-screen bg-gray-50 dark:bg-black transition-colors duration-500">
|
|
471
|
+
<div className="max-w-4xl mx-auto min-h-screen flex flex-col p-6">
|
|
275
472
|
|
|
276
473
|
{/* Sticky Header */}
|
|
277
|
-
<header className="sticky top-0 z-50 py-
|
|
278
|
-
<div
|
|
279
|
-
<h1 className="text-xl font-
|
|
280
|
-
|
|
474
|
+
<header className="sticky top-0 z-50 py-6 bg-gray-50/90 dark:bg-black/90 backdrop-blur-md border-b border-gray-200 dark:border-zinc-800 flex items-center justify-between mb-8 transition-all">
|
|
475
|
+
<div>
|
|
476
|
+
<h1 className="text-xl font-black text-gray-800 dark:text-zinc-100 transition-colors uppercase tracking-tight">
|
|
477
|
+
{workflowName || 'Workflow'}
|
|
478
|
+
</h1>
|
|
479
|
+
<div className="flex items-center gap-3 mt-1.5">
|
|
480
|
+
<div className="text-zinc-400 dark:text-zinc-600 text-[10px] font-black uppercase tracking-widest">
|
|
481
|
+
{token ? 'Remote Follow' : 'Local Terminal'}
|
|
482
|
+
</div>
|
|
483
|
+
<div className="w-1 h-1 rounded-full bg-zinc-300 dark:bg-zinc-800"></div>
|
|
484
|
+
<StatusBadge status={status} />
|
|
485
|
+
</div>
|
|
281
486
|
</div>
|
|
282
|
-
<div className="flex items-center space-x-
|
|
487
|
+
<div className="flex items-center space-x-3">
|
|
283
488
|
<button
|
|
284
489
|
onClick={toggleSort}
|
|
285
|
-
className="p-2 rounded-
|
|
286
|
-
title={
|
|
490
|
+
className="p-2.5 rounded-xl bg-gray-200 dark:bg-zinc-900 text-gray-600 dark:text-zinc-400 hover:text-blue-500 transition-all border border-transparent hover:border-gray-300 dark:hover:border-zinc-700"
|
|
491
|
+
title={sortNewest ? "Sort: Newest First" : "Sort: Oldest First"}
|
|
287
492
|
>
|
|
288
|
-
{
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12" transform="scale(1, -1) translate(0, -24)" /></svg>
|
|
292
|
-
}
|
|
493
|
+
<svg xmlns="http://www.w3.org/2000/svg" className={`h-5 w-5 transition-transform ${!sortNewest ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
494
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12" />
|
|
495
|
+
</svg>
|
|
293
496
|
</button>
|
|
294
497
|
<button
|
|
295
498
|
onClick={toggleTheme}
|
|
296
|
-
className="p-2 rounded-
|
|
499
|
+
className="p-2.5 rounded-xl bg-gray-200 dark:bg-zinc-900 text-gray-600 dark:text-zinc-400 hover:text-blue-500 transition-all border border-transparent hover:border-gray-300 dark:hover:border-zinc-700"
|
|
297
500
|
title="Toggle Theme"
|
|
298
501
|
>
|
|
299
502
|
{theme === 'dark' ? <SunIcon /> : <MoonIcon />}
|
|
@@ -301,10 +504,30 @@
|
|
|
301
504
|
</div>
|
|
302
505
|
</header>
|
|
303
506
|
|
|
507
|
+
{/* Pending Interaction at Top */}
|
|
508
|
+
{pendingInteraction && (
|
|
509
|
+
<InteractionForm
|
|
510
|
+
interaction={pendingInteraction}
|
|
511
|
+
onSubmit={handleSubmit}
|
|
512
|
+
disabled={status !== 'connected'}
|
|
513
|
+
/>
|
|
514
|
+
)}
|
|
515
|
+
|
|
516
|
+
{/* Disconnected Warning */}
|
|
517
|
+
{status === 'disconnected' && !pendingInteraction && (
|
|
518
|
+
<div className="bg-red-50 dark:bg-red-900/10 border border-red-200 dark:border-red-900/40 rounded-2xl p-4 mb-8 text-center">
|
|
519
|
+
<div className="text-[10px] font-black uppercase tracking-widest text-red-600 dark:text-red-500">
|
|
520
|
+
Terminal Connection Lost • Retrying...
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
)}
|
|
524
|
+
|
|
304
525
|
{/* Content */}
|
|
305
|
-
<div className="flex-1 space-y-
|
|
526
|
+
<div className="flex-1 space-y-12">
|
|
306
527
|
{visibleEvents.length === 0 && (
|
|
307
|
-
<div className="text-center text-
|
|
528
|
+
<div className="text-center text-zinc-400 dark:text-zinc-700 py-20 uppercase text-[10px] font-bold tracking-[0.3em]">
|
|
529
|
+
Waiting for events...
|
|
530
|
+
</div>
|
|
308
531
|
)}
|
|
309
532
|
|
|
310
533
|
{visibleEvents.map((item, idx) => {
|
|
@@ -312,19 +535,21 @@
|
|
|
312
535
|
if (item.event.startsWith('WORKFLOW_')) {
|
|
313
536
|
const colorMap = {
|
|
314
537
|
'WORKFLOW_STARTED': 'text-green-500 dark:text-green-400',
|
|
315
|
-
'WORKFLOW_COMPLETED': 'text-blue-
|
|
538
|
+
'WORKFLOW_COMPLETED': 'text-blue-510 dark:text-blue-400',
|
|
316
539
|
'WORKFLOW_FAILED': 'text-red-500 dark:text-red-400',
|
|
317
540
|
'WORKFLOW_RESET': 'text-yellow-500 dark:text-yellow-400'
|
|
318
541
|
};
|
|
319
542
|
return (
|
|
320
543
|
<div key={idx} className="flex flex-col items-center py-4">
|
|
321
|
-
<div className="flex items-center space-x-
|
|
322
|
-
<div className="h-px w-
|
|
323
|
-
<span className={colorMap[item.event]
|
|
324
|
-
|
|
325
|
-
|
|
544
|
+
<div className="flex items-center space-x-4 text-[10px] uppercase tracking-[0.25em] font-black text-zinc-300 dark:text-zinc-800">
|
|
545
|
+
<div className="h-px w-10 bg-current opacity-20"></div>
|
|
546
|
+
<span className={`${colorMap[item.event] || 'text-zinc-500'} dark:opacity-80`}>
|
|
547
|
+
{item.event.replace('WORKFLOW_', '')}
|
|
548
|
+
</span>
|
|
549
|
+
<span className="text-zinc-400 dark:text-zinc-700 font-medium tracking-normal">{formatTime(item.timestamp)}</span>
|
|
550
|
+
<div className="h-px w-10 bg-current opacity-20"></div>
|
|
326
551
|
</div>
|
|
327
|
-
{item.error && <div className="mt-2 text-red-500 text-xs font-mono">{item.error}</div>}
|
|
552
|
+
{item.error && <div className="mt-2 text-red-500 text-xs font-mono max-w-md text-center">{item.error}</div>}
|
|
328
553
|
</div>
|
|
329
554
|
);
|
|
330
555
|
}
|
|
@@ -333,9 +558,9 @@
|
|
|
333
558
|
if (item.event === 'AGENT_STARTED') {
|
|
334
559
|
return (
|
|
335
560
|
<div key={idx} className="flex justify-start">
|
|
336
|
-
<div className="text-[10px] text-zinc-400 dark:text-zinc-600 uppercase tracking-widest flex items-center space-x-2">
|
|
561
|
+
<div className="text-[10px] text-zinc-400 dark:text-zinc-600 uppercase tracking-widest font-bold flex items-center space-x-2">
|
|
337
562
|
<span className="w-1.5 h-1.5 rounded-full bg-blue-500/50 animate-pulse"></span>
|
|
338
|
-
<span>Agent <span className="text-zinc-
|
|
563
|
+
<span>Agent <span className="text-zinc-800 dark:text-zinc-300">{item.agent}</span> started</span>
|
|
339
564
|
<span>•</span>
|
|
340
565
|
<span>{formatTime(item.timestamp)}</span>
|
|
341
566
|
</div>
|
|
@@ -347,9 +572,9 @@
|
|
|
347
572
|
if (item.event === 'AGENT_FAILED') {
|
|
348
573
|
return (
|
|
349
574
|
<div key={idx} className="flex justify-center">
|
|
350
|
-
<div className="bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-900/50 rounded-
|
|
351
|
-
<div className="font-
|
|
352
|
-
<div>{item.error}</div>
|
|
575
|
+
<div className="bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-900/50 rounded-2xl px-6 py-4 text-red-600 dark:text-red-400 text-xs font-mono w-full max-w-2xl shadow-sm">
|
|
576
|
+
<div className="font-black mb-2 uppercase tracking-tight">AGENT FAILED: {item.agent}</div>
|
|
577
|
+
<div className="leading-relaxed opacity-80">{item.error}</div>
|
|
353
578
|
</div>
|
|
354
579
|
</div>
|
|
355
580
|
);
|
|
@@ -357,14 +582,14 @@
|
|
|
357
582
|
|
|
358
583
|
// 4. Interaction / Prompt Requested
|
|
359
584
|
if (item.event === 'INTERACTION_REQUESTED' || item.event === 'PROMPT_REQUESTED') {
|
|
360
|
-
const isPrompt = item.event === 'PROMPT_REQUESTED';
|
|
361
585
|
return (
|
|
362
|
-
<div key={idx} className="flex justify-center">
|
|
363
|
-
<div className="bg-zinc-100 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 border-dashed rounded-
|
|
364
|
-
<div className="text-[10px] text-zinc-400 dark:text-zinc-600 uppercase font-
|
|
365
|
-
|
|
586
|
+
<div key={idx} className="flex justify-center animate-in fade-in slide-in-from-bottom-2">
|
|
587
|
+
<div className="bg-zinc-100 dark:bg-zinc-900/50 border border-zinc-200 dark:border-zinc-800 border-dashed rounded-2xl px-8 py-6 text-center max-w-md w-full">
|
|
588
|
+
<div className="text-[10px] text-zinc-400 dark:text-zinc-600 uppercase font-black tracking-[0.2em] mb-2 flex items-center justify-center gap-2">
|
|
589
|
+
<span className="w-1 h-1 rounded-full bg-yellow-500"></span>
|
|
590
|
+
Intervention Required
|
|
366
591
|
</div>
|
|
367
|
-
<div className="text-xs text-zinc-600 dark:text-zinc-400 italic">
|
|
592
|
+
<div className="text-xs text-zinc-600 dark:text-zinc-400 italic font-medium leading-relaxed">
|
|
368
593
|
{item.question ? `"${item.question}"` : `Waiting for response to "${item.slug}"...`}
|
|
369
594
|
</div>
|
|
370
595
|
</div>
|
|
@@ -373,14 +598,15 @@
|
|
|
373
598
|
}
|
|
374
599
|
|
|
375
600
|
// 5. Prompt Answered
|
|
376
|
-
if (item.event === 'PROMPT_ANSWERED') {
|
|
601
|
+
if (item.event === 'PROMPT_ANSWERED' || item.event === 'INTERACTION_SUBMITTED') {
|
|
602
|
+
const isManual = item.source === 'remote';
|
|
377
603
|
return (
|
|
378
604
|
<div key={idx} className="flex justify-center">
|
|
379
|
-
<div className="bg-green-50 dark:bg-green-950/
|
|
380
|
-
<div className="text-[
|
|
381
|
-
User Answered
|
|
605
|
+
<div className="bg-green-50/50 dark:bg-green-950/10 border border-green-200/50 dark:border-green-900/30 rounded-2xl px-6 py-3 text-center max-w-md w-full">
|
|
606
|
+
<div className="text-[9px] text-green-600 dark:text-green-500 uppercase font-black tracking-widest mb-1">
|
|
607
|
+
{isManual ? 'Resolved via Browser' : 'User Answered'}
|
|
382
608
|
</div>
|
|
383
|
-
<div className="text-xs text-green-
|
|
609
|
+
<div className="text-xs text-green-800 dark:text-green-300 italic font-bold">
|
|
384
610
|
"{item.answer}"
|
|
385
611
|
</div>
|
|
386
612
|
</div>
|
|
@@ -388,20 +614,19 @@
|
|
|
388
614
|
);
|
|
389
615
|
}
|
|
390
616
|
|
|
391
|
-
// 6. Agent Completed / Interaction Resolved
|
|
617
|
+
// 6. Agent Completed / Interaction Resolved
|
|
392
618
|
if (item.event === 'AGENT_COMPLETED' || item.event === 'INTERACTION_RESOLVED') {
|
|
393
619
|
return (
|
|
394
|
-
<div key={idx} className="flex flex-col space-y-
|
|
620
|
+
<div key={idx} className="flex flex-col space-y-6">
|
|
395
621
|
{/* Header Line */}
|
|
396
|
-
<div className="flex items-center justify-center space-x-
|
|
397
|
-
<
|
|
398
|
-
<span
|
|
399
|
-
<span>
|
|
400
|
-
<
|
|
401
|
-
<span>{formatTime(item.timestamp)}</span>
|
|
622
|
+
<div className="flex items-center justify-center space-x-3 text-[10px] text-zinc-300 dark:text-zinc-800 uppercase tracking-widest font-black">
|
|
623
|
+
<div className="h-px flex-1 bg-current opacity-20"></div>
|
|
624
|
+
<span className="text-zinc-500 dark:text-zinc-400">{item.agent || item.slug}</span>
|
|
625
|
+
<span className="text-zinc-400 dark:text-zinc-700 font-medium">DONE • {formatTime(item.timestamp)}</span>
|
|
626
|
+
<div className="h-px flex-1 bg-current opacity-20"></div>
|
|
402
627
|
</div>
|
|
403
628
|
|
|
404
|
-
{/* Output (Response) -
|
|
629
|
+
{/* Output (Response) - ON TOP */}
|
|
405
630
|
{(item.output || item.result) && (
|
|
406
631
|
<JsonView
|
|
407
632
|
data={item.output || item.result}
|
|
@@ -410,13 +635,13 @@
|
|
|
410
635
|
/>
|
|
411
636
|
)}
|
|
412
637
|
|
|
413
|
-
{/* Prompt (Input) -
|
|
638
|
+
{/* Prompt (Input) - ON BOTTOM */}
|
|
414
639
|
{item.prompt && (
|
|
415
640
|
<div className="flex justify-start w-full group">
|
|
416
|
-
<div className="max-w-[85%] bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 rounded-2xl rounded-tl-none shadow-sm p-
|
|
417
|
-
<div className="flex justify-between items-center mb-
|
|
418
|
-
<div className="text-[9px] font-black text-zinc-300 dark:text-zinc-700 uppercase tracking-[0.
|
|
419
|
-
<div className="absolute top-
|
|
641
|
+
<div className="max-w-[85%] bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 rounded-2xl rounded-tl-none shadow-sm p-8 transition-all hover:border-zinc-300 dark:hover:border-zinc-700 relative">
|
|
642
|
+
<div className="flex justify-between items-center mb-6">
|
|
643
|
+
<div className="text-[9px] font-black text-zinc-300 dark:text-zinc-700 uppercase tracking-[0.3em]">Prompt / Input</div>
|
|
644
|
+
<div className="absolute top-6 right-6 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
420
645
|
<CopyButton text={item.prompt} className="text-gray-400 hover:text-gray-600 dark:text-zinc-600 dark:hover:text-zinc-400" />
|
|
421
646
|
</div>
|
|
422
647
|
</div>
|
|
@@ -430,12 +655,12 @@
|
|
|
430
655
|
);
|
|
431
656
|
}
|
|
432
657
|
|
|
433
|
-
// 7. CATCH-ALL
|
|
658
|
+
// 7. CATCH-ALL
|
|
434
659
|
return (
|
|
435
660
|
<div key={idx} className="flex justify-center px-4">
|
|
436
661
|
<JsonView
|
|
437
662
|
data={JSON.parse(JSON.stringify(item, (key, value) => {
|
|
438
|
-
if (key === 'event' || key === 'timestamp') return undefined;
|
|
663
|
+
if (key === 'event' || key === 'timestamp') return undefined;
|
|
439
664
|
return value;
|
|
440
665
|
}))}
|
|
441
666
|
label={item.event}
|
|
@@ -446,8 +671,8 @@
|
|
|
446
671
|
})}
|
|
447
672
|
</div>
|
|
448
673
|
|
|
449
|
-
<footer className="mt-
|
|
450
|
-
|
|
674
|
+
<footer className="mt-32 mb-12 text-center text-zinc-300 dark:text-zinc-800 text-[10px] font-black uppercase tracking-[0.4em] transition-colors">
|
|
675
|
+
SUPAMACHINE • Terminal v1.4
|
|
451
676
|
</footer>
|
|
452
677
|
</div>
|
|
453
678
|
</div>
|
|
@@ -460,4 +685,4 @@
|
|
|
460
685
|
</script>
|
|
461
686
|
</body>
|
|
462
687
|
|
|
463
|
-
</html>
|
|
688
|
+
</html>
|