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 +9 -9
- package/bin/cli.js +7 -9
- package/lib/setup.js +10 -10
- package/lib/ui/index.html +43 -13
- package/lib/ui/server.js +35 -10
- package/package.json +1 -1
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
|
-
|
|
48
|
-
state-machine
|
|
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
|
|
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
|
-
###
|
|
134
|
+
### Resuming workflows
|
|
135
135
|
|
|
136
|
-
`
|
|
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
|
|
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
|
|
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
|
|
33
|
-
state-machine
|
|
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
|
|
37
|
-
state-machine reset <workflow-name>
|
|
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 '
|
|
225
|
+
case 'follow':
|
|
228
226
|
if (!workflowName) {
|
|
229
227
|
console.error('Error: Workflow name required');
|
|
230
|
-
console.error('Usage: state-machine
|
|
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
|
|
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
|
|
45
|
-
<svg xmlns="http://www.w3.org/2000/svg" className="h-
|
|
46
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="
|
|
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
|
|
51
|
-
<svg xmlns="http://www.w3.org/2000/svg" className="h-
|
|
52
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="
|
|
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="
|
|
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="
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
}
|