agent-state-machine 2.0.15 → 2.1.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.
Files changed (47) 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 +1 -1
  10. package/templates/project-builder/agents/assumptions-clarifier.md +0 -1
  11. package/templates/project-builder/agents/code-reviewer.md +0 -1
  12. package/templates/project-builder/agents/code-writer.md +0 -1
  13. package/templates/project-builder/agents/requirements-clarifier.md +0 -1
  14. package/templates/project-builder/agents/response-interpreter.md +25 -0
  15. package/templates/project-builder/agents/roadmap-generator.md +0 -1
  16. package/templates/project-builder/agents/sanity-checker.md +45 -0
  17. package/templates/project-builder/agents/sanity-runner.js +161 -0
  18. package/templates/project-builder/agents/scope-clarifier.md +0 -1
  19. package/templates/project-builder/agents/security-clarifier.md +0 -1
  20. package/templates/project-builder/agents/security-reviewer.md +0 -1
  21. package/templates/project-builder/agents/task-planner.md +0 -1
  22. package/templates/project-builder/agents/test-planner.md +0 -1
  23. package/templates/project-builder/scripts/interaction-helpers.js +33 -0
  24. package/templates/project-builder/scripts/workflow-helpers.js +2 -47
  25. package/templates/project-builder/workflow.js +214 -54
  26. package/vercel-server/api/session/[token].js +3 -3
  27. package/vercel-server/api/submit/[token].js +5 -3
  28. package/vercel-server/local-server.js +33 -6
  29. package/vercel-server/public/remote/index.html +17 -0
  30. package/vercel-server/ui/index.html +9 -1012
  31. package/vercel-server/ui/package-lock.json +2650 -0
  32. package/vercel-server/ui/package.json +25 -0
  33. package/vercel-server/ui/postcss.config.js +6 -0
  34. package/vercel-server/ui/src/App.jsx +236 -0
  35. package/vercel-server/ui/src/components/ChoiceInteraction.jsx +127 -0
  36. package/vercel-server/ui/src/components/ConfirmInteraction.jsx +51 -0
  37. package/vercel-server/ui/src/components/ContentCard.jsx +161 -0
  38. package/vercel-server/ui/src/components/CopyButton.jsx +27 -0
  39. package/vercel-server/ui/src/components/EventsLog.jsx +82 -0
  40. package/vercel-server/ui/src/components/Footer.jsx +66 -0
  41. package/vercel-server/ui/src/components/Header.jsx +38 -0
  42. package/vercel-server/ui/src/components/InteractionForm.jsx +42 -0
  43. package/vercel-server/ui/src/components/TextInteraction.jsx +72 -0
  44. package/vercel-server/ui/src/index.css +145 -0
  45. package/vercel-server/ui/src/main.jsx +8 -0
  46. package/vercel-server/ui/tailwind.config.js +19 -0
  47. package/vercel-server/ui/vite.config.js +11 -0
