agent-state-machine 1.2.0 → 1.3.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 CHANGED
@@ -120,11 +120,16 @@ export default async function() {
120
120
  // await agent('yoda-greeter', userInfo);
121
121
 
122
122
  // Example: Parallel execution
123
- // const [a, b] = await parallel([
124
- // agent('example', { which: 'a' }),
125
- // agent('example', { which: 'b' })
123
+ // const [a, b, c] = await parallel([
124
+ // agent('yoda-greeter', { name: 'the names augustus but friends call me gus' }),
125
+ // agent('yoda-greeter', { name: 'uriah' }),
126
+ // agent('yoda-greeter', { name: 'lucas' })
126
127
  // ]);
127
128
 
129
+ // console.log('a: ' + JSON.stringify(a))
130
+ // console.log('b: ' + JSON.stringify(b))
131
+ // console.log('c: ' + JSON.stringify(c))
132
+
128
133
  notify(['project-builder', userInfo.name || userInfo + ' has been greeted!']);
129
134
 
130
135
  console.log('Workflow completed!');
package/lib/llm.js CHANGED
@@ -335,8 +335,6 @@ export async function llm(context, options) {
335
335
  const models = config.models || {};
336
336
  const apiKeys = config.apiKeys || {};
337
337
 
338
- // No longer needed to write to prompts/ directory here
339
-
340
338
  // Look up the model command/config
341
339
  const modelConfig = models[options.model];
342
340
 
@@ -356,7 +356,8 @@ Enter your response below:
356
356
  runtime.prependHistory({
357
357
  event: 'INTERACTION_REQUESTED',
358
358
  slug,
359
- targetKey
359
+ targetKey,
360
+ question: content
360
361
  });
361
362
 
362
363
  // Block and wait for user input (instead of throwing)
@@ -71,7 +71,8 @@ Enter your response below:
71
71
  event: 'INTERACTION_REQUESTED',
72
72
  slug,
73
73
  targetKey: memoryKey,
74
- file: interactionFile
74
+ file: interactionFile,
75
+ question
75
76
  });
76
77
 
77
78
  // Block and wait for user input (instead of throwing)
