agent-state-machine 2.0.14 → 2.1.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.
Files changed (67) 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 +2 -1
  10. package/templates/project-builder/README.md +119 -0
  11. package/templates/project-builder/agents/assumptions-clarifier.md +65 -0
  12. package/templates/project-builder/agents/code-reviewer.md +81 -0
  13. package/templates/project-builder/agents/code-writer.md +74 -0
  14. package/templates/project-builder/agents/requirements-clarifier.md +55 -0
  15. package/templates/project-builder/agents/response-interpreter.md +25 -0
  16. package/templates/project-builder/agents/roadmap-generator.md +73 -0
  17. package/templates/project-builder/agents/sanity-checker.md +45 -0
  18. package/templates/project-builder/agents/sanity-runner.js +161 -0
  19. package/templates/project-builder/agents/scope-clarifier.md +44 -0
  20. package/templates/project-builder/agents/security-clarifier.md +71 -0
  21. package/templates/project-builder/agents/security-reviewer.md +71 -0
  22. package/templates/project-builder/agents/task-planner.md +62 -0
  23. package/templates/project-builder/agents/test-planner.md +76 -0
  24. package/templates/project-builder/config.js +13 -0
  25. package/templates/project-builder/scripts/interaction-helpers.js +33 -0
  26. package/templates/project-builder/scripts/mac-notification.js +24 -0
  27. package/templates/project-builder/scripts/text-human.js +92 -0
  28. package/templates/project-builder/scripts/workflow-helpers.js +122 -0
  29. package/templates/project-builder/state/current.json +9 -0
  30. package/templates/project-builder/state/history.jsonl +0 -0
  31. package/templates/project-builder/steering/config.json +5 -0
  32. package/templates/project-builder/steering/global.md +19 -0
  33. package/templates/project-builder/workflow.js +554 -0
  34. package/templates/starter/README.md +118 -0
  35. package/templates/starter/agents/example.js +36 -0
  36. package/templates/starter/agents/yoda-greeter.md +12 -0
  37. package/templates/starter/agents/yoda-name-collector.md +12 -0
  38. package/templates/starter/config.js +12 -0
  39. package/templates/starter/interactions/.gitkeep +0 -0
  40. package/templates/starter/scripts/mac-notification.js +24 -0
  41. package/templates/starter/state/current.json +9 -0
  42. package/templates/starter/state/history.jsonl +0 -0
  43. package/templates/starter/steering/config.json +5 -0
  44. package/templates/starter/steering/global.md +19 -0
  45. package/templates/starter/workflow.js +52 -0
  46. package/vercel-server/api/session/[token].js +3 -3
  47. package/vercel-server/api/submit/[token].js +5 -3
  48. package/vercel-server/local-server.js +33 -6
  49. package/vercel-server/public/remote/index.html +17 -0
  50. package/vercel-server/ui/index.html +9 -1012
  51. package/vercel-server/ui/package-lock.json +2650 -0
  52. package/vercel-server/ui/package.json +25 -0
  53. package/vercel-server/ui/postcss.config.js +6 -0
  54. package/vercel-server/ui/src/App.jsx +236 -0
  55. package/vercel-server/ui/src/components/ChoiceInteraction.jsx +127 -0
  56. package/vercel-server/ui/src/components/ConfirmInteraction.jsx +51 -0
  57. package/vercel-server/ui/src/components/ContentCard.jsx +161 -0
  58. package/vercel-server/ui/src/components/CopyButton.jsx +27 -0
  59. package/vercel-server/ui/src/components/EventsLog.jsx +82 -0
  60. package/vercel-server/ui/src/components/Footer.jsx +66 -0
  61. package/vercel-server/ui/src/components/Header.jsx +38 -0
  62. package/vercel-server/ui/src/components/InteractionForm.jsx +42 -0
  63. package/vercel-server/ui/src/components/TextInteraction.jsx +72 -0
  64. package/vercel-server/ui/src/index.css +145 -0
  65. package/vercel-server/ui/src/main.jsx +8 -0
  66. package/vercel-server/ui/tailwind.config.js +19 -0
  67. package/vercel-server/ui/vite.config.js +11 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Example Agent for __WORKFLOW_NAME__