@@ -0,0 +1,161 @@
1
+ import { exec, spawn } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ const DEFAULT_TIMEOUT_MS = 30000;
6
+
7
+ export default async function sanityRunner(context) {
8
+ const { checks = [], setup, teardown } = context;
9
+ const cwd = context?._config?.workflowDir || process.cwd();
10
+ const results = [];
11
+
12
+ let setupError = null;
13
+ if (setup) {
14
+ try {
15
+ await runSetup(setup, cwd);
16
+ } catch (error) {
17
+ setupError = error;
18
+ }
19
+ }
20
+
21
+ for (const check of checks) {
22
+ if (setupError) {
23
+ results.push({
24
+ id: check.id,
25
+ status: 'failed',
26
+ error: `Setup failed: ${setupError.message}`
27
+ });
28
+ continue;
29
+ }
30
+
31
+ const result = await runCheck(check, cwd);
32
+ results.push(result);
33
+ }
34
+
35
+ if (teardown) {
36
+ try {
37
+ await execCommand(teardown, cwd, DEFAULT_TIMEOUT_MS);
38
+ } catch (error) {
39
+ results.push({
40
+ id: 'teardown',
41
+ status: 'failed',
42
+ error: `Teardown failed: ${error.message}`
43
+ });
44
+ }
45
+ }
46
+
47
+ const summary = results.reduce((acc, item) => {
48
+ if (item.status === 'passed') acc.passed += 1;
49
+ if (item.status === 'failed') acc.failed += 1;
50
+ return acc;
51
+ }, { passed: 0, failed: 0 });
52
+
53
+ return { summary, results };
54
+ }
55
+
56
+ async function runSetup(command, cwd) {
57
+ const trimmed = command.trim();
58
+ if (trimmed.endsWith('&')) {
59
+ const withoutAmp = trimmed.replace(/&\s*$/, '').trim();
60
+ const child = spawn(withoutAmp, {
61
+ cwd,
62
+ shell: true,
63
+ detached: true,
64
+ stdio: 'ignore'
65
+ });
66
+ child.unref();
67
+ return;
68
+ }
69
+ await execCommand(command, cwd, DEFAULT_TIMEOUT_MS);
70
+ }
71
+
72
+ async function runCheck(check, cwd) {
73
+ const timeoutMs = check.timeoutMs || DEFAULT_TIMEOUT_MS;
74
+ const type = check.type || 'shell';
75
+ const id = check.id ?? 'unknown';
76
+
77
+ try {
78
+ if (type === 'shell') {
79
+ const output = await execCommand(check.command, cwd, timeoutMs);
80
+ return compareOutput(id, output, check);
81
+ }
82
+
83
+ if (type === 'test_suite') {
84
+ await execCommand(check.command || check.testCommand, cwd, timeoutMs);
85
+ return { id, status: 'passed' };
86
+ }
87
+
88
+ if (type === 'file_exists') {
89
+ const filePath = path.resolve(cwd, check.path || '');
90
+ if (fs.existsSync(filePath)) {
91
+ return { id, status: 'passed' };
92
+ }
93
+ return { id, status: 'failed', error: `File not found: ${check.path}` };
94
+ }
95
+
96
+ if (type === 'file_contains') {
97
+ const filePath = path.resolve(cwd, check.path || '');
98
+ if (!fs.existsSync(filePath)) {
99
+ return { id, status: 'failed', error: `File not found: ${check.path}` };
100
+ }
101
+ const content = fs.readFileSync(filePath, 'utf-8');
102
+ const pattern = check.pattern || check.contains || check.text || '';
103
+ if (!pattern) {
104
+ return { id, status: 'failed', error: 'Missing pattern for file_contains' };
105
+ }
106
+ const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern, 'm');
107
+ if (regex.test(content)) {
108
+ return { id, status: 'passed' };
109
+ }
110
+ return { id, status: 'failed', error: `Pattern not found: ${pattern}` };
111
+ }
112
+
113
+ return { id, status: 'failed', error: `Unsupported check type: ${type}` };
114
+ } catch (error) {
115
+ return {
116
+ id,
117
+ status: 'failed',
118
+ error: error.message,
119
+ output: error.output
120
+ };
121
+ }
122
+ }
123
+
124
+ function compareOutput(id, output, check) {
125
+ const expected = check.expected ?? '';
126
+ const comparison = check.comparison || 'equals';
127
+ const trimmed = String(output ?? '').trim();
128
+
129
+ if (comparison === 'not_empty') {
130
+ return trimmed.length > 0
131
+ ? { id, status: 'passed', output: trimmed }
132
+ : { id, status: 'failed', error: 'Output was empty', output: trimmed };
133
+ }
134
+
135
+ if (comparison === 'contains') {
136
+ return trimmed.includes(String(expected))
137
+ ? { id, status: 'passed', output: trimmed }
138
+ : { id, status: 'failed', error: `Output did not contain: ${expected}`, output: trimmed };
139
+ }
140
+
141
+ return trimmed === String(expected)
142
+ ? { id, status: 'passed', output: trimmed }
143
+ : { id, status: 'failed', error: `Expected "${expected}", got "${trimmed}"`, output: trimmed };
144
+ }
145
+
146
+ function execCommand(command, cwd, timeoutMs) {
147
+ return new Promise((resolve, reject) => {
148
+ if (!command) {
149
+ reject(new Error('Missing command'));
150
+ return;
151
+ }
152
+ exec(command, { cwd, timeout: timeoutMs, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
153
+ if (error) {
154
+ error.output = stderr || stdout;
155
+ reject(error);
156
+ return;
157
+ }
158
+ resolve(stdout || stderr || '');
159
+ });
160
+ });
161
+ }
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  model: med
3
- output: result
4
3
  format: json
5
4
  interaction: true