package/lib/setup.js CHANGED
@@ -99,11 +99,16 @@ export default async function() {
99
99
  // await agent('yoda-greeter', userInfo);
100
100
 
101
101
  // Example: Parallel execution
102
- // const [a, b] = await parallel([
103
- // agent('example', { which: 'a' }),
104
- // agent('example', { which: 'b' })
102
+ // const [a, b, c] = await parallel([
103
+ // agent('yoda-greeter', { name: 'the names augustus but friends call me gus' }),
104
+ // agent('yoda-greeter', { name: 'uriah' }),
105
+ // agent('yoda-greeter', { name: 'lucas' })
105
106
  // ]);
106
107
 
108
+ // console.log('a: ' + JSON.stringify(a))
109
+ // console.log('b: ' + JSON.stringify(b))
110
+ // console.log('c: ' + JSON.stringify(c))
111
+
107
112
  notify(['${workflowName}', userInfo.name || userInfo + ' has been greeted!']);
108
113
 
109
114
  console.log('Workflow completed!');
@@ -288,7 +293,7 @@ ${workflowName}/
288
293
  ├── package.json # Sets "type": "module" for this workflow folder
289
294
  ├── agents/ # Custom agents (.js/.mjs/.cjs or .md)
290
295
  ├── interactions/ # Human-in-the-loop inputs (created at runtime)
291
- ├── state/ # Runtime state (current.json, history.jsonl, prompts/)
296
+ ├── state/ # Runtime state (current.json, history.jsonl)
292
297
  └── steering/ # Steering configuration
293
298
  \\\`\\\`\\\`
294
299
 
@@ -354,11 +359,16 @@ export default async function() {
354
359
  // await agent('yoda-greeter', userInfo);
355
360
 
356
361
  // Example: Parallel execution
357
- // const [a, b] = await parallel([
358
- // agent('example', { which: 'a' }),
359
- // agent('example', { which: 'b' })
362
+ // const [a, b, c] = await parallel([
363
+ // agent('yoda-greeter', { name: 'the names augustus but friends call me gus' }),
364
+ // agent('yoda-greeter', { name: 'uriah' }),
365
+ // agent('yoda-greeter', { name: 'lucas' })
360
366
  // ]);
361
367
 
368
+ // console.log('a: ' + JSON.stringify(a))
369
+ // console.log('b: ' + JSON.stringify(b))
370
+ // console.log('c: ' + JSON.stringify(c))
371
+
362
372
  notify(['project-builder', userInfo.name || userInfo + ' has been greeted!']);
363
373
 
364
374
  console.log('Workflow completed!');
package/lib/ui/index.html CHANGED
@@ -1,5 +1,6 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
+
3
4
  <head>
4
5
  <meta charset="UTF-8">
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -14,14 +15,32 @@
14
15
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
15
16
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
16
17
  <style>
17
- .markdown-body { white-space: pre-wrap; font-family: monospace; }
18
+ .markdown-body {
19
+ white-space: pre-wrap;
20
+ font-family: monospace;
21
+ }
22
+
18
23
  /* Scrollbar styles for dark mode */
19
- .dark ::-webkit-scrollbar { width: 10px; height: 10px; }
20
- .dark ::-webkit-scrollbar-track { background: #000000; }
21
- .dark ::-webkit-scrollbar-thumb { background: #27272a; border-radius: 5px; }
22
- .dark ::-webkit-scrollbar-thumb:hover { background: #3f3f46; }
24
+ .dark ::-webkit-scrollbar {
25
+ width: 10px;
26
+ height: 10px;
27
+ }
28
+
29
+ .dark ::-webkit-scrollbar-track {
30
+ background: #000000;
31
+ }
32
+
33
+ .dark ::-webkit-scrollbar-thumb {
34
+ background: #27272a;
35
+ border-radius: 5px;
36
+ }
37
+
38
+ .dark ::-webkit-scrollbar-thumb:hover {
39
+ background: #3f3f46;
40
+ }
23
41
  </style>
24
42
  </head>
43
+
25
44
  <body>
26
45
  <div id="root"></div>
27
46
 
@@ -64,8 +83,8 @@
64
83
  };
65
84
 
66
85
  return (
67
- <button
68
- onClick={handleCopy}
86
+ <button
87
+ onClick={handleCopy}
69
88
  className={`flex items-center space-x-1 text-[9px] uppercase tracking-wider transition-colors hover:text-blue-500 focus:outline-none ${className}`}
70
89
  title="Copy to clipboard"
71
90
  >
@@ -75,6 +94,105 @@
75
94
  );
76
95
  }
77
96
 
97
+ function JsonView({ data, label, onTop = false, timestamp }) {
98
+ const [viewMode, setViewMode] = useState('clean'); // 'clean' | 'raw'
99
+
100
+ const isObject = typeof data === 'object' && data !== null;
101
+ const rawContent = isObject ? JSON.stringify(data, null, 2) : String(data);
102
+ const lineBreak = '';
103
+
104
+ // UPDATED: "clean" view loops through ALL top-level keys.
105
+ // Headers render in CAPS, BOLD, and BLUE.
106
+ let cleanParts = null;
107
+ if (isObject) {
108
+ const keys = Object.keys(data);
109
+ if (keys.length > 0) {
110
+ cleanParts = keys.map((k) => {
111
+ const val = data[k];
112
+ const renderedVal = typeof val === 'string' ? val : JSON.stringify(val, null, 2);
113
+ const prettyVal = String(renderedVal).replace(/\\n/g, lineBreak);
114
+
115
+ return (
116
+ <div key={k} className="mb-5 last:mb-0">
117
+ <div className="text-[11px] font-extrabold uppercase tracking-wider text-blue-600 dark:text-blue-400 mb-2">
118
+ {k}
119
+ </div>
120
+ <div className="whitespace-pre-wrap">
121
+ {prettyVal}
122
+ </div>
123
+ </div>
124
+ );
125
+ });
126
+ }
127
+ }
128
+
129
+ // For RAW view we still render as a string
130
+ const rawContentUnescaped = rawContent; // keep raw exact
131
+
132
+ const hasToggle = isObject || rawContent.includes('\\n');
133
+
134
+ if (onTop) {
135
+ return (
136
+ <div className="flex justify-end w-full group">
137
+ <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">
138
+ <div className="flex justify-between items-center mb-3">
139
+ <div className="text-[9px] font-black text-blue-300 dark:text-blue-800/60 uppercase tracking-[0.2em] text-right w-full">
140
+ {label}
141
+ </div>
142
+ <div className="absolute top-4 left-4 opacity-0 group-hover:opacity-100 transition-opacity flex items-center space-x-2">
143
+ {hasToggle && (
144
+ <button
145
+ onClick={() => setViewMode(v => v === 'clean' ? 'raw' : 'clean')}
146
+ className="text-[9px] uppercase tracking-wider font-bold text-blue-400 hover:text-blue-600 dark:text-blue-600 dark:hover:text-blue-400 focus:outline-none"
147
+ >
148
+ {viewMode === 'clean' ? 'Raw' : 'Clean'}
149
+ </button>
150
+ )}
151
+ <CopyButton text={data} className="text-blue-400 hover:text-blue-600 dark:text-blue-600 dark:hover:text-blue-400" />
152
+ </div>
153
+ </div>
154
+
155
+ <div className="markdown-body text-gray-800 dark:text-zinc-200 text-sm overflow-x-auto leading-relaxed">
156
+ {viewMode === 'clean'
157
+ ? (cleanParts ?? String(rawContent).replace(/\\n/g, lineBreak))
158
+ : rawContentUnescaped
159
+ }
160
+ </div>
161
+ </div>
162
+ </div>
163
+ );
164
+ }
165
+
166
+ return (
167
+ <div className="bg-gray-100 dark:bg-zinc-900/50 border border-gray-200 dark:border-zinc-800 rounded-lg px-4 py-3 text-xs w-full max-w-2xl font-mono overflow-x-auto relative group">
168
+ <div className="text-[9px] text-gray-400 dark:text-zinc-600 uppercase tracking-widest mb-1 flex justify-between items-center">
169
+ <div className="flex space-x-4">
170
+ <span>{label}</span>
171
+ {timestamp && <span>{timestamp}</span>}
172
+ </div>
173
+ <div className="opacity-0 group-hover:opacity-100 transition-opacity flex items-center space-x-2">
174
+ {hasToggle && (
175
+ <button
176
+ onClick={() => setViewMode(v => v === 'clean' ? 'raw' : 'clean')}
177
+ className="text-[9px] uppercase tracking-wider font-bold text-zinc-400 hover:text-blue-500 focus:outline-none"
178
+ >
179
+ {viewMode === 'clean' ? 'Raw' : 'Clean'}
180
+ </button>
181
+ )}
182
+ <CopyButton text={data} className="text-gray-400 hover:text-gray-600" />
183
+ </div>
184
+ </div>
185
+
186
+ <div className="text-gray-600 dark:text-zinc-400 whitespace-pre-wrap">
187
+ {viewMode === 'clean'
188
+ ? (cleanParts ?? String(rawContent).replace(/\\n/g, lineBreak))
189
+ : rawContentUnescaped
190
+ }
191
+ </div>
192
+ </div>
193
+ );
194
+ }
195
+
78
196
  function App() {
79
197
  const [history, setHistory] = useState([]);
80
198
  const [loading, setLoading] = useState(true);
@@ -129,7 +247,7 @@
129
247
  </div>
130
248
  </div>
131
249
  );
132
-
250
+
133
251
  if (error) return (
134
252
  <div className={theme}>
135
253
  <div className="flex items-center justify-center h-screen bg-gray-50 dark:bg-black text-red-500">
@@ -138,14 +256,9 @@
138
256
  </div>
139
257
  );
140
258
 
141
- // Filter for events we want to display
142
- let visibleEvents = history.filter(item =>
143
- [
144
- 'WORKFLOW_STARTED', 'WORKFLOW_COMPLETED', 'WORKFLOW_FAILED', 'WORKFLOW_RESET',
145
- 'AGENT_STARTED', 'AGENT_COMPLETED', 'AGENT_FAILED',
146
- 'INTERACTION_REQUESTED', 'INTERACTION_RESOLVED'
147
- ].includes(item.event)
148
- );
259
+ // Filter for events we want to display - NOW INCLUDES EVERYTHING
260
+ // We only filter out nulls or malformed entries if any
261
+ let visibleEvents = history;
149
262
 
150
263
  // Apply Sort
151
264
  // History from API is "Newest First" (index 0 is latest)
@@ -159,26 +272,26 @@
159
272
  <div className={theme}>
160
273
  <div className="min-h-screen bg-gray-50 dark:bg-black transition-colors duration-200">
161
274
  <div className="max-w-5xl mx-auto min-h-screen flex flex-col">
162
-
275
+
163
276
  {/* Sticky Header */}
164
277
  <header className="sticky top-0 z-50 py-4 px-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-colors">
165
278
  <div className="flex-1">
166
279
  <h1 className="text-xl font-bold text-gray-800 dark:text-zinc-100 transition-colors uppercase tracking-tight">{workflowName}</h1>
167
- <p className="text-gray-500 dark:text-zinc-500 text-xs mt-0.5">Runtime History & Prompt Logs</p>
280
+ <p className="text-gray-500 dark:text-zinc-500 text-xs mt-0.5">Runtimes History & Prompt Logs</p>
168
281
  </div>
169
282
  <div className="flex items-center space-x-2">
170
- <button
283
+ <button
171
284
  onClick={toggleSort}
172
285
  className="p-2 rounded-full bg-gray-200 dark:bg-zinc-900 text-gray-800 dark:text-zinc-200 hover:bg-gray-300 dark:hover:bg-zinc-800 transition-colors"
173
286
  title={sortOrder === 'newest' ? "Sort: Newest First" : "Sort: Oldest First"}
174
287
  >
175
- {sortOrder === 'newest' ?
176
- <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" /></svg>
177
- :
288
+ {sortOrder === 'newest' ?
289
+ <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" /></svg>
290
+ :
178
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>
179
292
  }
180
293
  </button>
181
- <button
294
+ <button
182
295
  onClick={toggleTheme}
183
296
  className="p-2 rounded-full bg-gray-200 dark:bg-zinc-900 text-gray-800 dark:text-zinc-200 hover:bg-gray-300 dark:hover:bg-zinc-800 transition-colors"
184
297
  title="Toggle Theme"
@@ -242,19 +355,40 @@
242
355
  );
243
356
  }
244
357
 
245
- // 4. Interaction Requested
246
- if (item.event === 'INTERACTION_REQUESTED') {
358
+ // 4. Interaction / Prompt Requested
359
+ if (item.event === 'INTERACTION_REQUESTED' || item.event === 'PROMPT_REQUESTED') {
360
+ const isPrompt = item.event === 'PROMPT_REQUESTED';
247
361
  return (
248
362
  <div key={idx} className="flex justify-center">
249
363
  <div className="bg-zinc-100 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 border-dashed rounded-lg px-6 py-4 text-center max-w-md w-full">
250
- <div className="text-[10px] text-zinc-400 dark:text-zinc-600 uppercase font-bold tracking-widest mb-1">Human Intervention Needed</div>
251
- <div className="text-xs text-zinc-600 dark:text-zinc-400 italic">Waiting for response to "{item.slug}"...</div>
364
+ <div className="text-[10px] text-zinc-400 dark:text-zinc-600 uppercase font-bold tracking-widest mb-1">
365
+ {isPrompt ? 'User Input Requested' : 'Human Intervention Needed'}
366
+ </div>
367
+ <div className="text-xs text-zinc-600 dark:text-zinc-400 italic">
368
+ {item.question ? `"${item.question}"` : `Waiting for response to "${item.slug}"...`}
369
+ </div>
252
370
  </div>
253
371
  </div>
254
372
  );
255
373
  }
256
374
 
257
- // 5. Agent Completed / Interaction Resolved (The Bubbles)
375
+ // 5. Prompt Answered
376
+ if (item.event === 'PROMPT_ANSWERED') {
377
+ return (
378
+ <div key={idx} className="flex justify-center">
379
+ <div className="bg-green-50 dark:bg-green-950/20 border border-green-200 dark:border-green-900/50 rounded-lg px-4 py-2 text-center max-w-md w-full">
380
+ <div className="text-[10px] text-green-600 dark:text-green-400 uppercase font-bold tracking-widest mb-1">
381
+ User Answered
382
+ </div>
383
+ <div className="text-xs text-green-700 dark:text-green-300 italic font-medium">
384
+ "{item.answer}"
385
+ </div>
386
+ </div>
387
+ </div>
388
+ );
389
+ }
390
+
391
+ // 6. Agent Completed / Interaction Resolved (The Bubbles)
258
392
  if (item.event === 'AGENT_COMPLETED' || item.event === 'INTERACTION_RESOLVED') {
259
393
  return (
260
394
  <div key={idx} className="flex flex-col space-y-4">
@@ -269,19 +403,11 @@
269
403
 
270
404
  {/* Output (Response) - NOW ON TOP */}
271
405
  {(item.output || item.result) && (
272
- <div className="flex justify-end w-full group">
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>
280
- <div className="markdown-body text-gray-800 dark:text-zinc-200 text-sm overflow-x-auto leading-relaxed">
281
- {typeof item.output === 'object' ? JSON.stringify(item.output, null, 2) : (item.output || item.result)}
282
- </div>
283
- </div>
284
- </div>
406
+ <JsonView
407
+ data={item.output || item.result}
408
+ label="Output / Response"
409
+ onTop={true}
410
+ />
285
411
  )}
286
412
 
287
413
  {/* Prompt (Input) - NOW ON BOTTOM */}
@@ -304,7 +430,19 @@
304
430
  );
305
431
  }
306
432
 
307
- return null;
433
+ // 7. CATCH-ALL for Unknown Events
434
+ return (
435
+ <div key={idx} className="flex justify-center px-4">
436
+ <JsonView
437
+ data={JSON.parse(JSON.stringify(item, (key, value) => {
438
+ if (key === 'event' || key === 'timestamp') return undefined; // Keep it clean
439
+ return value;
440
+ }))}
441
+ label={item.event}
442
+ timestamp={formatTime(item.timestamp)}
443
+ />
444
+ </div>
445
+ );
308
446
  })}
309
447
  </div>
310
448
 
@@ -321,4 +459,5 @@
321
459
  root.render(<App />);
322
460
  </script>
323
461
  </body>
324
- </html>
462
+
463
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-state-machine",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
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",