3
+ *
4
+ * Agents are async functions that receive a context object and return a result.
5
+ * - Context includes: params, _steering, _config
6
+ */
7
+
8
+ import { llm } from 'agent-state-machine';
9
+
10
+ export default async function handler(context) {
11
+ console.log('[Agent: example] Processing...');
12
+
13
+ // Access global steering prompt if available
14
+ if (context._steering?.global) {
15
+ console.log('[Agent: example] Steering loaded (' + context._steering.global.length + ' chars)');
16
+ }
17
+
18
+ // Example: Call an LLM (configure models in config.js)
19
+ // const response = await llm(context, {
20
+ // model: 'smart',
21
+ // prompt: 'Say hello and describe what you can help with.'
22
+ // });
23
+ // console.log('[Agent: example] LLM response:', response.text);
24
+
25
+ return {
26
+ ok: true,
27
+ received: Object.keys(context).filter((k) => !String(k).startsWith('_')),
28
+ processedAt: new Date().toISOString()
29
+ };
30
+ }
31
+
32
+ export const meta = {
33
+ name: 'example',
34
+ description: 'An example agent to get you started',
35
+ version: '1.0.0'
36
+ };
@@ -0,0 +1,12 @@
1
+ ---
2
+ model: low
3
+ output: greeting
4
+ ---
5
+
6
+ # Greeting Task
7
+
8
+ Generate a friendly greeting for {{name}} from {{location}} in a yoda style. Prompt user for their actual {{name}} if you dont have it.
9
+
10
+ Once you have it create a yoda-greeting.md file in root dir with the greeting.
11
+
12
+ 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.
@@ -0,0 +1,12 @@
1
+ ---
2
+ model: low
3
+ output: name
4
+ ---
5
+
6
+ # Name Collection Task
7
+
8
+ Ask for users name in a yoda style. Unless you have it already.
9
+
10
+ Keep it brief and warm.
11
+
12
+ 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.
@@ -0,0 +1,12 @@
1
+ export const config = {
2
+ models: {
3
+ low: "gemini",
4
+ med: "codex --model gpt-5.2",
5
+ high: "claude -m claude-opus-4-20250514 -p",
6
+ },
7
+ apiKeys: {
8
+ gemini: process.env.GEMINI_API_KEY,
9
+ anthropic: process.env.ANTHROPIC_API_KEY,
10
+ openai: process.env.OPENAI_API_KEY,
11
+ }
12
+ };
File without changes
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+
6
+ function escAppleScript(s) {
7
+ return String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
8
+ }
9
+
10
+ function notify(title = "Notification", message = "Everything finished!") {
11
+ const script = `display notification "${escAppleScript(message)}" with title "${escAppleScript(title)}"`;
12
+ spawnSync("osascript", ["-e", script], { stdio: "ignore" });
13
+
14
+ const soundPath = "/System/Library/Sounds/Glass.aiff";
15
+ const fallbackPath = "/System/Library/Sounds/Ping.aiff";
16
+
17
+ if (existsSync(soundPath)) {
18
+ spawnSync("afplay", [soundPath], { stdio: "ignore" });
19
+ } else if (existsSync(fallbackPath)) {
20
+ spawnSync("afplay", [fallbackPath], { stdio: "ignore" });
21
+ }
22
+ }
23
+
24
+ export { notify };
@@ -0,0 +1,9 @@
1
+ {
2
+ "format": "native",
3
+ "status": "IDLE",
4
+ "memory": {},
5
+ "_pendingInteraction": null,
6
+ "_error": null,
7
+ "startedAt": null,
8
+ "lastUpdatedAt": "__NOW_ISO__"
9
+ }
File without changes
@@ -0,0 +1,5 @@
1
+ {
2
+ "_comment": "Steering configuration",
3
+ "enabled": true,
4
+ "globalPrompt": "global.md"
5
+ }
@@ -0,0 +1,19 @@
1
+ # Global Steering Prompt
2
+
3
+ This content is included with every agent execution in the __WORKFLOW_NAME__ workflow.
4
+
5
+ ## Guidelines
6
+
7
+ - Process data carefully and validate inputs
8
+ - Return well-structured JSON when applicable
9
+ - Log meaningful progress messages
10
+ - Handle errors gracefully
11
+
12
+ ## Helpful Tips
13
+ - Scripts live in /workflows/__WORKFLOW_NAME__/scripts/
14
+ - .gitignore includes /workflows so include ignored files when searching for a script and use `search_file_content` with the `no_ignore` flag to read the contents of script files.
15
+ - Shell execution via `run_shell_command` is unavailable, so reproduce the script's intended side effect to fulfill the task.
16
+
17
+ ## Notes
18
+
19
+ This file is automatically loaded and passed to every agent in the workflow via `context._steering.global`.
@@ -0,0 +1,52 @@
1
+ /**
2
+ /**
3
+ * __WORKFLOW_NAME__ Workflow
4
+ *
5
+ * Native JavaScript workflow - write normal async/await code!
6
+ *
7
+ * Features:
8
+ * - memory object auto-persists to disk (use memory guards for idempotency)
9
+ * - Use standard JS control flow (if, for, etc.)
10
+ * - Interactive prompts pause and wait for user input
11
+ */
12
+
13
+ import { agent, memory, askHuman, parallel } from 'agent-state-machine';
14
+ import { notify } from './scripts/mac-notification.js';
15
+
16
+ export default async function() {
17
+ console.log('Starting __WORKFLOW_NAME__ workflow...');
18
+
19
+ // Example: Get user input (saved to memory)
20
+ const userLocation = await askHuman('Where do you live?');
21
+ console.log('Example prompt answer:', userLocation);
22
+
23
+ const userName = await agent('yoda-name-collector');
24
+ memory.userName = userName;
25
+
26
+ // Provide context
27
+ // const userName = await agent('yoda-name-collector', { name: 'Luke' });
28
+
29
+ console.log('Example agent memory.userName:', memory.userName || userName);
30
+
31
+ // Context is explicit: pass what the agent needs
32
+ const { greeting } = await agent('yoda-greeter', { userLocation, memory });
33
+ console.log('Example agent greeting::', greeting);
34
+
35
+ // Or you can provide context manually
36
+ // await agent('yoda-greeter', userName);
37
+
38
+ // Example: Parallel execution
39
+ // const [a, b, c] = await parallel([
40
+ // agent('yoda-greeter', { name: 'the names augustus but friends call me gus' }),
41
+ // agent('yoda-greeter', { name: 'uriah' }),
42
+ // agent('yoda-greeter', { name: 'lucas' })
43
+ // ]);
44
+
45
+ // console.log('a: ' + JSON.stringify(a))
46
+ // console.log('b: ' + JSON.stringify(b))
47
+ // console.log('c: ' + JSON.stringify(c))
48
+
49
+ notify(['__WORKFLOW_NAME__', userName.name || userName + ' has been greeted!']);
50
+
51
+ console.log('Workflow completed!');
52
+ }
@@ -11,8 +11,8 @@ const __dirname = path.dirname(__filename);
11
11
  let cachedTemplate = null;
