agent-state-machine 1.1.0 → 1.2.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.
package/README.md CHANGED
@@ -44,11 +44,11 @@ Requirements: Node.js >= 16.
44
44
  ```bash
45
45
  state-machine --setup <workflow-name>
46
46
  state-machine run <workflow-name>
47
- state-machine resume <workflow-name>
48
- state-machine status <workflow-name>
47
+
48
+ state-machine follow <workflow-name> (view prompt trace history in browser with live updates)
49
49
  state-machine history <workflow-name> [limit]
50
- state-machine trace-logs <workflow-name>
51
- state-machine reset <workflow-name>
50
+ state-machine reset <workflow-name> (clears memory/state)
51
+ state-machine reset-hard <workflow-name> (clears everything: history/interactions/memory)
52
52
  ```
53
53
 
54
54
  Workflows live in:
@@ -131,13 +131,13 @@ export default async function() {
131
131
  }
132
132
  ```
133
133
 
134
- ### How “resume” works
134
+ ### Resuming workflows
135
135
 
136
- `resume` restarts your workflow from the top.
136
+ `state-machine run` restarts your workflow from the top, loading the persisted state.
137
137
 
138
138
  If the workflow needs human input, it will **block inline** in the terminal. You’ll be told which `interactions/<slug>.md` file to edit; after you fill it in, press `y` in the same terminal session to continue.
139
139
 
140
- If the process is interrupted, running `state-machine resume <workflow-name>` will restart the execution. Use the `memory` object to store and skip work manually if needed.
140
+ If the process is interrupted, running `state-machine run <workflow-name>` again will continue execution (assuming your workflow uses `memory` to skip completed steps).
141
141
 
142
142
  ---
143
143
 
@@ -293,10 +293,10 @@ export const config = {
293
293
  };
294
294
  ```
295
295
 
296
- The runtime captures the fully-built prompt in `state/history.jsonl`, viewable via:
296
+ The runtime captures the fully-built prompt in `state/history.jsonl`, viewable in the browser with live updates via:
297
297
 
298
298
  ```bash
299
- state-machine trace-logs <workflow-name>
299
+ state-machine follow <workflow-name>
300
300
  ```
301
301
 
302
302
  ---
package/bin/cli.js CHANGED
@@ -29,13 +29,12 @@ Agent State Machine CLI (Native JS Workflows Only) v${getVersion()}
29
29
 
30
30
  Usage:
31
31
  state-machine --setup <workflow-name> Create a new workflow project
32
- state-machine run <workflow-name> Run a workflow from the beginning
33
- state-machine resume <workflow-name> Resume a paused workflow
32
+ state-machine run <workflow-name> Run a workflow (loads existing state)
33
+ state-machine follow <workflow-name> View prompt trace history in browser with live updates
34
34
  state-machine status [workflow-name] Show current state (or list all)
35
- state-machine history <workflow-name> [limit] Show execution history
36
- state-machine trace-logs <workflow-name> View prompt trace history in browser
37
- state-machine reset <workflow-name> Reset workflow state
38
- state-machine reset-hard <workflow-name> Hard reset (clear history/interactions)
35
+ state-machine history <workflow-name> [limit] Show execution history logs
36
+ state-machine reset <workflow-name> Reset workflow state (clears memory/state)
37
+ state-machine reset-hard <workflow-name> Hard reset (clears everything: history/interactions/memory)
39
38
  state-machine list List all workflows
40
39
  state-machine help Show this help
41
40
 
