agent-state-machine 2.0.15 → 2.1.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.
Files changed (47) hide show
  1. package/bin/cli.js +1 -1
  2. package/lib/index.js +33 -0
  3. package/lib/remote/client.js +7 -2
  4. package/lib/runtime/agent.js +102 -67
  5. package/lib/runtime/index.js +13 -0
  6. package/lib/runtime/interaction.js +304 -0
  7. package/lib/runtime/prompt.js +39 -12
  8. package/lib/runtime/runtime.js +11 -10
  9. package/package.json +1 -1
  10. package/templates/project-builder/agents/assumptions-clarifier.md +0 -1
  11. package/templates/project-builder/agents/code-reviewer.md +0 -1
  12. package/templates/project-builder/agents/code-writer.md +0 -1
  13. package/templates/project-builder/agents/requirements-clarifier.md +0 -1
  14. package/templates/project-builder/agents/response-interpreter.md +25 -0
  15. package/templates/project-builder/agents/roadmap-generator.md +0 -1
  16. package/templates/project-builder/agents/sanity-checker.md +45 -0
  17. package/templates/project-builder/agents/sanity-runner.js +161 -0
  18. package/templates/project-builder/agents/scope-clarifier.md +0 -1
  19. package/templates/project-builder/agents/security-clarifier.md +0 -1
  20. package/templates/project-builder/agents/security-reviewer.md +0 -1
  21. package/templates/project-builder/agents/task-planner.md +0 -1
  22. package/templates/project-builder/agents/test-planner.md +0 -1
  23. package/templates/project-builder/scripts/interaction-helpers.js +33 -0
  24. package/templates/project-builder/scripts/workflow-helpers.js +2 -47
  25. package/templates/project-builder/workflow.js +214 -54
  26. package/vercel-server/api/session/[token].js +3 -3
  27. package/vercel-server/api/submit/[token].js +5 -3
  28. package/vercel-server/local-server.js +33 -6
  29. package/vercel-server/public/remote/index.html +17 -0
  30. package/vercel-server/ui/index.html +9 -1012
  31. package/vercel-server/ui/package-lock.json +2650 -0
  32. package/vercel-server/ui/package.json +25 -0
  33. package/vercel-server/ui/postcss.config.js +6 -0
  34. package/vercel-server/ui/src/App.jsx +236 -0
  35. package/vercel-server/ui/src/components/ChoiceInteraction.jsx +127 -0
  36. package/vercel-server/ui/src/components/ConfirmInteraction.jsx +51 -0
  37. package/vercel-server/ui/src/components/ContentCard.jsx +161 -0
  38. package/vercel-server/ui/src/components/CopyButton.jsx +27 -0
  39. package/vercel-server/ui/src/components/EventsLog.jsx +82 -0
  40. package/vercel-server/ui/src/components/Footer.jsx +66 -0
  41. package/vercel-server/ui/src/components/Header.jsx +38 -0
  42. package/vercel-server/ui/src/components/InteractionForm.jsx +42 -0
  43. package/vercel-server/ui/src/components/TextInteraction.jsx +72 -0
  44. package/vercel-server/ui/src/index.css +145 -0
  45. package/vercel-server/ui/src/main.jsx +8 -0
  46. package/vercel-server/ui/tailwind.config.js +19 -0
  47. package/vercel-server/ui/vite.config.js +11 -0