6
5
  ---
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  model: med
3
- output: result
4
3
  format: json
5
4
  interaction: true
6
5
  ---
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  model: med
3
- output: result
4
3
  format: json
5
4
  ---
6
5
 
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  model: high
3
- output: result
4
3
  format: json
5
4
  ---
6
5
 
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  model: med
3
- output: result
4
3
  format: json
5
4
  ---
6
5
 
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Interaction helpers for project-builder template
3
+ *
4
+ * Re-exports core interaction utilities from agent-state-machine
5
+ * and adds an LLM-based interpreter for ambiguous responses.
6
+ */
7
+
8
+ import {
9
+ agent,
10
+ createInteraction,
11
+ formatInteractionPrompt,
12
+ normalizeInteraction,
13
+ parseInteractionResponse
14
+ } from 'agent-state-machine';
15
+
16
+ // Re-export core utilities
17
+ export { createInteraction, formatInteractionPrompt, normalizeInteraction };
18
+
19
+ /**
20
+ * Parse a response with LLM interpreter fallback
21
+ *
22
+ * Uses the response-interpreter agent when fast-path matching fails.
23
+ */
24
+ export async function parseResponse(interaction, rawResponse) {
25
+ return parseInteractionResponse(interaction, rawResponse, async (int, raw) => {
26
+ // Use the response-interpreter agent to interpret ambiguous responses
27
+ const result = await agent('response-interpreter', {
28
+ userResponse: raw,
29
+ interaction: int
30
+ });
31
+ return result;
32
+ });
33
+ }
@@ -1,15 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { agent, memory } from 'agent-state-machine';
4
-
5
- // Normalize agent output - always extract the result consistently
6
- function unwrap(agentResult) {
7
- if (!agentResult) return null;
8
- if (typeof agentResult === 'object' && 'result' in agentResult) {
9
- return agentResult.result;
10
- }
11
- return agentResult;
12
- }
3
+ import { memory } from 'agent-state-machine';
13
4
 
14
5
  // Write markdown file to workflow state directory
15
6
  function writeMarkdownFile(stateDir, filename, content) {
@@ -84,41 +75,6 @@ function renderTasksMarkdown(phaseNumber, phaseTitle, tasks) {
84
75
  return md;
85
76
  }
86
77
 
87
- // Run agent with error handling and retry capability
88
- async function safeAgent(name, params, options = {}) {
89
- const { maxRetries = 1, onError } = options;
90
- let lastError;
91
-
92
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
93
- try {
94
- const result = await agent(name, params);
95
- return unwrap(result);
96
- } catch (error) {
97
- lastError = error;
98
- console.error(` [Agent: ${name}] Error (attempt ${attempt}/${maxRetries}): ${error.message}`);
99
-
100
- if (onError) {
101
- const shouldRetry = await onError(error, attempt);
102
- if (!shouldRetry) break;
103
- }
104
-
105
- if (attempt < maxRetries) {
106
- console.log(` [Agent: ${name}] Retrying...`);
107
- }
108
- }
109
- }
110
-
111
- // Store error in memory for debugging
112
- if (!memory._errors) memory._errors = [];
113
- memory._errors.push({
114
- agent: name,
115
- error: lastError?.message,
116
- timestamp: new Date().toISOString()
117
- });
118
-
119
- throw lastError;
120
- }
121
-
122
78
  // Task stage management
123
79
  const TASK_STAGES = {
124
80
  PENDING: 'pending',
@@ -127,6 +83,7 @@ const TASK_STAGES = {
127
83
  IMPLEMENTING: 'implementing',
128
84
  CODE_REVIEW: 'code_review',
129
85
  SECURITY_POST: 'security_post',
86
+ SANITY_CHECK: 'sanity_check',
130
87
  AWAITING_APPROVAL: 'awaiting_approval',
131
88
  COMPLETED: 'completed',
132
89
  FAILED: 'failed'
@@ -153,12 +110,10 @@ function setTaskData(phaseIndex, taskId, dataKey, value) {
153
110
  }
154
111
 
155
112
  export {
156
- unwrap,
157
113
  writeMarkdownFile,
158
114
  isApproval,
159
115
  renderRoadmapMarkdown,
160
116
  renderTasksMarkdown,
161
- safeAgent,
162
117
  TASK_STAGES,
163
118
  getTaskStage,
164
119
  setTaskStage,