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.
- package/bin/cli.js +1 -1
- package/lib/index.js +33 -0
- package/lib/remote/client.js +7 -2
- package/lib/runtime/agent.js +102 -67
- package/lib/runtime/index.js +13 -0
- package/lib/runtime/interaction.js +304 -0
- package/lib/runtime/prompt.js +39 -12
- package/lib/runtime/runtime.js +11 -10
- package/package.json +2 -1
- package/templates/project-builder/README.md +119 -0
- package/templates/project-builder/agents/assumptions-clarifier.md +65 -0
- package/templates/project-builder/agents/code-reviewer.md +81 -0
- package/templates/project-builder/agents/code-writer.md +74 -0
- package/templates/project-builder/agents/requirements-clarifier.md +55 -0
- package/templates/project-builder/agents/response-interpreter.md +25 -0
- package/templates/project-builder/agents/roadmap-generator.md +73 -0
- package/templates/project-builder/agents/sanity-checker.md +45 -0
- package/templates/project-builder/agents/sanity-runner.js +161 -0
- package/templates/project-builder/agents/scope-clarifier.md +44 -0
- package/templates/project-builder/agents/security-clarifier.md +71 -0
- package/templates/project-builder/agents/security-reviewer.md +71 -0
- package/templates/project-builder/agents/task-planner.md +62 -0
- package/templates/project-builder/agents/test-planner.md +76 -0
- package/templates/project-builder/config.js +13 -0
- package/templates/project-builder/scripts/interaction-helpers.js +33 -0
- package/templates/project-builder/scripts/mac-notification.js +24 -0
- package/templates/project-builder/scripts/text-human.js +92 -0
- package/templates/project-builder/scripts/workflow-helpers.js +122 -0
- package/templates/project-builder/state/current.json +9 -0
- package/templates/project-builder/state/history.jsonl +0 -0
- package/templates/project-builder/steering/config.json +5 -0
- package/templates/project-builder/steering/global.md +19 -0
- package/templates/project-builder/workflow.js +554 -0
- package/templates/starter/README.md +118 -0
- package/templates/starter/agents/example.js +36 -0
- package/templates/starter/agents/yoda-greeter.md +12 -0
- package/templates/starter/agents/yoda-name-collector.md +12 -0
- package/templates/starter/config.js +12 -0
- package/templates/starter/interactions/.gitkeep +0 -0
- package/templates/starter/scripts/mac-notification.js +24 -0
- package/templates/starter/state/current.json +9 -0
- package/templates/starter/state/history.jsonl +0 -0
- package/templates/starter/steering/config.json +5 -0
- package/templates/starter/steering/global.md +19 -0
- package/templates/starter/workflow.js +52 -0
- package/vercel-server/api/session/[token].js +3 -3
- package/vercel-server/api/submit/[token].js +5 -3
- package/vercel-server/local-server.js +33 -6
- package/vercel-server/public/remote/index.html +17 -0
- package/vercel-server/ui/index.html +9 -1012
- package/vercel-server/ui/package-lock.json +2650 -0
- package/vercel-server/ui/package.json +25 -0
- package/vercel-server/ui/postcss.config.js +6 -0
- package/vercel-server/ui/src/App.jsx +236 -0
- package/vercel-server/ui/src/components/ChoiceInteraction.jsx +127 -0
- package/vercel-server/ui/src/components/ConfirmInteraction.jsx +51 -0
- package/vercel-server/ui/src/components/ContentCard.jsx +161 -0
- package/vercel-server/ui/src/components/CopyButton.jsx +27 -0
- package/vercel-server/ui/src/components/EventsLog.jsx +82 -0
- package/vercel-server/ui/src/components/Footer.jsx +66 -0
- package/vercel-server/ui/src/components/Header.jsx +38 -0
- package/vercel-server/ui/src/components/InteractionForm.jsx +42 -0
- package/vercel-server/ui/src/components/TextInteraction.jsx +72 -0
- package/vercel-server/ui/src/index.css +145 -0
- package/vercel-server/ui/src/main.jsx +8 -0
- package/vercel-server/ui/tailwind.config.js +19 -0
- 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 };
|
|
File without changes
|
|
@@ -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
|
|
15
|
-
const templatePath = path.join(__dirname, '..', '..', '
|
|
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 ||
|
|
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 (
|
|
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:
|
|
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 ||
|
|
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:
|
|
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, '
|
|
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
|
|
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>
|
|
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 === '/'
|
|
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>
|