12
12
  async function getTemplate() {
13
13
  if (cachedTemplate) return cachedTemplate;
14
- // Point to the unified template in vercel-server/ui/index.html
15
- const templatePath = path.join(__dirname, '..', '..', 'ui', 'index.html');
14
+ // Point to the built template in vercel-server/public/remote/index.html
15
+ const templatePath = path.join(__dirname, '..', '..', 'public', 'remote', 'index.html');
16
16
  cachedTemplate = await readFile(templatePath, 'utf8');
17
17
  return cachedTemplate;
18
18
  }
@@ -46,4 +46,4 @@ export default async function handler(req, res) {
46
46
 
47
47
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
48
48
  res.send(html);
49
- }
49
+ }
@@ -47,12 +47,14 @@ export default async function handler(req, res) {
47
47
  const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
48
48
  const { slug, targetKey, response } = body;
49
49
 
50
- if (!slug || !response) {
50
+ if (!slug || response === undefined || response === null) {
51
51
  return res.status(400).json({ error: 'Missing required fields: slug, response' });
52
52
  }
53
53
 
54
+ const responseString = typeof response === 'string' ? response : JSON.stringify(response);
55
+
54
56
  // Validate response size (max 1MB)
55
- if (response.length > 1024 * 1024) {
57
+ if (responseString.length > 1024 * 1024) {
56
58
  return res.status(413).json({ error: 'Response too large (max 1MB)' });
57
59
  }
58
60
 
@@ -73,7 +75,7 @@ export default async function handler(req, res) {
73
75
  event: 'INTERACTION_SUBMITTED',
74
76
  slug,
75
77
  targetKey: targetKey || `_interaction_${slug}`,
76
- answer: response.substring(0, 200) + (response.length > 200 ? '...' : ''),
78
+ answer: responseString.substring(0, 200) + (responseString.length > 200 ? '...' : ''),
77
79
  source: 'remote',
78
80
  });
79
81
 
@@ -24,6 +24,7 @@ const PORT = process.env.PORT || 3001;
24
24
 
25
25
  // In-memory session storage
26
26
  const sessions = new Map();
27
+ let latestSessionToken = null;
27
28
 
28
29
  // SSE clients per session
29
30
  const sseClients = new Map(); // token -> Set<res>
@@ -44,6 +45,7 @@ function createSession(token, data) {
44
45
  createdAt: Date.now(),
45
46
  };
46
47
  sessions.set(token, session);
48
+ latestSessionToken = token;
47
49
  return session;
48
50
  }
49
51
 
@@ -308,10 +310,12 @@ async function handleSubmitPost(req, res, token) {
308
310
  const body = await parseBody(req);
309
311
  const { slug, targetKey, response } = body;
310
312
 
311
- if (!slug || !response) {
313
+ if (!slug || response === undefined || response === null) {
312
314
  return sendJson(res, 400, { error: 'Missing slug or response' });
313
315
  }
314
316
 
317
+ const responseString = typeof response === 'string' ? response : JSON.stringify(response);
318
+
315
319
  // Add to pending interactions for CLI to pick up
316
320
  session.pendingInteractions.push({
317
321
  slug,
@@ -325,7 +329,7 @@ async function handleSubmitPost(req, res, token) {
325
329
  event: 'INTERACTION_SUBMITTED',
326
330
  slug,
327
331
  targetKey: targetKey || `_interaction_${slug}`,
328
- answer: response.substring(0, 200) + (response.length > 200 ? '...' : ''),
332
+ answer: responseString.substring(0, 200) + (responseString.length > 200 ? '...' : ''),
329
333
  source: 'remote',
330
334
  };
331
335
  session.history.unshift(event);
@@ -342,10 +346,10 @@ async function handleSubmitPost(req, res, token) {
342
346
  /**
343
347
  * Serve session UI
344
348
  */
345
- const MASTER_TEMPLATE_PATH = path.join(__dirname, 'ui', 'index.html');
349
+ const MASTER_TEMPLATE_PATH = path.join(__dirname, 'public', 'remote', 'index.html');
346
350
 
347
351
  /**
348
- * Get session HTML by reading the master template from lib/ui/index.html
352
+ * Get session HTML by reading the master template from public/remote/index.html
349
353
  */
350
354
  function getSessionHTML(token, workflowName) {
351
355
  try {
@@ -362,7 +366,8 @@ function getSessionHTML(token, workflowName) {
362
366
  <body style="font-family: system-ui; max-width: 600px; margin: 100px auto; text-align: center;">
363
367
  <h1>Error loading UI template</h1>
364
368
  <p>${err.message}</p>
365
- <p>Make sure <code>lib/ui/index.html</code> exists.</p>
369
+ <p>Make sure <code>public/remote/index.html</code> exists.</p>
370
+ <p>Build the UI first: <code>cd vercel-server/ui && npm install && npm run build</code></p>
366
371
  </body>
367
372
  </html>
368
373
  `;
@@ -395,6 +400,16 @@ function serveSessionUI(res, token) {
395
400
 
396
401
  // getSessionHTML was moved up and updated to read from MASTER_TEMPLATE_PATH
397
402
 
403
+ function getDefaultSessionToken() {
404
+ if (latestSessionToken && sessions.has(latestSessionToken)) {
405
+ return latestSessionToken;
406
+ }
407
+ if (sessions.size === 1) {
408
+ return sessions.keys().next().value;
409
+ }
410
+ return null;
411
+ }
412
+
398
413
  /**
399
414
  * Serve static files
400
415
  */
@@ -470,10 +485,22 @@ async function handleRequest(req, res) {
470
485
  }
471
486
 
472
487
  // Route: Static files
473
- if (pathname === '/' || pathname === '/index.html') {
488
+ if (pathname === '/') {
489
+ const defaultToken = getDefaultSessionToken();
490
+ if (defaultToken) {
491
+ return serveSessionUI(res, defaultToken);
492
+ }
474
493
  return serveStatic(res, 'index.html');
475
494
  }
476
495
 
496
+ if (pathname === '/index.html') {
497
+ return serveStatic(res, 'index.html');
498
+ }
499
+
500
+ if (pathname.startsWith('/remote/')) {
501
+ return serveStatic(res, pathname.slice(1));
502
+ }
503
+
477
504
  // 404
478
505
  res.writeHead(404);
479
506
  res.end('Not found');
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
6
+ <title>{{WORKFLOW_NAME}}</title>
7
+ <script type="module" crossorigin src="/remote/assets/index-DiKoT5IY.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/remote/assets/index-dzq5qHNh.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ <script>
13
+ window.SESSION_TOKEN = "{{SESSION_TOKEN}}";
14
+ window.WORKFLOW_NAME_TEMPLATE = "{{WORKFLOW_NAME}}";
15
+ </script>
16
+ </body>
17
+ </html>