agent-state-machine 2.0.13 → 2.0.15
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 +19 -6
- package/bin/cli.js +23 -5
- package/lib/setup.js +82 -388
- package/package.json +2 -1
- package/templates/project-builder/README.md +119 -0
- package/templates/project-builder/agents/assumptions-clarifier.md +66 -0
- package/templates/project-builder/agents/code-reviewer.md +82 -0
- package/templates/project-builder/agents/code-writer.md +75 -0
- package/templates/project-builder/agents/requirements-clarifier.md +56 -0
- package/templates/project-builder/agents/roadmap-generator.md +74 -0
- package/templates/project-builder/agents/scope-clarifier.md +45 -0
- package/templates/project-builder/agents/security-clarifier.md +72 -0
- package/templates/project-builder/agents/security-reviewer.md +72 -0
- package/templates/project-builder/agents/task-planner.md +63 -0
- package/templates/project-builder/agents/test-planner.md +77 -0
- package/templates/project-builder/config.js +13 -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 +167 -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 +394 -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
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Builder Workflow
|
|
3
|
+
*
|
|
4
|
+
* A comprehensive workflow that guides users through:
|
|
5
|
+
* 1. Project intake and clarification
|
|
6
|
+
* 2. Phased roadmap generation and approval
|
|
7
|
+
* 3. Sequential phase execution with task lists
|
|
8
|
+
* 4. Task lifecycle with optimal agent sequencing
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { memory, askHuman } from 'agent-state-machine';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import {
|
|
15
|
+
writeMarkdownFile,
|
|
16
|
+
isApproval,
|
|
17
|
+
renderRoadmapMarkdown,
|
|
18
|
+
renderTasksMarkdown,
|
|
19
|
+
safeAgent,
|
|
20
|
+
TASK_STAGES,
|
|
21
|
+
getTaskStage,
|
|
22
|
+
setTaskStage,
|
|
23
|
+
getTaskData,
|
|
24
|
+
setTaskData
|
|
25
|
+
} from './scripts/workflow-helpers.js';
|
|
26
|
+
|
|
27
|
+
// Derive workflow directory dynamically
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = path.dirname(__filename);
|
|
30
|
+
const WORKFLOW_DIR = __dirname;
|
|
31
|
+
const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
|
|
32
|
+
|
|
33
|
+
// ============================================
|
|
34
|
+
// MAIN WORKFLOW
|
|
35
|
+
// ============================================
|
|
36
|
+
|
|
37
|
+
export default async function () {
|
|
38
|
+
console.log('Starting Project Builder Workflow...\n');
|
|
39
|
+
|
|
40
|
+
// ============================================
|
|
41
|
+
// PHASE 1: PROJECT INTAKE
|
|
42
|
+
// ============================================
|
|
43
|
+
console.log('=== PHASE 1: PROJECT INTAKE ===\n');
|
|
44
|
+
|
|
45
|
+
if (!memory.projectDescription) {
|
|
46
|
+
const description = await askHuman(
|
|
47
|
+
'Describe the project you want to build. Include any initial requirements, goals, or constraints you have in mind.',
|
|
48
|
+
{ slug: 'project-description' }
|
|
49
|
+
);
|
|
50
|
+
memory.projectDescription = description;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log('Project description captured. Starting clarification process...\n');
|
|
54
|
+
|
|
55
|
+
// ============================================
|
|
56
|
+
// CLARIFICATION AGENTS (Optimal Sequence)
|
|
57
|
+
// Order: Scope -> Requirements -> Assumptions -> Security
|
|
58
|
+
// Runtime handles interaction blocking automatically
|
|
59
|
+
// ============================================
|
|
60
|
+
|
|
61
|
+
// 1. Scope Clarification
|
|
62
|
+
if (!memory.scopeClarified) {
|
|
63
|
+
console.log('--- Scope Clarification ---');
|
|
64
|
+
const scopeResult = await safeAgent('scope-clarifier', {
|
|
65
|
+
projectDescription: memory.projectDescription
|
|
66
|
+
});
|
|
67
|
+
memory.scope = scopeResult;
|
|
68
|
+
memory.scopeClarified = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Requirements Clarification
|
|
72
|
+
if (!memory.requirementsClarified) {
|
|
73
|
+
console.log('--- Requirements Clarification ---');
|
|
74
|
+
const reqResult = await safeAgent('requirements-clarifier', {
|
|
75
|
+
projectDescription: memory.projectDescription,
|
|
76
|
+
scope: memory.scope
|
|
77
|
+
});
|
|
78
|
+
memory.requirements = reqResult;
|
|
79
|
+
memory.requirementsClarified = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 3. Assumptions Clarification
|
|
83
|
+
if (!memory.assumptionsClarified) {
|
|
84
|
+
console.log('--- Assumptions Clarification ---');
|
|
85
|
+
const assumeResult = await safeAgent('assumptions-clarifier', {
|
|
86
|
+
projectDescription: memory.projectDescription,
|
|
87
|
+
scope: memory.scope,
|
|
88
|
+
requirements: memory.requirements
|
|
89
|
+
});
|
|
90
|
+
memory.assumptions = assumeResult;
|
|
91
|
+
memory.assumptionsClarified = true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 4. Security Clarification
|
|
95
|
+
if (!memory.securityClarified) {
|
|
96
|
+
console.log('--- Security Clarification ---');
|
|
97
|
+
const secResult = await safeAgent('security-clarifier', {
|
|
98
|
+
projectDescription: memory.projectDescription,
|
|
99
|
+
scope: memory.scope,
|
|
100
|
+
requirements: memory.requirements,
|
|
101
|
+
assumptions: memory.assumptions
|
|
102
|
+
});
|
|
103
|
+
memory.security = secResult;
|
|
104
|
+
memory.securityClarified = true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log('\nClarification complete. Generating roadmap...\n');
|
|
108
|
+
|
|
109
|
+
// ============================================
|
|
110
|
+
// PHASE 2: PHASED ROADMAP
|
|
111
|
+
// ============================================
|
|
112
|
+
console.log('=== PHASE 2: PHASED ROADMAP ===\n');
|
|
113
|
+
|
|
114
|
+
if (!memory.roadmapApproved) {
|
|
115
|
+
// Generate roadmap as JSON
|
|
116
|
+
if (!memory.roadmap) {
|
|
117
|
+
const roadmapResult = await safeAgent('roadmap-generator', {
|
|
118
|
+
projectDescription: memory.projectDescription,
|
|
119
|
+
scope: memory.scope,
|
|
120
|
+
requirements: memory.requirements,
|
|
121
|
+
assumptions: memory.assumptions,
|
|
122
|
+
security: memory.security
|
|
123
|
+
});
|
|
124
|
+
memory.roadmap = roadmapResult;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
writeMarkdownFile(STATE_DIR, 'roadmap.md', renderRoadmapMarkdown(memory.roadmap));
|
|
128
|
+
|
|
129
|
+
// Roadmap approval loop
|
|
130
|
+
let approved = false;
|
|
131
|
+
while (!approved) {
|
|
132
|
+
const reviewResponse = await askHuman(
|
|
133
|
+
`Please review the roadmap in state/roadmap.md\n\nOptions:\n- A: Approve roadmap as-is\n- B: Request changes (describe what to change)\n\nYour choice:`,
|
|
134
|
+
{ slug: 'roadmap-review' }
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (isApproval(reviewResponse)) {
|
|
138
|
+
approved = true;
|
|
139
|
+
memory.roadmapApproved = true;
|
|
140
|
+
console.log('Roadmap approved!\n');
|
|
141
|
+
} else {
|
|
142
|
+
// Regenerate roadmap with feedback
|
|
143
|
+
const updatedRoadmap = await safeAgent('roadmap-generator', {
|
|
144
|
+
projectDescription: memory.projectDescription,
|
|
145
|
+
scope: memory.scope,
|
|
146
|
+
requirements: memory.requirements,
|
|
147
|
+
assumptions: memory.assumptions,
|
|
148
|
+
security: memory.security,
|
|
149
|
+
feedback: reviewResponse
|
|
150
|
+
});
|
|
151
|
+
memory.roadmap = updatedRoadmap;
|
|
152
|
+
writeMarkdownFile(STATE_DIR, 'roadmap.md', renderRoadmapMarkdown(memory.roadmap));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ============================================
|
|
158
|
+
// PHASE 3: PHASE EXECUTION
|
|
159
|
+
// ============================================
|
|
160
|
+
console.log('=== PHASE 3: PHASE EXECUTION ===\n');
|
|
161
|
+
|
|
162
|
+
// Initialize phase tracking with proper undefined check
|
|
163
|
+
if (memory.currentPhaseIndex === undefined) {
|
|
164
|
+
memory.currentPhaseIndex = 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const phases = memory.roadmap?.phases || [];
|
|
168
|
+
|
|
169
|
+
// Sequential phase processing
|
|
170
|
+
for (let i = memory.currentPhaseIndex; i < phases.length; i++) {
|
|
171
|
+
memory.currentPhaseIndex = i;
|
|
172
|
+
const phase = phases[i];
|
|
173
|
+
console.log(`\n--- Processing Phase ${i + 1}: ${phase.title} ---\n`);
|
|
174
|
+
|
|
175
|
+
const tasksKey = `phase_${i}_tasks`;
|
|
176
|
+
const tasksApprovedKey = `phase_${i}_tasks_approved`;
|
|
177
|
+
|
|
178
|
+
// Generate task list for this phase (as JSON)
|
|
179
|
+
if (!memory[tasksApprovedKey]) {
|
|
180
|
+
if (!memory[tasksKey]) {
|
|
181
|
+
const taskResult = await safeAgent('task-planner', {
|
|
182
|
+
projectDescription: memory.projectDescription,
|
|
183
|
+
scope: memory.scope,
|
|
184
|
+
requirements: memory.requirements,
|
|
185
|
+
phase: phase,
|
|
186
|
+
phaseIndex: i + 1
|
|
187
|
+
});
|
|
188
|
+
memory[tasksKey] = taskResult;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
writeMarkdownFile(STATE_DIR, `phase-${i + 1}-tasks.md`, renderTasksMarkdown(i + 1, phase.title, memory[tasksKey]?.tasks || memory[tasksKey]));
|
|
192
|
+
|
|
193
|
+
// Task list approval loop
|
|
194
|
+
let tasksApproved = false;
|
|
195
|
+
while (!tasksApproved) {
|
|
196
|
+
const taskReview = await askHuman(
|
|
197
|
+
`Please review the task list for Phase ${i + 1} in state/phase-${i + 1}-tasks.md\n\nOptions:\n- A: Approve task list\n- B: Request changes (describe what to change)\n\nYour choice:`,
|
|
198
|
+
{ slug: `phase-${i + 1}-task-review` }
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (isApproval(taskReview)) {
|
|
202
|
+
tasksApproved = true;
|
|
203
|
+
memory[tasksApprovedKey] = true;
|
|
204
|
+
console.log(`Phase ${i + 1} task list approved!\n`);
|
|
205
|
+
} else {
|
|
206
|
+
const updatedTasks = await safeAgent('task-planner', {
|
|
207
|
+
projectDescription: memory.projectDescription,
|
|
208
|
+
scope: memory.scope,
|
|
209
|
+
requirements: memory.requirements,
|
|
210
|
+
phase: phase,
|
|
211
|
+
phaseIndex: i + 1,
|
|
212
|
+
feedback: taskReview
|
|
213
|
+
});
|
|
214
|
+
memory[tasksKey] = updatedTasks;
|
|
215
|
+
writeMarkdownFile(STATE_DIR, `phase-${i + 1}-tasks.md`, renderTasksMarkdown(i + 1, phase.title, memory[tasksKey]?.tasks || memory[tasksKey]));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ============================================
|
|
221
|
+
// TASK LIFECYCLE WITH IDEMPOTENCY
|
|
222
|
+
// ============================================
|
|
223
|
+
console.log(`\n=== TASK LIFECYCLE: Phase ${i + 1} ===\n`);
|
|
224
|
+
|
|
225
|
+
const tasks = memory[tasksKey]?.tasks || memory[tasksKey] || [];
|
|
226
|
+
const taskIndexKey = `phase_${i}_task_index`;
|
|
227
|
+
|
|
228
|
+
// Fix: use undefined check instead of falsy check
|
|
229
|
+
if (memory[taskIndexKey] === undefined) {
|
|
230
|
+
memory[taskIndexKey] = 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Process each task with optimal agent ordering and idempotency
|
|
234
|
+
for (let t = memory[taskIndexKey]; t < tasks.length; t++) {
|
|
235
|
+
memory[taskIndexKey] = t;
|
|
236
|
+
const task = tasks[t];
|
|
237
|
+
const taskId = task.id || t;
|
|
238
|
+
|
|
239
|
+
console.log(`\n Task ${t + 1}/${tasks.length}: ${task.title}\n`);
|
|
240
|
+
|
|
241
|
+
// Get current stage for this task
|
|
242
|
+
let stage = getTaskStage(i, taskId);
|
|
243
|
+
|
|
244
|
+
// Store any feedback for this task
|
|
245
|
+
const feedback = getTaskData(i, taskId, 'feedback');
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
// 1. Security Review (pre-implementation)
|
|
249
|
+
if (stage === TASK_STAGES.PENDING || stage === TASK_STAGES.SECURITY_PRE) {
|
|
250
|
+
if (!getTaskData(i, taskId, 'security_pre')) {
|
|
251
|
+
console.log(' > Security pre-review...');
|
|
252
|
+
const securityPreReview = await safeAgent('security-reviewer', {
|
|
253
|
+
task: task,
|
|
254
|
+
phase: phase,
|
|
255
|
+
scope: memory.scope,
|
|
256
|
+
stage: 'pre-implementation',
|
|
257
|
+
feedback: feedback
|
|
258
|
+
});
|
|
259
|
+
setTaskData(i, taskId, 'security_pre', securityPreReview);
|
|
260
|
+
}
|
|
261
|
+
setTaskStage(i, taskId, TASK_STAGES.TEST_PLANNING);
|
|
262
|
+
stage = TASK_STAGES.TEST_PLANNING;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 2. Test Planning
|
|
266
|
+
if (stage === TASK_STAGES.TEST_PLANNING) {
|
|
267
|
+
if (!getTaskData(i, taskId, 'tests')) {
|
|
268
|
+
console.log(' > Test planning...');
|
|
269
|
+
const testPlan = await safeAgent('test-planner', {
|
|
270
|
+
task: task,
|
|
271
|
+
phase: phase,
|
|
272
|
+
requirements: memory.requirements,
|
|
273
|
+
securityConsiderations: getTaskData(i, taskId, 'security_pre'),
|
|
274
|
+
feedback: feedback
|
|
275
|
+
});
|
|
276
|
+
setTaskData(i, taskId, 'tests', testPlan);
|
|
277
|
+
}
|
|
278
|
+
setTaskStage(i, taskId, TASK_STAGES.IMPLEMENTING);
|
|
279
|
+
stage = TASK_STAGES.IMPLEMENTING;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 3. Code Writing
|
|
283
|
+
if (stage === TASK_STAGES.IMPLEMENTING) {
|
|
284
|
+
if (!getTaskData(i, taskId, 'code')) {
|
|
285
|
+
console.log(' > Code implementation...');
|
|
286
|
+
const implementation = await safeAgent('code-writer', {
|
|
287
|
+
task: task,
|
|
288
|
+
phase: phase,
|
|
289
|
+
requirements: memory.requirements,
|
|
290
|
+
testPlan: getTaskData(i, taskId, 'tests'),
|
|
291
|
+
securityConsiderations: getTaskData(i, taskId, 'security_pre'),
|
|
292
|
+
feedback: feedback
|
|
293
|
+
});
|
|
294
|
+
setTaskData(i, taskId, 'code', implementation);
|
|
295
|
+
}
|
|
296
|
+
setTaskStage(i, taskId, TASK_STAGES.CODE_REVIEW);
|
|
297
|
+
stage = TASK_STAGES.CODE_REVIEW;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 4. Code Review
|
|
301
|
+
if (stage === TASK_STAGES.CODE_REVIEW) {
|
|
302
|
+
if (!getTaskData(i, taskId, 'review')) {
|
|
303
|
+
console.log(' > Code review...');
|
|
304
|
+
const codeReview = await safeAgent('code-reviewer', {
|
|
305
|
+
task: task,
|
|
306
|
+
implementation: getTaskData(i, taskId, 'code'),
|
|
307
|
+
testPlan: getTaskData(i, taskId, 'tests'),
|
|
308
|
+
feedback: feedback
|
|
309
|
+
});
|
|
310
|
+
setTaskData(i, taskId, 'review', codeReview);
|
|
311
|
+
}
|
|
312
|
+
setTaskStage(i, taskId, TASK_STAGES.SECURITY_POST);
|
|
313
|
+
stage = TASK_STAGES.SECURITY_POST;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 5. Final Security Check
|
|
317
|
+
if (stage === TASK_STAGES.SECURITY_POST) {
|
|
318
|
+
if (!getTaskData(i, taskId, 'security_post')) {
|
|
319
|
+
console.log(' > Final security check...');
|
|
320
|
+
const securityPostReview = await safeAgent('security-reviewer', {
|
|
321
|
+
task: task,
|
|
322
|
+
phase: phase,
|
|
323
|
+
implementation: getTaskData(i, taskId, 'code'),
|
|
324
|
+
stage: 'post-implementation',
|
|
325
|
+
feedback: feedback
|
|
326
|
+
});
|
|
327
|
+
setTaskData(i, taskId, 'security_post', securityPostReview);
|
|
328
|
+
}
|
|
329
|
+
setTaskStage(i, taskId, TASK_STAGES.AWAITING_APPROVAL);
|
|
330
|
+
stage = TASK_STAGES.AWAITING_APPROVAL;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 6. Sanity check with user
|
|
334
|
+
if (stage === TASK_STAGES.AWAITING_APPROVAL) {
|
|
335
|
+
const sanityCheck = await askHuman(
|
|
336
|
+
`Task ${t + 1} (${task.title}) complete.\n\nDefinition of Done: ${task.doneDefinition || 'Task completed successfully'}\n\nSanity Check: ${task.sanityCheck || 'Review the implementation and confirm it meets requirements.'}\n\nOptions:\n- A: Confirm task completion\n- B: Flag issue (describe the problem)\n\nYour response:`,
|
|
337
|
+
{ slug: `phase-${i + 1}-task-${taskId}-sanity` }
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
if (isApproval(sanityCheck)) {
|
|
341
|
+
// Mark task complete
|
|
342
|
+
setTaskStage(i, taskId, TASK_STAGES.COMPLETED);
|
|
343
|
+
task.stage = 'completed';
|
|
344
|
+
memory[tasksKey] = tasks; // Persist updated tasks
|
|
345
|
+
writeMarkdownFile(STATE_DIR, `phase-${i + 1}-tasks.md`, renderTasksMarkdown(i + 1, phase.title, tasks));
|
|
346
|
+
console.log(` Task ${t + 1} confirmed complete!\n`);
|
|
347
|
+
} else {
|
|
348
|
+
// Store feedback and reset task for reprocessing
|
|
349
|
+
console.log(' > Issue flagged, reprocessing task with feedback...');
|
|
350
|
+
setTaskData(i, taskId, 'feedback', sanityCheck);
|
|
351
|
+
|
|
352
|
+
// Clear previous outputs to force regeneration
|
|
353
|
+
setTaskData(i, taskId, 'security_pre', null);
|
|
354
|
+
setTaskData(i, taskId, 'tests', null);
|
|
355
|
+
setTaskData(i, taskId, 'code', null);
|
|
356
|
+
setTaskData(i, taskId, 'review', null);
|
|
357
|
+
setTaskData(i, taskId, 'security_post', null);
|
|
358
|
+
|
|
359
|
+
// Reset to pending and reprocess same task
|
|
360
|
+
setTaskStage(i, taskId, TASK_STAGES.PENDING);
|
|
361
|
+
t--; // Reprocess this task
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.error(` Task ${t + 1} failed: ${error.message}`);
|
|
367
|
+
setTaskStage(i, taskId, TASK_STAGES.FAILED);
|
|
368
|
+
|
|
369
|
+
const retry = await askHuman(
|
|
370
|
+
`Task "${task.title}" failed with error: ${error.message}\n\nOptions:\n- A: Retry this task\n- B: Skip and continue\n- C: Abort workflow\n\nYour choice:`,
|
|
371
|
+
{ slug: `phase-${i + 1}-task-${taskId}-error` }
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const retryTrimmed = retry.trim().toLowerCase();
|
|
375
|
+
if (retryTrimmed.startsWith('a') || retryTrimmed.startsWith('retry')) {
|
|
376
|
+
setTaskStage(i, taskId, TASK_STAGES.PENDING);
|
|
377
|
+
t--; // Retry this task
|
|
378
|
+
} else if (retryTrimmed.startsWith('c') || retryTrimmed.startsWith('abort')) {
|
|
379
|
+
throw new Error('Workflow aborted by user');
|
|
380
|
+
}
|
|
381
|
+
// Otherwise skip and continue to next task
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Mark phase complete in roadmap
|
|
386
|
+
phase.completed = true;
|
|
387
|
+
memory.roadmap.phases[i] = phase;
|
|
388
|
+
writeMarkdownFile(STATE_DIR, 'roadmap.md', renderRoadmapMarkdown(memory.roadmap));
|
|
389
|
+
console.log(`\nPhase ${i + 1} completed!\n`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
console.log('\n=== PROJECT BUILD COMPLETE ===\n');
|
|
393
|
+
memory.projectComplete = true;
|
|
394
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# __WORKFLOW_NAME__
|
|
2
|
+
|
|
3
|
+
A workflow created with agent-state-machine (native JS format).
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
__WORKFLOW_NAME__/
|
|
9
|
+
├── workflow.js # Native JS workflow (async/await)
|
|
10
|
+
├── config.js # Model/API key configuration
|
|
11
|
+
├── agents/ # Custom agents (.js/.mjs/.cjs or .md)
|
|
12
|
+
├── interactions/ # Human-in-the-loop inputs (created at runtime)
|
|
13
|
+
├── state/ # Runtime state (current.json, history.jsonl)
|
|
14
|
+
└── steering/ # Steering configuration
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Edit `config.js` to set models and API keys for this workflow.
|
|
20
|
+
|
|
21
|
+
Run the workflow (or resume if interrupted):
|
|
22
|
+
```bash
|
|
23
|
+
state-machine run __WORKFLOW_NAME__
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Check status:
|
|
27
|
+
```bash
|
|
28
|
+
state-machine status __WORKFLOW_NAME__
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
View history:
|
|
32
|
+
```bash
|
|
33
|
+
state-machine history __WORKFLOW_NAME__
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
View trace logs in browser with live updates:
|
|
37
|
+
```bash
|
|
38
|
+
state-machine follow __WORKFLOW_NAME__
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Reset state (clears memory/state):
|
|
42
|
+
```bash
|
|
43
|
+
state-machine reset __WORKFLOW_NAME__
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Hard reset (clears everything: history/interactions/memory):
|
|
47
|
+
```bash
|
|
48
|
+
state-machine reset-hard __WORKFLOW_NAME__
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Writing Workflows
|
|
52
|
+
|
|
53
|
+
Edit `workflow.js` - write normal async JavaScript:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
import { agent, memory, askHuman, parallel } from 'agent-state-machine';
|
|
57
|
+
|
|
58
|
+
export default async function() {
|
|
59
|
+
console.log('Starting __WORKFLOW_NAME__ workflow...');
|
|
60
|
+
|
|
61
|
+
// Example: Get user input (saved to memory)
|
|
62
|
+
const userLocation = await askHuman('Where do you live?');
|
|
63
|
+
console.log('Example prompt answer:', userLocation);
|
|
64
|
+
|
|
65
|
+
const userInfo = await agent('yoda-name-collector');
|
|
66
|
+
memory.userInfo = userInfo;
|
|
67
|
+
|
|
68
|
+
// Provide context
|
|
69
|
+
// const userInfo = await agent('yoda-name-collector', { name: 'Luke' });
|
|
70
|
+
|
|
71
|
+
console.log('Example agent memory.userInfo:', memory.userInfo || userInfo);
|
|
72
|
+
|
|
73
|
+
// Context is explicit: pass what the agent needs
|
|
74
|
+
const { greeting } = await agent('yoda-greeter', { userLocation, memory });
|
|
75
|
+
console.log('Example agent greeting:', greeting);
|
|
76
|
+
|
|
77
|
+
// Or you can provide context manually
|
|
78
|
+
// await agent('yoda-greeter', userInfo);
|
|
79
|
+
|
|
80
|
+
// Example: Parallel execution
|
|
81
|
+
// const [a, b, c] = await parallel([
|
|
82
|
+
// agent('yoda-greeter', { name: 'the names augustus but friends call me gus' }),
|
|
83
|
+
// agent('yoda-greeter', { name: 'uriah' }),
|
|
84
|
+
// agent('yoda-greeter', { name: 'lucas' })
|
|
85
|
+
// ]);
|
|
86
|
+
|
|
87
|
+
// console.log('a: ' + JSON.stringify(a))
|
|
88
|
+
// console.log('b: ' + JSON.stringify(b))
|
|
89
|
+
// console.log('c: ' + JSON.stringify(c))
|
|
90
|
+
|
|
91
|
+
notify(['__WORKFLOW_NAME__', userInfo.name || userInfo + ' has been greeted!']);
|
|
92
|
+
|
|
93
|
+
console.log('Workflow completed!');
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Creating Agents
|
|
98
|
+
|
|
99
|
+
**JavaScript agent** (`agents/my-agent.js`):
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
import { llm } from 'agent-state-machine';
|
|
103
|
+
|
|
104
|
+
export default async function handler(context) {
|
|
105
|
+
const response = await llm(context, { model: 'smart', prompt: 'Hello!' });
|
|
106
|
+
return { greeting: response.text };
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Markdown agent** (`agents/greeter.md`):
|
|
111
|
+
|
|
112
|
+
```md
|
|
113
|
+
---
|
|
114
|
+
model: fast
|
|
115
|
+
output: greeting
|
|
116
|
+
---
|
|
117
|
+
Generate a greeting for {{name}}.
|
|
118
|
+
```
|
|
@@ -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`.
|