@@ -184,7 +183,6 @@ async function main() {
184
183
 
185
184
  switch (command) {
186
185
  case 'run':
187
- case 'resume':
188
186
  if (!workflowName) {
189
187
  console.error('Error: Workflow name required');
190
188
  console.error(`Usage: state-machine ${command} <workflow-name>`);
@@ -224,10 +222,10 @@ async function main() {
224
222
  }
225
223
  break;
226
224
 
227
- case 'trace-logs':
225
+ case 'follow':
228
226
  if (!workflowName) {
229
227
  console.error('Error: Workflow name required');
230
- console.error('Usage: state-machine trace-logs <workflow-name>');
228
+ console.error('Usage: state-machine follow <workflow-name>');
231
229
  process.exit(1);
232
230
  }
233
231
  {
package/lib/setup.js CHANGED
@@ -173,7 +173,7 @@ Once you have it create a yoda-greeting.md file in root dir with the greeting.
173
173
  You are a fast, direct worker. Do NOT investigate the codebase or read files unless strictly necessary. Perform the requested action immediately using the provided context. Avoid "thinking" steps or creating plans if the task is simple.
174
174
  `;
175
175
 
176
- const yodaNameCollectorAgent = `---
176
+ const yodaNameCollectorAgent = `---
177
177
  model: low
178
178
  output: name
179
179
  ---
@@ -294,16 +294,11 @@ ${workflowName}/
294
294
 
295
295
  ## Usage
296
296
 
297
- Run the workflow:
297
+ Run the workflow (or resume if interrupted):
298
298
  \\\`\\\`\\\`bash
299
299
  state-machine run ${workflowName}
300
300
  \\\`\\\`\\\`
301
301
 
302
- Resume a paused workflow:
303
- \\\`\\\`\\\`bash
304
- state-machine resume ${workflowName}
305
- \\\`\\\`\\\`
306
-
307
302
  Check status:
308
303
  \\\`\\\`\\\`bash
309
304
  state-machine status ${workflowName}
@@ -314,16 +309,21 @@ View history:
314
309
  state-machine history ${workflowName}
315
310
  \\\`\\\`\\\`
316
311
 
317
- View trace logs in browser:
312
+ View trace logs in browser with live updates:
318
313
  \\\`\\\`\\\`bash
319
- state-machine trace-logs ${workflowName}
314
+ state-machine follow ${workflowName}
320
315
  \\\`\\\`\\\`
321
316
 
322
- Reset state:
317
+ Reset state (clears memory/state):
323
318
  \\\`\\\`\\\`bash
324
319
  state-machine reset ${workflowName}
325
320
  \\\`\\\`\\\`
326
321
 
322
+ Hard reset (clears everything: history/interactions/memory):
323
+ \\\`\\\`\\\`bash
324
+ state-machine reset-hard ${workflowName}
325
+ \\\`\\\`\\\`
326
+
327
327
  ## Writing Workflows
328
328
 
329
329
  Edit \`workflow.js\` - write normal async JavaScript:
package/lib/ui/index.html CHANGED
@@ -41,20 +41,40 @@
41
41
  </svg>
42
42
  );
43
43
 
44
- const SortDescIcon = () => (
45
- <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
46
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12" />
44
+ const CopyIcon = () => (
45
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
46
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
47
47
  </svg>
48
48
  );
49
49
 
50
- const SortAscIcon = () => (
51
- <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
52
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4h13M3 8h9m-9 4h5m4 0l4-4m0 0l4 4m-4-4v12" transform="scale(1, -1) translate(0, -24)" />
53
- {/* Simple Down Arrow for Newest Top */}
54
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
50
+ const CheckIcon = () => (
51
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
52
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
55
53
  </svg>
56
54
  );
57
55
 
56
+ function CopyButton({ text, className }) {
57
+ const [copied, setCopied] = useState(false);
58
+
59
+ const handleCopy = () => {
60
+ const content = typeof text === 'object' ? JSON.stringify(text, null, 2) : text;
61
+ navigator.clipboard.writeText(content);
62
+ setCopied(true);
63
+ setTimeout(() => setCopied(false), 2000);
64
+ };
65
+
66
+ return (
67
+ <button
68
+ onClick={handleCopy}
69
+ className={`flex items-center space-x-1 text-[9px] uppercase tracking-wider transition-colors hover:text-blue-500 focus:outline-none ${className}`}
70
+ title="Copy to clipboard"
71
+ >
72
+ {copied ? <CheckIcon /> : <CopyIcon />}
73
+ <span>{copied ? 'Copied' : 'Copy'}</span>
74
+ </button>
75
+ );
76
+ }
77
+
58
78
  function App() {
59
79
  const [history, setHistory] = useState([]);
60
80
  const [loading, setLoading] = useState(true);
@@ -250,8 +270,13 @@
250
270
  {/* Output (Response) - NOW ON TOP */}
251
271
  {(item.output || item.result) && (
252
272
  <div className="flex justify-end w-full group">
253
- <div className="max-w-[85%] bg-blue-50 dark:bg-blue-950/20 border border-blue-100 dark:border-blue-900/40 rounded-2xl rounded-tr-none shadow-sm p-6 transition-all hover:border-blue-200 dark:hover:border-blue-800">
254
- <div className="text-[9px] font-black text-blue-300 dark:text-blue-800/60 mb-3 uppercase tracking-[0.2em] text-right">Output / Response</div>
273
+ <div className="max-w-[85%] bg-blue-50 dark:bg-blue-950/20 border border-blue-100 dark:border-blue-900/40 rounded-2xl rounded-tr-none shadow-sm p-6 transition-all hover:border-blue-200 dark:hover:border-blue-800 relative">
274
+ <div className="flex justify-between items-center mb-3">
275
+ <div className="text-[9px] font-black text-blue-300 dark:text-blue-800/60 uppercase tracking-[0.2em] text-right w-full">Output / Response</div>
276
+ <div className="absolute top-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity">
277
+ <CopyButton text={item.output || item.result} className="text-blue-400 hover:text-blue-600 dark:text-blue-600 dark:hover:text-blue-400" />
278
+ </div>
279
+ </div>
255
280
  <div className="markdown-body text-gray-800 dark:text-zinc-200 text-sm overflow-x-auto leading-relaxed">
256
281
  {typeof item.output === 'object' ? JSON.stringify(item.output, null, 2) : (item.output || item.result)}
257
282
  </div>
@@ -262,8 +287,13 @@
262
287
  {/* Prompt (Input) - NOW ON BOTTOM */}
263
288
  {item.prompt && (
264
289
  <div className="flex justify-start w-full group">
265
- <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-6 transition-all hover:border-zinc-300 dark:hover:border-zinc-700">
266
- <div className="text-[9px] font-black text-zinc-300 dark:text-zinc-700 mb-3 uppercase tracking-[0.2em]">Prompt / Input</div>
290
+ <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-6 transition-all hover:border-zinc-300 dark:hover:border-zinc-700 relative">
291
+ <div className="flex justify-between items-center mb-3">
292
+ <div className="text-[9px] font-black text-zinc-300 dark:text-zinc-700 uppercase tracking-[0.2em]">Prompt / Input</div>
293
+ <div className="absolute top-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity">
294
+ <CopyButton text={item.prompt} className="text-gray-400 hover:text-gray-600 dark:text-zinc-600 dark:hover:text-zinc-400" />
295
+ </div>
296
+ </div>
267
297
  <div className="markdown-body text-gray-800 dark:text-zinc-300 text-sm overflow-x-auto leading-relaxed">
268
298
  {item.prompt}
269
299
  </div>
@@ -291,4 +321,4 @@
291
321
  root.render(<App />);
292
322
  </script>
293
323
  </body>
294
- </html>
324
+ </html>
package/lib/ui/server.js CHANGED
@@ -10,7 +10,7 @@ import { fileURLToPath } from 'url';
10
10
  const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = path.dirname(__filename);
12
12
 
13
- export function startServer(workflowDir, port = 3000) {
13
+ export function startServer(workflowDir, initialPort = 3000) {
14
14
  const clients = new Set();
15
15
  const stateDir = path.join(workflowDir, 'state');
16
16
 
@@ -45,7 +45,8 @@ export function startServer(workflowDir, port = 3000) {
45
45
  console.warn('Warning: Failed to setup file watcher:', err.message);
46
46
  }
47
47
 
48
- const server = http.createServer((req, res) => {
48
+ // Request Handler
49
+ const requestHandler = (req, res) => {
49
50
  // Serve the main HTML page
50
51
  if (req.url === '/' || req.url === '/index.html') {
51
52
  const htmlPath = path.join(__dirname, 'index.html');
@@ -115,11 +116,35 @@ export function startServer(workflowDir, port = 3000) {
115
116
  // 404
116
117
  res.writeHead(404);
117
118
  res.end('Not found');
118
- });
119
-
120
- server.listen(port, () => {
121
- console.log(`\n> Trace Logs running at http://localhost:${port}`);
122
- console.log(`> Viewing history for: ${workflowDir}`);
123
- console.log(`> Press Ctrl+C to stop`);
124
- });
125
- }
119
+ };
120
+
121
+ // Port hunting logic
122
+ let port = initialPort;
123
+ const maxPort = initialPort + 100; // Try up to 100 ports
124
+
125
+ const attemptServer = () => {
126
+ const server = http.createServer(requestHandler);
127
+
128
+ server.on('error', (e) => {
129
+ if (e.code === 'EADDRINUSE') {
130
+ if (port < maxPort) {
131
+ console.log(`Port ${port} is in use, trying ${port + 1}...`);
132
+ port++;
133
+ attemptServer();
134
+ } else {
135
+ console.error(`Error: Could not find an open port between ${initialPort} and ${maxPort}.`);
136
+ }
137
+ } else {
138
+ console.error('Server error:', e);
139
+ }
140
+ });
141
+
142
+ server.listen(port, () => {
143
+ console.log(`\n> Follow UI running at http://localhost:${port}`);
144
+ console.log(`> Viewing history for: ${workflowDir}`);
145
+ console.log(`> Press Ctrl+C to stop`);
146
+ });
147
+ };
148
+
149
+ attemptServer();
150
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-state-machine",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "A workflow orchestrator for running agents and scripts in sequence with state management",
6
6
  "main": "lib/index.js",