agent-state-machine 2.2.1 → 2.3.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.
Files changed (33) hide show
  1. package/bin/cli.js +33 -5
  2. package/lib/file-tree.js +1 -1
  3. package/lib/runtime/agent.js +6 -2
  4. package/lib/runtime/interaction.js +2 -1
  5. package/lib/runtime/prompt.js +37 -1
  6. package/lib/runtime/runtime.js +67 -5
  7. package/lib/setup.js +4 -4
  8. package/package.json +1 -1
  9. package/templates/project-builder/agents/code-fixer.md +50 -0
  10. package/templates/project-builder/agents/code-writer.md +3 -0
  11. package/templates/project-builder/agents/sanity-checker.md +6 -0
  12. package/templates/project-builder/agents/test-planner.md +3 -1
  13. package/templates/project-builder/config.js +4 -4
  14. package/templates/project-builder/scripts/workflow-helpers.js +104 -2
  15. package/templates/project-builder/workflow.js +151 -14
  16. package/templates/starter/config.js +1 -1
  17. package/vercel-server/api/submit/[token].js +0 -11
  18. package/vercel-server/local-server.js +0 -19
  19. package/vercel-server/public/remote/assets/index-BTLc1QSv.js +168 -0
  20. package/vercel-server/public/remote/assets/index-DLa4X08t.css +1 -0
  21. package/vercel-server/public/remote/index.html +2 -2
  22. package/vercel-server/ui/src/App.jsx +53 -18
  23. package/vercel-server/ui/src/components/ChoiceInteraction.jsx +69 -18
  24. package/vercel-server/ui/src/components/ConfirmInteraction.jsx +7 -7
  25. package/vercel-server/ui/src/components/ContentCard.jsx +607 -103
  26. package/vercel-server/ui/src/components/EventsLog.jsx +20 -13
  27. package/vercel-server/ui/src/components/Footer.jsx +9 -4
  28. package/vercel-server/ui/src/components/Header.jsx +12 -3
  29. package/vercel-server/ui/src/components/SendingCard.jsx +33 -0
  30. package/vercel-server/ui/src/components/TextInteraction.jsx +8 -8
  31. package/vercel-server/ui/src/index.css +82 -10
  32. package/vercel-server/public/remote/assets/index-CbgeVnKw.js +0 -148
  33. package/vercel-server/public/remote/assets/index-DHL_iHQW.css +0 -1
@@ -1,5 +1,36 @@
1
+ import { useEffect, useState } from "react";
1
2
  import CopyButton from "./CopyButton.jsx";
2
- import { Bot, Brain, ChevronRight } from "lucide-react";
3
+ import { Bot, Brain, ChevronRight, Search, X } from "lucide-react";
4
+
5
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6
+
7
+ const highlightText = (text, query) => {
8
+ const source = String(text ?? "");
9
+ const q = (query || "").trim();
10
+ if (!q) return source;
11
+ const regex = new RegExp(escapeRegExp(q), "gi");
12
+ const matches = source.match(regex);
13
+ if (!matches) return source;
14
+
15
+ const parts = source.split(regex);
16
+ const nodes = [];
17
+
18
+ parts.forEach((part, index) => {
19
+ nodes.push(part);
20
+ if (index < matches.length) {
21
+ nodes.push(
22
+ <mark
23
+ key={`mark-${index}`}
24
+ className="rounded bg-black/10 dark:bg-white/20 px-1"
25
+ >
26
+ {matches[index]}
27
+ </mark>
28
+ );
29
+ }
30
+ });
31
+
32
+ return nodes;
33
+ };
3
34
 
