agent-state-machine 2.0.7 → 2.0.10

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
@@ -4,7 +4,7 @@ A workflow runner for building **linear, stateful agent workflows** in plain Jav
4
4
 
5
5
  You write normal `async/await` code. The runtime handles:
6
6
  - **Auto-persisted** `memory` (saved to disk on mutation)
7
- - **Human-in-the-loop** blocking via `initialPrompt()` or agent-driven interactions
7
+ - **Human-in-the-loop** blocking via `askHuman()` or agent-driven interactions
8
8
  - Local **JS agents** + **Markdown agents** (LLM-powered)
9
9
 
10
10
  ---
@@ -80,7 +80,7 @@ workflows/<name>/
80
80
  * - Interactive prompts pause and wait for user input
81
81
  */
82
82
 
83
- import { agent, memory, initialPrompt, parallel } from 'agent-state-machine';
83
+ import { agent, memory, askHuman, parallel } from 'agent-state-machine';
84
84
  import { notify } from './scripts/mac-notification.js';
85
85
 
86
86
  // Model configuration (also supports models in a separate config export)
@@ -101,7 +101,7 @@ export default async function() {
101
101
  console.log('Starting project-builder workflow...');
102
102
 
103
103
  // Example: Get user input (saved to memory)
104
- const userLocation = await initialPrompt('Where do you live?');
104
+ const userLocation = await askHuman('Where do you live?');
105
105
  console.log('Example prompt answer:', userLocation);
106
106
 
107
107
  const userInfo = await agent('yoda-name-collector');
@@ -168,7 +168,7 @@ A persisted object for your workflow.
168
168
  memory.count = (memory.count || 0) + 1;
169
169
  ```
170
170
 
171
- ### `initialPrompt(question, options?)`
171
+ ### `askHuman(question, options?)`
172
172
 
173
173
  Gets user input.
174
174
 
@@ -176,7 +176,7 @@ Gets user input.
176
176
  - Otherwise it creates `interactions/<slug>.md` and blocks until you confirm in the terminal (or respond in the browser).
177
177
 
178
178
  ```js
179
- const repo = await initialPrompt('What repo should I work on?', { slug: 'repo' });
179
+ const repo = await askHuman('What repo should I work on?', { slug: 'repo' });
180
180
  memory.repo = repo;
181
181
  ```
182
182
 
package/lib/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  WorkflowRuntime,
12
12
  agent,
13
13
  executeAgent,
14
- initialPrompt,
14
+ askHuman,
15
15
  parallel,
16
16
  parallelLimit,
17
17
  getMemory,
@@ -82,7 +82,7 @@ export {
82
82
  WorkflowRuntime,
83
83
  agent,
84
84
  executeAgent,
85
- initialPrompt,
85
+ askHuman,
86
86
  parallel,
87
87
  parallelLimit,
88
88
  getCurrentRuntime,
@@ -99,7 +99,7 @@ const api = {
99
99
  WorkflowRuntime,
100
100
  agent,
101
101
  executeAgent,
102
- initialPrompt,
102
+ askHuman,
103
103
  parallel,
104
104
  parallelLimit,
105
105
  getCurrentRuntime,
@@ -14,7 +14,7 @@ export {
14
14
  } from './runtime.js';
15
15
 
16
16
  export { agent, executeAgent } from './agent.js';
17
- export { initialPrompt } from './prompt.js';
17
+ export { askHuman } from './prompt.js';
18
18
  export { parallel, parallelLimit } from './parallel.js';
19
19
  export { createMemoryProxy } from './memory.js';
20
20
 
@@ -27,10 +27,10 @@ const C = {
27
27
  * @param {string} options.slug - Unique identifier for this prompt (for file)
28
28
  * @returns {Promise<string>} User's response
29
29
  */
30
- export async function initialPrompt(question, options = {}) {
30
+ export async function askHuman(question, options = {}) {
31
31
  const runtime = getCurrentRuntime();
32
32
  if (!runtime) {
33
- throw new Error('initialPrompt() must be called within a workflow context');
33
+ throw new Error('askHuman() must be called within a workflow context');
34
34
  }
35
35
 
36
36
  const slug = options.slug || generateSlug(question);
@@ -121,7 +121,7 @@ function askQuestionWithRemote(runtime, question, slug, memoryKey) {
121
121
  console.log(`\n${C.green}✓ Answered via remote${C.reset}`);
122
122
  resolve(response);
123
123
  },
124
- reject: () => {} // Prompts don't reject
124
+ reject: () => { } // Prompts don't reject
125
125
  };
126
126
  }
127
127
 
package/lib/setup.js CHANGED
@@ -59,7 +59,7 @@ async function setup(workflowName) {
59
59
  * - Interactive prompts pause and wait for user input
60
60
  */
61
61
 
62
- import { agent, memory, initialPrompt, parallel } from 'agent-state-machine';
62
+ import { agent, memory, askHuman, parallel } from 'agent-state-machine';
63
63
  import { notify } from './scripts/mac-notification.js';
64
64
 
65
65
  // Model configuration (also supports models in a separate config export)
@@ -80,7 +80,7 @@ export default async function() {
80
80
  console.log('Starting ${workflowName} workflow...');
81
81
 
82
82
  // Example: Get user input (saved to memory)
83
- const userLocation = await initialPrompt('Where do you live?');
83
+ const userLocation = await askHuman('Where do you live?');
84
84
  console.log('Example prompt answer:', userLocation);
85
85
 
86
86
  const userInfo = await agent('yoda-name-collector');
@@ -339,13 +339,13 @@ state-machine reset-hard ${workflowName}
339
339
  Edit \`workflow.js\` - write normal async JavaScript:
340
340
 
341
341
  \\\`\\\`\\\`js
342
- import { agent, memory, initialPrompt, parallel } from 'agent-state-machine';
342
+ import { agent, memory, askHuman, parallel } from 'agent-state-machine';
343
343
 
344
344
  export default async function() {
345
345
  console.log('Starting project-builder workflow...');
346
346
 
347
347
  // Example: Get user input (saved to memory)
348
- const userLocation = await initialPrompt('Where do you live?');
348
+ const userLocation = await askHuman('Where do you live?');
349
349
  console.log('Example prompt answer:', userLocation);
350
350
 
351
351
  const userInfo = await agent('yoda-name-collector');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-state-machine",
3
- "version": "2.0.7",
3
+ "version": "2.0.10",
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",
@@ -32,5 +32,8 @@
32
32
  "vercel-server/public",
33
33
  "vercel-server/ui",
34
34
  "vercel-server/api"
35
- ]
35
+ ],
36
+ "devDependencies": {
37
+ "nodemailer": "^7.0.11"
38
+ }
36
39
  }
@@ -219,7 +219,7 @@
219
219
  </script>
220
220
 
221
221
  <script type="text/babel">
222
- const { useEffect, useMemo, useState } = React;
222
+ const { useEffect, useMemo, useRef, useState } = React;
223
223
 
224
224
  const Icon = ({ name }) => {
225
225
  const common = "w-4 h-4";
@@ -253,14 +253,6 @@
253
253
  </svg>
254
254
  );
255
255
  }
256
- if (name === "sort") {
257
- return (
258
- <svg className={common} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
259
- <path strokeLinecap="round" d="M7 4h14M7 8h10M7 12h6" />
260
- <path strokeLinecap="round" strokeLinejoin="round" d="M3 20V4m0 16 2-2m-2 2-2-2" />
261
- </svg>
262
- );
263
- }
264
256
  return null;
265
257
  };
266
258
 
@@ -396,6 +388,18 @@
396
388
  function InteractionForm({ interaction, onSubmit, disabled }) {
397
389
  const [response, setResponse] = useState("");
398
390
  const [submitting, setSubmitting] = useState(false);
391
+ const textareaRef = useRef(null);
392
+
393
+ useEffect(() => {
394
+ setResponse(interaction.question || "");
395
+ }, [interaction.slug, interaction.question]);
396
+
397
+ useEffect(() => {
398
+ const el = textareaRef.current;
399
+ if (!el) return;
400
+ el.style.height = "auto";
401
+ el.style.height = `${el.scrollHeight}px`;
402
+ }, [response]);
399
403
 
400
404
  const handleSubmit = async (e) => {
401
405
  e.preventDefault();
@@ -416,8 +420,8 @@
416
420
  <div className="text-[11px] tracking-[0.22em] font-semibold" style={{ color: "var(--muted)" }}>
417
421
  INPUT REQUIRED
418
422
  </div>
419
- <div className="mt-2 text-[13px] leading-relaxed markdown-body">
420
- {interaction.question || "Please provide your input."}
423
+ <div className="mt-2 text-[12px] tracking-[0.16em] uppercase" style={{ color: "var(--muted)" }}>
424
+ Safe to delete all text and type your answer.
421
425
  </div>
422
426
  </div>
423
427
  <div className="flex items-center gap-2">
@@ -427,17 +431,17 @@
427
431
 
428
432
  <form onSubmit={handleSubmit} className="px-6 py-5">
429
433
  <textarea
434
+ ref={textareaRef}
430
435
  value={response}
431
436
  onChange={(e) => setResponse(e.target.value)}
432
437
  disabled={disabled || submitting}
433
- placeholder="Type response…"
434
438
  className="w-full rounded-2xl hairline p-4 text-[13px] leading-relaxed min-h-[120px]"
435
439
  style={{
436
440
  background: "transparent",
437
441
  color: "var(--fg)",
438
442
  borderColor: "var(--hairline)",
439
443
  outline: "none",
440
- resize: "vertical",
444
+ resize: "none",
441
445
  direction: "ltr",
442
446
  textAlign: "left",
443
447
  }}
@@ -471,7 +475,6 @@
471
475
  if (saved === "light" || saved === "dark") return saved;
472
476
  return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
473
477
  });
474
- const [sortNewest, setSortNewest] = useState(true);
475
478
  const [pendingInteraction, setPendingInteraction] = useState(null);
476
479
 
477
480
  const token =
@@ -630,12 +633,11 @@
630
633
  };
631
634
 
632
635
  const toggleTheme = () => setTheme((p) => (p === "dark" ? "light" : "dark"));
633
- const toggleSort = () => setSortNewest((p) => !p);
634
636
 
635
637
  const formatTime = (ts) =>
636
638
  new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
637
639
 
638
- const visibleEvents = sortNewest ? history : [...history].reverse();
640
+ const visibleEvents = [...history].reverse();
639
641
 
640
642
  const wrapIO = (side, children, key) => (
641
643
  <div key={key} className={`io-row ${side === "right" ? "io-right" : side === "center" ? "io-center" : "io-left"}`}>
@@ -667,14 +669,12 @@
667
669
  return wrapIO(
668
670
  "center",
669
671
  <section className="text-center">
670
- <div className="divider" />
671
672
  <div className="py-4">
672
673
  <div className="text-[11px] tracking-[0.24em] font-semibold" style={{ color: "var(--muted)" }}>
673
674
  {item.event.replace("WORKFLOW_", "")} • {time}
674
675
  </div>
675
676
  {item.error && <div className="mt-3 text-[13px] leading-relaxed markdown-body">{item.error}</div>}
676
677
  </div>
677
- <div className="divider" />
678
678
  </section>,
679
679
  idx
680
680
  );
@@ -916,15 +916,6 @@
916
916
  </div>
917
917
 
918
918
  <div className="flex items-center gap-2">
919
- <Toggle
920
- onClick={() => setSortNewest((p) => !p)}
921
- label=""
922
- title={sortNewest ? "Newest first" : "Oldest first"}
923
- >
924
- <span className={`transition-transform duration-100 ${!sortNewest ? "rotate-180" : ""}`}>
925
- <Icon name="sort" />
926
- </span>
927
- </Toggle>
928
919
  <Toggle onClick={() => setTheme((p) => (p === "dark" ? "light" : "dark"))} label="" title="Toggle theme">
929
920
  {theme === "dark" ? <Icon name="sun" /> : <Icon name="moon" />}
930
921
  </Toggle>
@@ -935,19 +926,6 @@
935
926
  </div>
936
927
  </header>
937
928
 
938
- {/* Pending interaction always left */}
939
- {pendingInteraction && (
940
- <div className="io-row io-left mb-8">
941
- <div className="io-card">
942
- <InteractionForm
943
- interaction={pendingInteraction}
944
- onSubmit={handleSubmit}
945
- disabled={status !== "connected"}
946
- />
947
- </div>
948
- </div>
949
- )}
950
-
951
929
  {/* Error stays right */}
952
930
  {error && (
953
931
  <div className="io-row io-right mb-8">
@@ -974,6 +952,18 @@
974
952
  {visibleEvents.map(renderEvent)}
975
953
  </main>
976
954
 
955
+ {pendingInteraction && (
956
+ <div className="io-row io-left mt-10">
957
+ <div className="io-card">
958
+ <InteractionForm
959
+ interaction={pendingInteraction}
960
+ onSubmit={handleSubmit}
961
+ disabled={status !== "connected"}
962
+ />
963
+ </div>
964
+ </div>
965
+ )}
966
+
977
967
  <footer className="mt-16 pt-8 divider">
978
968
  <div className="center-wrap text-center">
979
969
  <div className="py-6 text-[11px] tracking-[0.28em] font-semibold" style={{ color: "var(--muted)" }}>