@@ -0,0 +1,82 @@
1
+ import { Terminal, Send, Play, AlertCircle, CheckCircle2 } from "lucide-react";
2
+
3
+ const getEventIcon = (event) => {
4
+ if (!event) return <Terminal className="w-3.5 h-3.5 opacity-40" />;
5
+ const e = event.toUpperCase();
6
+ if (e.includes("STARTED") || e.includes("START")) return <Play className="w-3.5 h-3.5 text-blue-500" />;
7
+ if (e.includes("SUBMITTED") || e.includes("ANSWERED") || e.includes("SUCCESS")) return <CheckCircle2 className="w-3.5 h-3.5 text-green-500" />;
8
+ if (e.includes("REQUESTED") || e.includes("REQUEST")) return <Send className="w-3.5 h-3.5 text-accent" />;
9
+ if (e.includes("ERROR") || e.includes("FAILED")) return <AlertCircle className="w-3.5 h-3.5 text-red-500" />;
10
+ return <Terminal className="w-3.5 h-3.5 opacity-40" />;
11
+ };
12
+
13
+ const getEventSummary = (item) => {
14
+ const mainText = item.question || item.answer || item.prompt || item.error || item.status || item.message;
15
+ const subText = item.slug || (item.event && item.event.startsWith("WORKFLOW_") ? item.event.replace("WORKFLOW_", "") : null);
16
+
17
+ if (!mainText && !subText) return item.event || "Unknown Event";
18
+
19
+ return (
20
+ <div className="flex flex-col gap-0.5">
21
+ {subText && <span className="text-accent font-medium text-[10px] tracking-wider uppercase opacity-80">{subText}</span>}
22
+ {mainText && (
23
+ <span className="opacity-80 truncate max-w-md">
24
+ {typeof mainText === "string" ? mainText : JSON.stringify(mainText)}
25
+ </span>
26
+ )}
27
+ </div>
28
+ );
29
+ };
30
+
31
+ export default function EventsLog({ history, onJump }) {
32
+ return (
33
+ <div className="w-full h-full flex flex-col overflow-hidden bg-bg">
34
+ <div className="flex-1 overflow-y-auto custom-scroll px-6 py-24">
35
+ <div className="max-w-4xl mx-auto space-y-1">
36
+ {history.length === 0 ? (
37
+ <div className="h-[40vh] flex flex-col items-center justify-center opacity-20 space-y-4">
38
+ <Terminal className="w-12 h-12" />
39
+ <div className="text-xs font-bold tracking-[0.4em] uppercase">No events yet</div>
40
+ </div>
41
+ ) : (
42
+ history.map((item, idx) => {
43
+ const time = new Date(item.timestamp).toLocaleTimeString([], {
44
+ hour: "2-digit",
45
+ minute: "2-digit",
46
+ second: "2-digit",
47
+ hour12: false
48
+ });
49
+
50
+ return (
51
+ <button
52
+ key={idx}
53
+ onClick={() => onJump(idx)}
54
+ className="w-full text-left group flex items-start gap-6 p-4 rounded-2xl hover:bg-black/[0.03] dark:hover:bg-white/[0.03] transition-all border border-transparent hover:border-border"
55
+ >
56
+ <div className="flex-shrink-0 w-20 pt-1">
57
+ <span className="text-[10px] font-mono font-medium opacity-20 group-hover:opacity-40 transition-opacity">
58
+ {time}
59
+ </span>
60
+ </div>
61
+
62
+ <div className="flex-shrink-0 pt-0.5">
63
+ {getEventIcon(item.event)}
64
+ </div>
65
+
66
+ <div className="flex-1 min-w-0 flex flex-col gap-1">
67
+ <div className="text-[10px] font-bold tracking-[0.2em] uppercase opacity-30 group-hover:opacity-50 transition-opacity">
68
+ {item.event?.replace(/_/g, " ") || "EVENT"}
69
+ </div>
70
+ <div className="text-sm leading-relaxed truncate group-hover:text-accent transition-colors">
71
+ {getEventSummary(item)}
72
+ </div>
73
+ </div>
74
+ </button>
75
+ );
76
+ })
77
+ )}
78
+ </div>
79
+ </div>
80
+ </div>
81
+ );
82
+ }
@@ -0,0 +1,66 @@
1
+ import { useEffect, useState } from "react";
2
+ import { ChevronLeft, ChevronRight } from "lucide-react";
3
+
4
+ export default function Footer({ page, total, onNext, onPrev, onJump, hasNew, onJumpToLatest, className = "" }) {
5
+ const [inputValue, setInputValue] = useState(page + 1);
6
+ useEffect(() => setInputValue(page + 1), [page]);
7
+
8
+ const handleInputChange = (event) => {
9
+ const { value } = event.target;
10
+ setInputValue(value);
11
+ const num = parseInt(value, 10);
12
+ if (!Number.isNaN(num) && num >= 1 && num <= total) {
13
+ onJump(num - 1);
14
+ }
15
+ };
16
+
17
+ return (
18
+ <footer className={`nav-footer transition-opacity duration-300 ${className}`}>
19
+ <div className="footer-control">
20
+ <button
21
+ onClick={onPrev}
22
+ disabled={page === 0}
23
+ className="tooltip p-1 hover:text-accent disabled:opacity-0 transition-all pointer-events-auto"
24
+ data-tooltip="Previous"
25
+ aria-label="Previous event"
26
+ >
27
+ <ChevronLeft className="w-5 h-5" />
28
+ </button>
29
+
30
+ <div className="flex items-center gap-2 text-xs font-mono font-bold tracking-widest opacity-60">
31
+ <input
32
+ type="number"
33
+ value={inputValue}
34
+ onChange={handleInputChange}
35
+ className="jumper-input"
36
+ min="1"
37
+ max={total}
38
+ />
39
+ <span className="opacity-20">/</span>
40
+ <span>{total}</span>
41
+ </div>
42
+
43
+ <button
44
+ onClick={onNext}
45
+ disabled={page === total - 1}
46
+ className="tooltip p-1 hover:text-accent disabled:opacity-0 transition-all pointer-events-auto"
47
+ data-tooltip="Next"
48
+ aria-label="Next event"
49
+ >
50
+ <ChevronRight className="w-5 h-5" />
51
+ </button>
52
+
53
+ {hasNew ? (
54
+ <button
55
+ onClick={onJumpToLatest}
56
+ className="tooltip px-3 py-1 rounded-full text-[10px] font-bold tracking-[0.2em] uppercase bg-accent text-white shadow-[0_10px_30px_rgba(0,122,255,0.25)] hover:scale-[1.02] transition-transform"
57
+ data-tooltip="Latest"
58
+ aria-label="Jump to latest event"
59
+ >
60
+ New
61
+ </button>
62
+ ) : null}
63
+ </div>
64
+ </footer>
65
+ );
66
+ }
@@ -0,0 +1,38 @@
1
+ import { Moon, Sun, LayoutList, Presentation } from "lucide-react";
2
+ import CopyButton from "./CopyButton.jsx";
3
+
4
+ export default function Header({ workflowName, status, theme, toggleTheme, viewMode, setViewMode, history }) {
5
+ return (
6
+ <header className="fixed top-0 inset-x-0 h-20 px-12 flex items-center justify-between z-50 bg-bg/80 backdrop-blur-3xl">
7
+ <div className="flex items-center gap-4">
8
+ <div className={`w-2 h-2 rounded-full ${status === "connected" ? "bg-green-500 shadow-[0_0_15px_rgba(34,197,94,0.6)]" : "bg-red-500"}`}></div>
9
+ <span className="font-bold text-[10px] tracking-[0.4em] uppercase opacity-30 truncate max-w-[300px]">{workflowName || "Workflow"}</span>
10
+ </div>
11
+
12
+ <div className="flex items-center gap-2">
13
+ <CopyButton text={history || []} label="Copy full history" disabled={!history || history.length === 0} />
14
+ <button
15
+ onClick={() => setViewMode(viewMode === "present" ? "log" : "present")}
16
+ className="tooltip w-10 h-10 flex items-center justify-center rounded-full hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
17
+ data-tooltip={viewMode === "present" ? "Log view" : "Present view"}
18
+ aria-label={viewMode === "present" ? "Switch to Log view" : "Switch to Presentation view"}
19
+ >
20
+ {viewMode === "present" ? (
21
+ <LayoutList className="w-5 h-5 opacity-40 hover:opacity-100" />
22
+ ) : (
23
+ <Presentation className="w-5 h-5 opacity-40 hover:opacity-100" />
24
+ )}
25
+ </button>
26
+
27
+ <button
28
+ onClick={toggleTheme}
29
+ className="tooltip w-10 h-10 flex items-center justify-center rounded-full hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
30
+ data-tooltip={theme === "dark" ? "Light theme" : "Dark theme"}
31
+ aria-label={theme === "dark" ? "Switch to Light theme" : "Switch to Dark theme"}
32
+ >
33
+ {theme === "dark" ? <Sun className="w-5 h-5 opacity-40 hover:opacity-100" /> : <Moon className="w-5 h-5 opacity-40 hover:opacity-100" />}
34
+ </button>
35
+ </div>
36
+ </header>
37
+ );
38
+ }
@@ -0,0 +1,42 @@
1
+ import ChoiceInteraction from "./ChoiceInteraction.jsx";
2
+ import ConfirmInteraction from "./ConfirmInteraction.jsx";
3
+ import TextInteraction from "./TextInteraction.jsx";
4
+
5
+ export default function InteractionForm({ interaction, onSubmit, disabled }) {
6
+ const type = interaction?.type || "text";
7
+
8
+ const handleResponse = (response) => {
9
+ onSubmit(interaction.slug, interaction.targetKey, response);
10
+ };
11
+
12
+ switch (type) {
13
+ case "choice":
14
+ return (
15
+ <ChoiceInteraction
16
+ key={interaction.slug}
17
+ interaction={interaction}
18
+ onSubmit={handleResponse}
19
+ disabled={disabled}
20
+ />
21
+ );
22
+ case "confirm":
23
+ return (
24
+ <ConfirmInteraction
25
+ key={interaction.slug}
26
+ interaction={interaction}
27
+ onSubmit={handleResponse}
28
+ disabled={disabled}
29
+ />
30
+ );
31
+ case "text":
32
+ default:
33
+ return (
34
+ <TextInteraction
35
+ key={interaction.slug}
36
+ interaction={interaction}
37
+ onSubmit={handleResponse}
38
+ disabled={disabled}
39
+ />
40
+ );
41
+ }
42
+ }
@@ -0,0 +1,72 @@
1
+ import { useMemo, useState } from "react";
2
+ import { Bot } from "lucide-react";
3
+
4
+ export default function TextInteraction({ interaction, onSubmit, disabled }) {
5
+ const { prompt, question, placeholder, validation } = interaction;
6
+ const [text, setText] = useState("");
7
+ const [error, setError] = useState(null);
8
+
9
+ const rules = useMemo(() => validation || {}, [validation]);
10
+
11
+ const validate = (value) => {
12
+ if (rules.minLength && value.length < rules.minLength) {
13
+ return `Minimum ${rules.minLength} characters required`;
14
+ }
15
+ if (rules.maxLength && value.length > rules.maxLength) {
16
+ return `Maximum ${rules.maxLength} characters allowed`;
17
+ }
18
+ if (rules.pattern && !new RegExp(rules.pattern).test(value)) {
19
+ return "Invalid format";
20
+ }
21
+ return null;
22
+ };
23
+
24
+ const handleSubmit = () => {
25
+ const trimmed = text.trim();
26
+ const err = validate(trimmed);
27
+ if (err) {
28
+ setError(err);
29
+ return;
30
+ }
31
+ onSubmit({ text: trimmed, raw: trimmed });
32
+ };
33
+
34
+ return (
35
+ <div className="w-full h-full flex flex-col items-stretch overflow-hidden">
36
+ <div className="flex-1 overflow-y-auto custom-scroll px-6 py-12 space-y-8 flex flex-col items-center">
37
+ <div className="space-y-4 shrink-0">
38
+ <div className="w-16 h-16 rounded-3xl bg-accent text-white flex items-center justify-center mx-auto shadow-2xl shadow-accent/40">
39
+ <Bot className="w-8 h-8" />
40
+ </div>
41
+ <h3 className="text-4xl font-extrabold tracking-tight text-fg pt-4 text-center">Action required.</h3>
42
+ </div>
43
+
44
+ <div className="w-full max-w-2xl space-y-4">
45
+ <div className="text-xl font-medium text-fg/70 text-center whitespace-pre-wrap">
46
+ {prompt || question || "Provide your response."}
47
+ </div>
48
+ <textarea
49
+ value={text}
50
+ onChange={(event) => { setText(event.target.value); setError(null); }}
51
+ disabled={disabled}
52
+ placeholder={placeholder || "Your response..."}
53
+ className={`w-full h-64 p-8 rounded-[32px] bg-black/[0.03] dark:bg-white/[0.03] border-2 ${
54
+ error ? "border-red-500" : "border-transparent"
55
+ } focus:ring-4 focus:ring-accent/10 focus:outline-none text-2xl font-medium transition-all text-center placeholder:opacity-20`}
56
+ />
57
+ {error && <div className="text-red-500 text-center text-sm">{error}</div>}
58
+ </div>
59
+ </div>
60
+
61
+ <div className="p-4 flex justify-center bg-gradient-to-t from-bg via-bg to-transparent shrink-0 border-t border-white/5">
62
+ <button
63
+ onClick={handleSubmit}
64
+ disabled={disabled || !text.trim()}
65
+ className="px-12 py-6 bg-fg text-bg rounded-full font-bold text-xl hover:scale-105 active:scale-95 transition-all disabled:opacity-30 shadow-2xl"
66
+ >
67
+ Submit Response
68
+ </button>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,145 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --bg: #ffffff;
7
+ --fg: #1d1d1f;
8
+ --subtle: #86868b;
9
+ --border: rgba(0, 0, 0, 0.06);
10
+ --accent: #007aff;
11
+ }
12
+
13
+ .dark {
14
+ --bg: #000000;
15
+ --fg: #f5f5f7;
16
+ --subtle: #86868b;
17
+ --border: rgba(255, 255, 255, 0.1);
18
+ --accent: #0a84ff;
19
+ }
20
+
21
+ body {
22
+ background-color: var(--bg);
23
+ color: var(--fg);
24
+ font-family: "Inter", sans-serif;
25
+ transition: background-color 0.4s ease, color 0.4s ease;
26
+ overflow: hidden;
27
+ margin: 0;
28
+ height: 100vh;
29
+ height: 100dvh;
30
+ width: 100vw;
31
+ }
32
+
33
+ .custom-scroll::-webkit-scrollbar {
34
+ width: 4px;
35
+ }
36
+
37
+ .custom-scroll::-webkit-scrollbar-track {
38
+ background: transparent;
39
+ }
40
+
41
+ .custom-scroll::-webkit-scrollbar-thumb {
42
+ background: var(--subtle);
43
+ border-radius: 4px;
44
+ opacity: 0.2;
45
+ }
46
+
47
+ .markdown-body pre {
48
+ background: rgba(127, 127, 127, 0.05);
49
+ padding: 1.5rem;
50
+ border-radius: 12px;
51
+ overflow-x: auto;
52
+ font-size: 0.85rem;
53
+ line-height: 1.6;
54
+ }
55
+
56
+ .content-width {
57
+ width: 100%;
58
+ max-width: 900px;
59
+ margin: 0 auto;
60
+ }
61
+
62
+ .main-container {
63
+ height: 100vh;
64
+ width: 100vw;
65
+ display: flex;
66
+ flex-direction: column;
67
+ }
68
+
69
+ .main-stage {
70
+ flex: 1;
71
+ position: relative;
72
+ z-index: 10;
73
+ padding-top: calc(80px + env(safe-area-inset-top));
74
+ }
75
+
76
+ .jumper-input {
77
+ background: transparent;
78
+ border: none;
79
+ color: inherit;
80
+ font-family: inherit;
81
+ font-weight: 700;
82
+ text-align: center;
83
+ width: 2.2rem;
84
+ outline: none;
85
+ padding: 0;
86
+ margin: 0;
87
+ font-size: inherit;
88
+ }
89
+
90
+ .jumper-input::-webkit-inner-spin-button,
91
+ .jumper-input::-webkit-outer-spin-button {
92
+ -webkit-appearance: none;
93
+ margin: 0;
94
+ }
95
+
96
+ .nav-footer {
97
+ height: 80px;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ z-index: 50;
102
+ }
103
+
104
+ .footer-control {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 1.5rem;
108
+ padding: 0.5rem 1.2rem;
109
+ background: rgba(127, 127, 127, 0.05);
110
+ backdrop-filter: blur(20px);
111
+ border-radius: 99px;
112
+ border: 1px solid var(--border);
113
+ }
114
+
115
+ .tooltip {
116
+ position: relative;
117
+ }
118
+
119
+ .tooltip::after {
120
+ content: attr(data-tooltip);
121
+ position: absolute;
122
+ bottom: -2.2rem;
123
+ left: 50%;
124
+ transform: translateX(-50%) translateY(4px);
125
+ background: var(--fg);
126
+ color: var(--bg);
127
+ padding: 0.35rem 0.55rem;
128
+ border-radius: 999px;
129
+ font-size: 10px;
130
+ font-weight: 700;
131
+ letter-spacing: 0.18em;
132
+ text-transform: uppercase;
133
+ white-space: nowrap;
134
+ opacity: 0;
135
+ pointer-events: none;
136
+ transition: opacity 0.12s ease, transform 0.12s ease;
137
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);
138
+ z-index: 40;
139
+ }
140
+
141
+ .tooltip:hover::after,
142
+ .tooltip:focus-visible::after {
143
+ opacity: 1;
144
+ transform: translateX(-50%) translateY(0);
145
+ }
@@ -0,0 +1,8 @@
1
+ import { createRoot } from "react-dom/client";
2
+ import App from "./App.jsx";
3
+ import "./index.css";
4
+ import "@fontsource-variable/inter/index.css";
5
+ import "@fontsource-variable/jetbrains-mono/index.css";
6
+
7
+ const root = createRoot(document.getElementById("root"));
8
+ root.render(<App />);
@@ -0,0 +1,19 @@
1
+ export default {
2
+ darkMode: "class",
3
+ content: ["./index.html", "./src/**/*.{js,jsx,ts,tsx}"],
4
+ theme: {
5
+ extend: {
6
+ fontFamily: {
7
+ sans: ["Inter", "system-ui", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "sans-serif"],
8
+ mono: ["JetBrains Mono", "ui-monospace", "SFMono-Regular", "Menlo", "Monaco", "Consolas", "monospace"]
9
+ },
10
+ colors: {
11
+ bg: "var(--bg)",
12
+ fg: "var(--fg)",
13
+ subtle: "var(--subtle)",
14
+ border: "var(--border)",
15
+ accent: "var(--accent)"
16
+ }
17
+ }
18
+ }
19
+ };
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ base: "/remote/",
7
+ build: {
8
+ outDir: "../public/remote",
9
+ emptyOutDir: true
10
+ }
11
+ });