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.
- 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 +1 -1
- package/templates/project-builder/agents/assumptions-clarifier.md +0 -1
- package/templates/project-builder/agents/code-reviewer.md +0 -1
- package/templates/project-builder/agents/code-writer.md +0 -1
- package/templates/project-builder/agents/requirements-clarifier.md +0 -1
- package/templates/project-builder/agents/response-interpreter.md +25 -0
- package/templates/project-builder/agents/roadmap-generator.md +0 -1
- 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 +0 -1
- package/templates/project-builder/agents/security-clarifier.md +0 -1
- package/templates/project-builder/agents/security-reviewer.md +0 -1
- package/templates/project-builder/agents/task-planner.md +0 -1
- package/templates/project-builder/agents/test-planner.md +0 -1
- package/templates/project-builder/scripts/interaction-helpers.js +33 -0
- package/templates/project-builder/scripts/workflow-helpers.js +2 -47
- package/templates/project-builder/workflow.js +214 -54
- 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,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
|
+
}
|
|
@@ -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 {
|
|
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,
|