4
35
  function AgentStartedIcon({ className = "" }) {
5
36
  return (
@@ -12,6 +43,106 @@ function AgentStartedIcon({ className = "" }) {
12
43
  );
13
44
  }
14
45
 
46
+ function MonoBlock({ children }) {
47
+ return (
48
+ <pre className="text-xs sm:text-sm font-mono leading-relaxed whitespace-pre-wrap break-words overflow-auto max-h-[60vh] p-6 rounded-[24px] border border-black bg-black text-white/90 dark:border-white dark:bg-white dark:text-black/90">
49
+ {children}
50
+ </pre>
51
+ );
52
+ }
53
+
54
+ function TextBlock({ children }) {
55
+ return (
56
+ <div className="rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6 text-base sm:text-lg leading-relaxed whitespace-pre-wrap break-words text-black/80 dark:text-white/80">
57
+ {children}
58
+ </div>
59
+ );
60
+ }
61
+
62
+ function PanelBody({ as: Component = "div", className = "", children }) {
63
+ return (
64
+ <Component
65
+ className={`border-t border-black/10 dark:border-white/10 p-6 ${className}`}
66
+ >
67
+ {children}
68
+ </Component>
69
+ );
70
+ }
71
+
72
+ function RawToggle({ open, onToggle }) {
73
+ return (
74
+ <button
75
+ type="button"
76
+ onClick={onToggle}
77
+ className="px-3 py-1 rounded-full text-[10px] font-bold tracking-[0.2em] uppercase border border-black/20 dark:border-white/20 text-black/60 dark:text-white/60 hover:text-black dark:hover:text-white hover:border-black/40 dark:hover:border-white/40 transition-colors"
78
+ aria-pressed={open}
79
+ aria-label={open ? "Hide raw event JSON" : "Show raw event JSON"}
80
+ >
81
+ Raw
82
+ </button>
83
+ );
84
+ }
85
+
86
+ const renderInlineValue = (value) => {
87
+ if (value === null || value === undefined) {
88
+ return <span className="italic">—</span>;
89
+ }
90
+ if (typeof value === "string") {
91
+ if (value.length > 140 || value.includes("\n")) {
92
+ return <TextBlock>{value}</TextBlock>;
93
+ }
94
+ return (
95
+ <span className="text-lg sm:text-xl font-semibold whitespace-pre-wrap break-words">
96
+ {value}
97
+ </span>
98
+ );
99
+ }
100
+ if (typeof value === "object") {
101
+ return <pre className="raw-json-block raw-json-block--full">{renderJsonWithHighlight(value)}</pre>;
102
+ }
103
+ return <span className="text-lg sm:text-xl font-semibold break-words">{String(value)}</span>;
104
+ };
105
+
106
+ function renderJsonWithHighlight(value) {
107
+ const raw = JSON.stringify(value, null, 2);
108
+ const regex =
109
+ /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*"(?:\s*:)?|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g;
110
+ const nodes = [];
111
+ let lastIndex = 0;
112
+ let match;
113
+
114
+ while ((match = regex.exec(raw)) !== null) {
115
+ const { index } = match;
116
+ if (index > lastIndex) {
117
+ nodes.push(raw.slice(lastIndex, index));
118
+ }
119
+
120
+ const token = match[0];
121
+ let className = "json-token-number";
122
+ if (token.startsWith('"')) {
123
+ className = token.endsWith(":") ? "json-token-key" : "json-token-string";
124
+ } else if (token === "true" || token === "false") {
125
+ className = "json-token-boolean";
126
+ } else if (token === "null") {
127
+ className = "json-token-null";
128
+ }
129
+
130
+ nodes.push(
131
+ <span key={`json-${index}`} className={className}>
132
+ {token}
133
+ </span>
134
+ );
135
+
136
+ lastIndex = index + token.length;
137
+ }
138
+
139
+ if (lastIndex < raw.length) {
140
+ nodes.push(raw.slice(lastIndex));
141
+ }
142
+
143
+ return nodes;
144
+ }
145
+
15
146
  export default function ContentCard({ item }) {
16
147
  if (!item) return null;
17
148
 
@@ -21,10 +152,42 @@ export default function ContentCard({ item }) {
21
152
  second: "2-digit",
22
153
  });
23
154
 
24
- const eventLabel = item.event ? item.event.replace(/_/g, " ") : "EVENT";
25
-
26
- const formatKey = (key) =>
27
- key.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
155
+ const [promptQuery, setPromptQuery] = useState("");
156
+ const [showRaw, setShowRaw] = useState(false);
157
+ const [showPromptSearch, setShowPromptSearch] = useState(false);
158
+
159
+ // Full-auto countdown logic (must be at top level for hooks rules)
160
+ const isInteractionEvent = item.event === "PROMPT_REQUESTED" || item.event === "INTERACTION_REQUESTED";
161
+ const isFullAuto = isInteractionEvent && !!item.fullAuto;
162
+ const interactionType = item.type || "text";
163
+ const interactionOptions = item.options || [];
164
+ const autoSelectDelay = item.autoSelectDelay ?? 20;
165
+ const shouldShowCountdown = isFullAuto && interactionType === "choice" && interactionOptions.length > 0;
166
+
167
+ // Calculate countdown directly - triggers re-render via tick state
168
+ const [tick, setTick] = useState(0);
169
+
170
+ const countdown = (() => {
171
+ if (!shouldShowCountdown) return null;
172
+ const eventTime = new Date(item.timestamp).getTime();
173
+ const elapsed = Math.floor((Date.now() - eventTime) / 1000);
174
+ return autoSelectDelay - elapsed;
175
+ })();
176
+
177
+ useEffect(() => {
178
+ if (!shouldShowCountdown) return;
179
+
180
+ const timer = setInterval(() => {
181
+ setTick(t => t + 1); // Force re-render to recalculate countdown
182
+ }, 1000);
183
+
184
+ return () => clearInterval(timer);
185
+ }, [shouldShowCountdown, item.timestamp]);
186
+
187
+ const formatKey = (key) => {
188
+ const spaced = key.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
189
+ return spaced.replace(/\b\w/g, (char) => char.toUpperCase());
190
+ };
28
191
 
29
192
  const tryParseJson = (raw) => {
30
193
  try {
@@ -109,11 +272,73 @@ export default function ContentCard({ item }) {
109
272
  };
110
273
  };
111
274
 
112
- const MonoBlock = ({ children }) => (
113
- <pre className="text-sm font-mono leading-relaxed overflow-auto p-6 rounded-[24px] border border-black bg-black text-white dark:border-white dark:bg-white dark:text-black">
114
- {children}
115
- </pre>
116
- );
275
+ const splitPromptSections = (promptText) => {
276
+ if (typeof promptText !== "string" || !promptText.trim()) return [];
277
+ const lines = promptText.split(/\r?\n/);
278
+ const sections = [];
279
+ let current = null;
280
+ let inFence = false;
281
+
282
+ const pushSection = () => {
283
+ if (!current) return;
284
+ const content = current.content.join("\n").replace(/\n{3,}/g, "\n\n").trim();
285
+ if (!content) {
286
+ current = null;
287
+ return;
288
+ }
289
+ sections.push({ title: current.title || "Prompt", content });
290
+ current = null;
291
+ };
292
+
293
+ lines.forEach((line) => {
294
+ const trimmed = line.trim();
295
+ if (/^```/.test(trimmed)) {
296
+ inFence = !inFence;
297
+ }
298
+ if (!trimmed) {
299
+ if (current) current.content.push("");
300
+ return;
301
+ }
302
+ if (!inFence && /^---+$/.test(trimmed)) {
303
+ pushSection();
304
+ return;
305
+ }
306
+ const headingMatch = !inFence ? /^(#{1,6})\s+(.+)$/.exec(trimmed) : null;
307
+ if (headingMatch) {
308
+ pushSection();
309
+ current = { title: headingMatch[2].trim(), content: [] };
310
+ return;
311
+ }
312
+ if (!current) current = { title: "Prompt", content: [] };
313
+ current.content.push(line);
314
+ });
315
+
316
+ pushSection();
317
+ return sections;
318
+ };
319
+
320
+ const renderPromptSectionContent = (content, query) => {
321
+ if (!content) {
322
+ return (
323
+ <PanelBody className="italic text-black/40 dark:text-white/40">—</PanelBody>
324
+ );
325
+ }
326
+ if (content.includes("```")) {
327
+ return (
328
+ <PanelBody
329
+ as="pre"
330
+ className="text-xs sm:text-sm font-mono leading-relaxed whitespace-pre-wrap break-words overflow-auto max-h-[60vh] text-black/90 dark:text-white/90"
331
+ >
332
+ {highlightText(content, query)}
333
+ </PanelBody>
334
+ );
335
+ }
336
+ return (
337
+ <PanelBody className="text-base sm:text-lg leading-relaxed whitespace-pre-wrap break-words text-black/80 dark:text-white/80">
338
+ {highlightText(content, query)}
339
+ </PanelBody>
340
+ );
341
+ };
117
342
 
118
343
  const renderValue = (value) => {
119
344
  if (value === null || value === undefined) {
@@ -122,7 +347,7 @@ export default function ContentCard({ item }) {
122
347
 
123
348
  if (typeof value === "boolean") {
124
349
  const base =
125
- "inline-flex items-center rounded-full px-3 py-1 text-xs font-semibold tracking-[0.2em] uppercase border";
350
+ "inline-flex items-center rounded-full px-3 py-1 text-[11px] font-semibold tracking-[0.16em] uppercase border";
126
351
  return value ? (
127
352
  <span className={`${base} bg-black text-white border-black dark:bg-white dark:text-black dark:border-white`}>
128
353
  Yes
@@ -137,15 +362,15 @@ export default function ContentCard({ item }) {
137
362
  if (typeof value === "string") {
138
363
  const parsedJson = extractJsonFromString(value);
139
364
  if (parsedJson !== null) {
140
- return <MonoBlock>{JSON.stringify(parsedJson, null, 2)}</MonoBlock>;
365
+ return renderStructured(parsedJson);
141
366
  }
142
367
 
143
368
  if (value.length > 140 || value.includes("\n")) {
144
- return <MonoBlock>{value}</MonoBlock>;
369
+ return <TextBlock>{value}</TextBlock>;
145
370
  }
146
371
 
147
372
  return (
148
- <span className="text-2xl font-semibold whitespace-pre-wrap break-words">
373
+ <span className="text-lg sm:text-xl font-semibold whitespace-pre-wrap break-words">
149
374
  {value}
150
375
  </span>
151
376
  );
@@ -155,7 +380,7 @@ export default function ContentCard({ item }) {
155
380
  return <MonoBlock>{JSON.stringify(value, null, 2)}</MonoBlock>;
156
381
  }
157
382
 
158
- return <span className="text-2xl font-semibold">{String(value)}</span>;
383
+ return <span className="text-lg sm:text-xl font-semibold break-words">{String(value)}</span>;
159
384
  };
160
385
 
161
386
  const MAX_STRUCT_DEPTH = 6;
@@ -181,9 +406,9 @@ export default function ContentCard({ item }) {
181
406
 
182
407
  if (isSimple) {
183
408
  return (
184
- <div className="space-y-2">
409
+ <div className="space-y-3">
185
410
  {items.map((entry, index) => (
186
- <div key={`item-${index}`} className="text-base">
411
+ <div key={`item-${index}`} className="text-base sm:text-lg">
187
412
  {renderValue(entry)}
188
413
  </div>
189
414
  ))}
@@ -197,7 +422,7 @@ export default function ContentCard({ item }) {
197
422
  {items.map((entry, index) => (
198
423
  <div
199
424
  key={`item-${index}`}
200
- className="rounded-[20px] border border-black p-4 dark:border-white"
425
+ className="rounded-[20px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-4"
201
426
  >
202
427
  {renderKeyValueGrid(Object.entries(entry), depth)}
203
428
  </div>
@@ -211,9 +436,9 @@ export default function ContentCard({ item }) {
211
436
  {items.map((entry, index) => (
212
437
  <div
213
438
  key={`item-${index}`}
214
- className="space-y-4 rounded-[24px] border border-black p-5 dark:border-white"
439
+ className="space-y-4 rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-5"
215
440
  >
216
- <div className="text-[10px] font-bold tracking-[0.35em] uppercase">
441
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
217
442
  Item {index + 1}
218
443
  </div>
219
444
  {renderStructured(entry, depth + 1)}
@@ -245,13 +470,15 @@ export default function ContentCard({ item }) {
245
470
 
246
471
  function renderKeyValueGrid(entries, depth = 0) {
247
472
  return (
248
- <div className="space-y-8">
473
+ <div className="space-y-6">
249
474
  {entries.map(([key, value]) => (
250
- <div key={key} className="space-y-2">
251
- <div className="text-[10px] font-bold tracking-[0.35em] uppercase">
475
+ <div key={key} className="grid gap-2 sm:grid-cols-[160px_1fr] sm:items-start min-w-0">
476
+ <div className="text-xs font-semibold text-black/50 dark:text-white/50 break-words">
252
477
  {formatKey(key)}
253
478
  </div>
254
- <div className="text-base">{renderStructured(value, depth + 1)}</div>
479
+ <div className="text-base sm:text-lg leading-relaxed break-words min-w-0">
480
+ {renderStructured(value, depth + 1)}
481
+ </div>
255
482
  </div>
256
483
  ))}
257
484
  </div>
@@ -264,136 +491,390 @@ export default function ContentCard({ item }) {
264
491
  const { cleanedPrompt, contextTitle, contextData, contextRaw } = extractContextFromPrompt(
265
492
  item.prompt || ""
266
493
  );
494
+ const promptSections = splitPromptSections(cleanedPrompt);
495
+ const trimmedPromptQuery = promptQuery.trim();
496
+ const normalizedPromptQuery = trimmedPromptQuery.toLowerCase();
497
+ const filteredPromptSections = trimmedPromptQuery
498
+ ? promptSections.filter((section) =>
499
+ `${section.title}\n${section.content}`.toLowerCase().includes(normalizedPromptQuery)
500
+ )
501
+ : [];
502
+ const promptCountLabel = promptSections.length ? `${promptSections.length} sections` : null;
267
503
 
268
504
  const contextTokenCount = estimateTokensFromText(contextRaw || "");
269
505
  const contextMeta = contextRaw ? `~${formatTokenCount(contextTokenCount)}` : "";
270
506
 
271
507
  content = (
272
- <div className="space-y-12 py-4">
273
- <div className="space-y-4 text-center">
508
+ <div className="space-y-10 py-6">
509
+ <div className="space-y-3 text-center">
274
510
  <AgentStartedIcon />
275
- <div className="space-y-2">
276
- <h2 className="text-4xl font-black tracking-tight">{item.agent}</h2>
277
- <div className="text-xs font-mono">{time}</div>
511
+ <div className="space-y-1">
512
+ <div className="text-[11px] font-semibold tracking-[0.28em] uppercase text-black/50 dark:text-white/50">
513
+ Agent started
514
+ </div>
515
+ <h2 className="text-3xl sm:text-4xl font-black tracking-tight">{item.agent}</h2>
278
516
  </div>
279
517
  </div>
280
518
 
281
519
  {contextTitle && contextData !== null && (
282
- <details className="group rounded-[24px] border border-black dark:border-white">
520
+ <details className="group rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04]">
283
521
  <summary className="cursor-pointer select-none px-6 py-5 flex items-center justify-between gap-6 [&::-webkit-details-marker]:hidden">
284
522
  <div className="flex items-center gap-3">
285
523
  <ChevronRight className="w-4 h-4 transition-transform group-open:rotate-90" />
286
524
  {contextTitle === "Current Context" ? (
287
525
  <Brain className="w-4 h-4" aria-hidden="true" />
288
526
  ) : null}
289
- <div className="text-sm font-semibold tracking-[0.12em] uppercase">
527
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/60 dark:text-white/60">
290
528
  {contextTitle}
291
529
  </div>
292
530
  </div>
293
- {contextMeta ? <div className="text-xs font-mono">{contextMeta}</div> : null}
531
+ {contextMeta ? <div className="text-xs font-mono text-black/50 dark:text-white/50">{contextMeta}</div> : null}
294
532
  </summary>
295
533
 
296
- <div className="border-t border-black dark:border-white p-6">
297
- {typeof contextData === "string" ? (
298
- <MonoBlock>{contextData}</MonoBlock>
299
- ) : (
300
- renderStructured(contextData)
301
- )}
302
- </div>
534
+ {typeof contextData === "string" ? (
535
+ <PanelBody className="text-base sm:text-lg leading-relaxed whitespace-pre-wrap break-words text-black/80 dark:text-white/80">
536
+ {contextData}
537
+ </PanelBody>
538
+ ) : (
539
+ <PanelBody>{renderStructured(contextData)}</PanelBody>
540
+ )}
303
541
  </details>
304
542
  )}
305
543
 
306
- <div className="markdown-body leading-relaxed text-xl font-light whitespace-pre-wrap [&_*]:text-inherit [&_a]:underline">
307
- {cleanedPrompt}
544
+ <div className="space-y-4">
545
+ <div className="flex flex-wrap items-center justify-between gap-3">
546
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
547
+ Prompt
548
+ </div>
549
+ {promptCountLabel ? (
550
+ <div className="text-xs font-mono text-black/40 dark:text-white/40">
551
+ {promptCountLabel}
552
+ </div>
553
+ ) : null}
554
+ </div>
555
+
556
+ <div className="space-y-4">
557
+ {promptSections.length > 0 ? (
558
+ promptSections.map((section, index) => {
559
+ const isInstructions = /(^| )instructions$/i.test(section.title.trim());
560
+ const shouldOpen = index === 0 || isInstructions;
561
+ const lineCount = section.content
562
+ ? section.content.split(/\r?\n/).length
563
+ : 0;
564
+ return (
565
+ <details
566
+ key={`${section.title}-${index}`}
567
+ open={shouldOpen}
568
+ className="group rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04]"
569
+ >
570
+ <summary className="cursor-pointer select-none px-6 py-5 flex items-center justify-between gap-6 [&::-webkit-details-marker]:hidden">
571
+ <div className="flex items-center gap-3 min-w-0">
572
+ <ChevronRight className="w-4 h-4 transition-transform group-open:rotate-90" />
573
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/60 dark:text-white/60 break-words">
574
+ {section.title || "Prompt"}
575
+ </div>
576
+ </div>
577
+ {lineCount ? (
578
+ <div className="text-xs font-mono text-black/40 dark:text-white/40">
579
+ {lineCount} lines
580
+ </div>
581
+ ) : null}
582
+ </summary>
583
+ {renderPromptSectionContent(section.content, "")}
584
+ </details>
585
+ );
586
+ })
587
+ ) : (
588
+ <TextBlock>{cleanedPrompt}</TextBlock>
589
+ )}
590
+ </div>
308
591
  </div>
592
+
593
+ {showPromptSearch ? (
594
+ <div className="fixed inset-0 z-40 bg-white text-black dark:bg-black dark:text-white">
595
+ <div className="h-full w-full overflow-y-auto custom-scroll px-6 sm:px-10 py-10">
596
+ <div className="max-w-3xl mx-auto space-y-8">
597
+ <div className="flex flex-wrap items-center justify-between gap-4">
598
+ <div className="text-[11px] font-semibold tracking-[0.32em] uppercase text-black/50 dark:text-white/50">
599
+ Prompt search
600
+ </div>
601
+ <button
602
+ type="button"
603
+ onClick={() => setShowPromptSearch(false)}
604
+ className="text-[10px] font-bold tracking-[0.2em] uppercase text-black/60 hover:text-black dark:text-white/60 dark:hover:text-white"
605
+ >
606
+ Close
607
+ </button>
608
+ </div>
609
+
610
+ <div className="relative">
611
+ <Search className="w-4 h-4 text-black/50 dark:text-white/60 absolute left-4 top-1/2 -translate-y-1/2" />
612
+ <input
613
+ value={promptQuery}
614
+ onChange={(event) => setPromptQuery(event.target.value)}
615
+ placeholder="Search the prompt..."
616
+ className="w-full rounded-full border border-black/20 dark:border-white/20 bg-transparent py-4 pl-11 pr-10 text-base text-black dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40 focus:outline-none focus:ring-1 focus:ring-black/30 dark:focus:ring-white/40"
617
+ autoFocus
618
+ />
619
+ {trimmedPromptQuery ? (
620
+ <button
621
+ type="button"
622
+ onClick={() => setPromptQuery("")}
623
+ aria-label="Clear prompt search"
624
+ className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full p-1 text-black/60 hover:text-black dark:text-white/60 dark:hover:text-white"
625
+ >
626
+ <X className="w-3.5 h-3.5" />
627
+ </button>
628
+ ) : null}
629
+ </div>
630
+
631
+ {trimmedPromptQuery ? (
632
+ filteredPromptSections.length > 0 ? (
633
+ <div className="space-y-4">
634
+ {filteredPromptSections.map((section, index) => {
635
+ const lineCount = section.content
636
+ ? section.content.split(/\r?\n/).length
637
+ : 0;
638
+ return (
639
+ <div
640
+ key={`${section.title}-${index}`}
641
+ className="rounded-[22px] border border-black/10 dark:border-white/15 bg-black/[0.02] dark:bg-white/5 overflow-hidden"
642
+ >
643
+ <div className="px-5 py-4 flex items-center justify-between gap-4">
644
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/70 dark:text-white/70 break-words">
645
+ {highlightText(section.title || "Prompt", trimmedPromptQuery)}
646
+ </div>
647
+ {lineCount ? (
648
+ <div className="text-xs font-mono text-black/50 dark:text-white/50">
649
+ {lineCount} lines
650
+ </div>
651
+ ) : null}
652
+ </div>
653
+ <div className="border-t border-black/10 dark:border-white/10 p-5">
654
+ <pre className="m-0 text-xs sm:text-sm font-mono leading-relaxed whitespace-pre-wrap break-words text-black/85 dark:text-white/85">
655
+ {highlightText(section.content || "", trimmedPromptQuery)}
656
+ </pre>
657
+ </div>
658
+ </div>
659
+ );
660
+ })}
661
+ </div>
662
+ ) : (
663
+ <div className="rounded-[20px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/5 p-5 text-sm text-black/60 dark:text-white/60">
664
+ No prompt sections match "{trimmedPromptQuery}".
665
+ </div>
666
+ )
667
+ ) : (
668
+ <div className="text-sm text-black/50 dark:text-white/50">
669
+ Start typing to filter prompt sections.
670
+ </div>
671
+ )}
672
+ </div>
673
+ </div>
674
+ </div>
675
+ ) : null}
309
676
  </div>
310
677
  );
311
678
  } else if (item.event && item.event.startsWith("WORKFLOW_")) {
312
679
  const step = item.event.replace("WORKFLOW_", "");
313
680
  content = (
314
- <div className="min-h-[50vh] flex flex-col items-center justify-center text-center space-y-8 py-20">
315
- <div className="text-[10px] font-bold tracking-[0.5em] uppercase">
681
+ <div className="min-h-[50vh] flex flex-col items-center justify-center text-center space-y-8 py-16">
682
+ <div className="text-[10px] font-semibold tracking-[0.4em] uppercase text-black/50 dark:text-white/50">
316
683
  Lifecycle status
317
684
  </div>
318
685
 
319
- <div className="w-full max-w-5xl rounded-[28px] border border-black p-10 dark:border-white">
320
- <h2 className="text-6xl sm:text-7xl md:text-8xl font-black tracking-tighter leading-none uppercase">
686
+ <div className="w-full max-w-5xl rounded-[28px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-10">
687
+ <h2 className="text-4xl sm:text-6xl md:text-7xl font-black tracking-tighter leading-none uppercase">
321
688
  {step}
322
689
  </h2>
323
690
 
324
691
  {item.error && (
325
- <pre className="mt-8 text-xs font-mono whitespace-pre-wrap rounded-[24px] border border-black p-6 bg-black text-white dark:border-white dark:bg-white dark:text-black">
326
- {item.error}
327
- </pre>
692
+ <div className="mt-6">
693
+ <MonoBlock>{item.error}</MonoBlock>
694
+ </div>
328
695
  )}
329
696
  </div>
330
697
  </div>
331
698
  );
332
699
  } else if (item.event === "PROMPT_REQUESTED" || item.event === "INTERACTION_REQUESTED") {
333
- const details = [
334
- ["slug", item.slug],
335
- ["targetKey", item.targetKey],
336
- ["type", item.type],
337
- ].filter((entry) => entry[1] !== undefined);
700
+ const isInteraction = item.event === "INTERACTION_REQUESTED";
701
+ const label = isInteraction ? "Interaction needed" : "Question asked";
702
+ const headline = item.question || item.prompt || "Prompt";
703
+ const agentName = item.agent || item.slug || "Agent";
338
704
 
339
705
  content = (
340
- <div className="space-y-10 py-4">
341
- <div className="text-xs font-mono">{time}</div>
342
-
343
- <div className="text-4xl font-bold tracking-tight text-balance leading-tight">
344
- {item.question || item.prompt || "Prompt"}
706
+ <div className="py-10 sm:py-14 space-y-8">
707
+ <div className="text-center space-y-4">
708
+ <AgentStartedIcon className="w-12 h-12" />
709
+ <div className="text-[11px] font-semibold tracking-[0.32em] uppercase text-black/50 dark:text-white/50">
710
+ {label}
711
+ </div>
712
+ <div className="text-lg sm:text-xl font-semibold text-black/70 dark:text-white/70">
713
+ {agentName} needs your input
714
+ </div>
715
+ </div>
716
+ <div className="text-3xl sm:text-4xl md:text-5xl font-black tracking-tight leading-tight text-center text-balance">
717
+ {headline}
345
718
  </div>
346
719
 
347
- {item.prompt && item.question && item.prompt !== item.question && (
348
- <div className="text-lg font-medium whitespace-pre-wrap">{item.prompt}</div>
720
+ {shouldShowCountdown && countdown !== null && (
721
+ <div className="text-center">
722
+ <div className={`inline-flex items-center gap-2 px-4 py-2 rounded-full ${
723
+ countdown > 0
724
+ ? "bg-black/5 dark:bg-white/10"
725
+ : "bg-black text-white dark:bg-white dark:text-black"
726
+ }`}>
727
+ <span className="text-xl">⚡</span>
728
+ <span className={`text-sm font-semibold ${
729
+ countdown > 0 ? "text-black/70 dark:text-white/70" : ""
730
+ }`}>
731
+ {countdown > 0
732
+ ? `Agent deciding in ${countdown}s...`
733
+ : "Auto-selected"}
734
+ </span>
735
+ </div>
736
+ </div>
349
737
  )}
350
738
 
351
- {details.length > 0 && <div className="pt-4">{renderKeyValueGrid(details)}</div>}
739
+ {interactionType === "choice" && interactionOptions.length > 0 && (
740
+ <div className="space-y-3 max-w-2xl mx-auto">
741
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50 text-center">
742
+ Options
743
+ </div>
744
+ <div className="space-y-2">
745
+ {interactionOptions.map((opt, index) => (
746
+ <div
747
+ key={opt.key || index}
748
+ className={`p-4 rounded-2xl border transition-all ${
749
+ index === 0
750
+ ? "border-black bg-black text-white dark:border-white dark:bg-white dark:text-black"
751
+ : "border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.03]"
752
+ }`}
753
+ >
754
+ <div className="flex items-center gap-3">
755
+ <div className={`font-bold ${index === 0 ? "" : "text-black dark:text-white"}`}>
756
+ {opt.label || opt.key}
757
+ {index === 0 && (
758
+ <span className={`ml-2 text-xs font-medium px-2 py-0.5 rounded-full ${
759
+ "bg-white/20 dark:bg-black/20"
760
+ }`}>
761
+ Recommended
762
+ </span>
763
+ )}
764
+ </div>
765
+ </div>
766
+ {opt.description && (
767
+ <div className={`text-sm mt-1 ${
768
+ index === 0 ? "text-white/70 dark:text-black/70" : "text-black/50 dark:text-white/50"
769
+ }`}>
770
+ {opt.description}
771
+ </div>
772
+ )}
773
+ </div>
774
+ ))}
775
+ </div>
776
+ {item.allowCustom && (
777
+ <div className="text-xs text-center text-black/40 dark:text-white/40 mt-2">
778
+ Custom response allowed
779
+ </div>
780
+ )}
781
+ </div>
782
+ )}
352
783
  </div>
353
784
  );
354
- } else if (item.event === "PROMPT_ANSWERED" || item.event === "INTERACTION_SUBMITTED") {
355
- const responseValue = item.answer !== undefined ? item.answer : item.response;
356
-
357
- const details = [
358
- ["slug", item.slug],
359
- ["targetKey", item.targetKey],
360
- ].filter((entry) => entry[1] !== undefined);
785
+ } else if (item.event === "INTERACTION_AUTO_RESOLVED" || item.event === "PROMPT_AUTO_ANSWERED") {
786
+ const autoSelected = item.autoSelected || "Unknown";
787
+ const agentName = item.agent || item.slug || "Agent";
361
788
 
362
789
  content = (
363
- <div className="space-y-10 py-4">
364
- <div className="text-xs font-mono">{time}</div>
790
+ <div className="space-y-8 py-6">
791
+ <div className="space-y-4 text-center">
792
+ <div className="mx-auto w-14 h-14 rounded-full border-2 border-black dark:border-white bg-black dark:bg-white flex items-center justify-center">
793
+ <span className="text-2xl">⚡</span>
794
+ </div>
795
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
796
+ Auto-selected
797
+ </div>
798
+ <div className="text-lg sm:text-xl font-semibold text-black/70 dark:text-white/70">
799
+ {agentName} decided automatically
800
+ </div>
801
+ </div>
802
+ <div className="rounded-[24px] border-2 border-black bg-black text-white dark:border-white dark:bg-white dark:text-black p-6 text-center max-w-md mx-auto">
803
+ <div className="text-2xl sm:text-3xl font-black tracking-tight">
804
+ {autoSelected}
805
+ </div>
806
+ </div>
807
+ </div>
808
+ );
809
+ } else if (item.event === "INTERACTION_RESOLVED") {
810
+ const resolvedCopy =
811
+ item.source === "remote" ? "Remote response received." : "Response captured.";
812
+ const sourceCopy =
813
+ item.source === "remote" ? "Received from remote" : item.source ? `Received from ${item.source}` : "Received";
814
+ const agentName = item.agent || item.slug || "Agent";
365
815
 
366
- <div className="text-4xl font-bold tracking-tight text-balance leading-tight">
367
- {renderValue(responseValue)}
816
+ content = (
817
+ <div className="space-y-8 py-6">
818
+ <div className="space-y-4 text-center">
819
+ <AgentStartedIcon className="w-12 h-12" />
820
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
821
+ Interaction resolved
822
+ </div>
823
+ <div className="text-lg sm:text-xl font-semibold text-black/70 dark:text-white/70">
824
+ {agentName} received your response
825
+ </div>
368
826
  </div>
827
+ <div className="rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6 text-center">
828
+ <div className="text-xl sm:text-2xl font-semibold leading-relaxed text-balance">
829
+ {resolvedCopy}
830
+ </div>
831
+ <div className="mt-3 text-sm text-black/60 dark:text-white/60">
832
+ {sourceCopy}
833
+ </div>
834
+ </div>
835
+ </div>
836
+ );
837
+ } else if (item.event === "PROMPT_ANSWERED" || item.event === "INTERACTION_SUBMITTED") {
838
+ const responseValue = item.answer !== undefined ? item.answer : item.response;
369
839
 
370
- {details.length > 0 && <div className="pt-4">{renderKeyValueGrid(details)}</div>}
840
+ content = (
841
+ <div className="space-y-8 py-6">
842
+ <div className="space-y-3">
843
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
844
+ Response sent
845
+ </div>
846
+ {renderInlineValue(responseValue)}
847
+ </div>
371
848
  </div>
372
849
  );
373
850
  } else if (item.event === "AGENT_COMPLETED") {
374
- const details = [
375
- ["agent", item.agent],
376
- ["attempts", item.attempts],
377
- ].filter((entry) => entry[1] !== undefined);
378
-
379
851
  content = (
380
- <div className="space-y-12 py-4">
381
- {item.output !== undefined && (
382
- <div className="space-y-8">
383
- <div className="rounded-[28px] border border-black px-6 py-5 dark:border-white">
384
- <div className="flex flex-wrap items-center justify-between gap-3">
385
- <div className="text-3xl font-black tracking-tight">
386
- Agent <i>{item.agent ? " " + item.agent : ""}</i> responded
387
- </div>
388
- <div className="text-xs font-mono">{time}</div>
389
- </div>
852
+ <div className="space-y-10 py-6">
853
+ <div className="rounded-[28px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6 sm:p-8 text-center space-y-4">
854
+ <AgentStartedIcon className="w-12 h-12" />
855
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
856
+ Agent completed
857
+ </div>
858
+ <div className="text-2xl sm:text-3xl font-black tracking-tight">
859
+ {item.agent || "Agent"}
860
+ </div>
861
+ {typeof item.attempts === "number" ? (
862
+ <div className="text-sm text-black/60 dark:text-white/60">
863
+ {item.attempts} {item.attempts === 1 ? "attempt" : "attempts"}
390
864
  </div>
865
+ ) : null}
866
+ </div>
391
867
 
392
- {renderStructured(item.output)}
868
+ {item.output !== undefined ? (
869
+ <div className="space-y-4">
870
+ <div className="text-[11px] font-semibold tracking-[0.24em] uppercase text-black/50 dark:text-white/50">
871
+ Answer
872
+ </div>
873
+ <div className="rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6">
874
+ {renderStructured(item.output)}
875
+ </div>
393
876
  </div>
394
- )}
395
-
396
- {details.length > 0 && <div className="pt-4">{renderKeyValueGrid(details)}</div>}
877
+ ) : null}
397
878
  </div>
398
879
  );
399
880
  } else {
@@ -401,26 +882,49 @@ export default function ContentCard({ item }) {
401
882
  ([key]) => key !== "event" && key !== "timestamp"
402
883
  );
403
884
 
404
- const fallbackEntries = entries.length > 0 ? entries : [["event", item.event || "Event"]];
405
-
406
885
  content = (
407
- <div className="space-y-12 py-4">
408
- <div className="text-xs font-mono">{time}</div>
409
- {renderKeyValueGrid(fallbackEntries)}
886
+ <div className="space-y-6 py-6">
887
+ <div className="rounded-[24px] border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.04] p-6">
888
+ <div className="text-lg sm:text-xl font-semibold leading-relaxed text-balance">
889
+ {item.event ? item.event.replace(/_/g, " ") : "Event"}
890
+ </div>
891
+ <div className="mt-3 text-sm text-black/60 dark:text-white/60">
892
+ {entries.length > 0 ? "Open Raw for full details." : "Open Raw for more info."}
893
+ </div>
894
+ </div>
410
895
  </div>
411
896
  );
412
897
  }
413
898
 
414
899
  return (
415
- <div className="w-full h-full flex flex-col overflow-y-auto custom-scroll px-12 bg-white text-black dark:bg-black dark:text-white">
900
+ <div className="w-full h-full flex flex-col overflow-y-auto custom-scroll px-6 sm:px-10 lg:px-12 bg-white text-black dark:bg-black dark:text-white">
416
901
  <div className="content-width flex-1">
417
- <div className="flex items-center justify-between pt-10">
418
- <div className="text-[10px] font-bold tracking-[0.4em] uppercase">
419
- {eventLabel}
902
+ <div className="flex flex-wrap items-center justify-between gap-4 pt-8 sm:pt-10 pb-6 border-b border-black/10 dark:border-white/10">
903
+ <div className="flex items-center gap-3">
904
+ {item.event === "AGENT_STARTED" ? (
905
+ <button
906
+ type="button"
907
+ onClick={() => setShowPromptSearch(true)}
908
+ className="w-12 h-12 rounded-full bg-white text-black dark:bg-black dark:text-white border border-black/10 dark:border-white/10 flex items-center justify-center shadow-2xl shadow-black/20 dark:shadow-white/10 hover:scale-[1.02] transition-transform"
909
+ aria-label="Search prompt sections"
910
+ >
911
+ <Search className="w-5 h-5" />
912
+ </button>
913
+ ) : null}
914
+ </div>
915
+ <div className="flex items-center gap-3">
916
+ <RawToggle open={showRaw} onToggle={() => setShowRaw((prev) => !prev)} />
917
+ <CopyButton text={item} />
420
918
  </div>
421
- <CopyButton text={item} />
422
919
  </div>
423
- {content}
920
+ <div className="pt-6">
921
+ <div className="text-xs font-mono text-black/50 dark:text-white/50">{time}</div>
922
+ </div>
923
+ {showRaw ? (
924
+ <pre className="raw-json-block">{renderJsonWithHighlight(item)}</pre>
925
+ ) : (
926
+ content
927
+ )}
424
928
  </div>
425
929
  </div>
426
930
  );