agent-state-machine 1.0.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/lib/setup.js ADDED
@@ -0,0 +1,398 @@
1
+ /**
2
+ * File: /lib/setup.js
3
+ */
4
+
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+
8
+ /**
9
+ * Setup a new workflow with directory structure
10
+ */
11
+ async function setup(workflowName) {
12
+ const workflowsDir = path.join(process.cwd(), 'workflows');
13
+ const workflowDir = path.join(workflowsDir, workflowName);
14
+
15
+ // Check if workflow already exists
16
+ if (fs.existsSync(workflowDir)) {
17
+ console.error(`Error: Workflow '${workflowName}' already exists at ${workflowDir}`);
18
+ process.exit(1);
19
+ }
20
+
21
+ console.log(`\nCreating workflow: ${workflowName}`);
22
+ console.log('─'.repeat(40));
23
+
24
+ // Create directory structure (native JS workflow only)
25
+ const dirs = [
26
+ workflowDir,
27
+ path.join(workflowDir, 'agents'),
28
+ path.join(workflowDir, 'interactions'),
29
+ path.join(workflowDir, 'state'),
30
+ path.join(workflowDir, 'steering'),
31
+ path.join(workflowDir, 'scripts')
32
+ ];
33
+
34
+ dirs.forEach((dir) => {
35
+ fs.mkdirSync(dir, { recursive: true });
36
+ console.log(` Created: ${path.relative(process.cwd(), dir)}/`);
37
+ });
38
+
39
+ // Ensure this workflow folder is ESM (so workflow.js + agents/*.js can use import/export)
40
+ // const workflowPkg = {
41
+ // name: `workflow-${workflowName}`,
42
+ // private: true,
43
+ // type: 'module'
44
+ // };
45
+ // const workflowPkgFile = path.join(workflowDir, 'package.json');
46
+ // fs.writeFileSync(workflowPkgFile, JSON.stringify(workflowPkg, null, 2));
47
+ // console.log(` Created: ${path.relative(process.cwd(), workflowPkgFile)}`);
48
+
49
+ // Create workflow.js (native JS format)
50
+ const workflowJs = `/**
51
+ /**
52
+ * ${workflowName} Workflow
53
+ *
54
+ * Native JavaScript workflow - write normal async/await code!
55
+ *
56
+ * Features:
57
+ * - memory object auto-persists to disk (use memory guards for idempotency)
58
+ * - Use standard JS control flow (if, for, etc.)
59
+ * - Interactive prompts pause and wait for user input
60
+ */
61
+
62
+ import { agent, memory, initialPrompt, parallel } from 'agent-state-machine';
63
+ import { notify } from './scripts/mac-notification.js';
64
+
65
+ // Model configuration (also supports models in a separate config export)
66
+ export const config = {
67
+ models: {
68
+ low: "gemini",
69
+ med: "codex --model gpt-5.2",
70
+ high: "claude -m claude-opus-4-20250514 -p",
71
+ },
72
+ apiKeys: {
73
+ gemini: process.env.GEMINI_API_KEY,
74
+ anthropic: process.env.ANTHROPIC_API_KEY,
75
+ openai: process.env.OPENAI_API_KEY,
76
+ }
77
+ };
78
+
79
+ export default async function() {
80
+ console.log('Starting ${workflowName} workflow...');
81
+
82
+ // Example: Get user input (saved to memory)
83
+ const answer = await initialPrompt('Where do you live?');
84
+ console.log('Example prompt answer:', answer);
85
+
86
+ const userInfo = await agent('yoda-name-collector');
87
+ memory.userInfo = userInfo;
88
+
89
+ // Provide context
90
+ // const userInfo = await agent('yoda-name-collector', { name: 'Luke' });
91
+
92
+ console.log('Example agent memory.userInfo:', memory.userInfo || userInfo);
93
+
94
+ // Context is provided automatically
95
+ const { greeting } = await agent('yoda-greeter');
96
+ console.log('Example agent greeting:', greeting);
97
+
98
+ // Or you can provide context manually
99
+ // await agent('yoda-greeter', userInfo);
100
+
101
+ // Example: Parallel execution
102
+ // const [a, b] = await parallel([
103
+ // agent('example', { which: 'a' }),
104
+ // agent('example', { which: 'b' })
105
+ // ]);
106
+
107
+ notify(['${workflowName}', userInfo.name || userInfo + ' has been greeted!']);
108
+
109
+ console.log('Workflow completed!');
110
+ }
111
+ `;
112
+
113
+ const workflowFile = path.join(workflowDir, 'workflow.js');
114
+ fs.writeFileSync(workflowFile, workflowJs);
115
+ console.log(` Created: ${path.relative(process.cwd(), workflowFile)}`);
116
+
117
+ // Create example JS agent (ESM)
118
+ // Create example JS agent (ESM)
119
+ const exampleAgent = `/**
120
+ * Example Agent for ${workflowName}
121
+ *
122
+ * Agents are async functions that receive a context object and return a result.
123
+ * - Context includes: persisted memory (spread), params, _steering, _config
124
+ */
125
+
126
+ import { llm } from 'agent-state-machine';
127
+
128
+ export default async function handler(context) {
129
+ console.log('[Agent: example] Processing...');
130
+
131
+ // Access global steering prompt if available
132
+ if (context._steering?.global) {
133
+ console.log('[Agent: example] Steering loaded (' + context._steering.global.length + ' chars)');
134
+ }
135
+
136
+ // Example: Call an LLM (configure models in workflow.js)
137
+ // const response = await llm(context, {
138
+ // model: 'smart',
139
+ // prompt: 'Say hello and describe what you can help with.'
140
+ // });
141
+ // console.log('[Agent: example] LLM response:', response.text);
142
+
143
+ return {
144
+ ok: true,
145
+ received: Object.keys(context).filter((k) => !String(k).startsWith('_')),
146
+ processedAt: new Date().toISOString()
147
+ };
148
+ }
149
+
150
+ export const meta = {
151
+ name: 'example',
152
+ description: 'An example agent to get you started',
153
+ version: '1.0.0'
154
+ };
155
+ `;
156
+
157
+ const agentFile = path.join(workflowDir, 'agents', 'example.js');
158
+ fs.writeFileSync(agentFile, exampleAgent);
159
+ console.log(` Created: ${path.relative(process.cwd(), agentFile)}`);
160
+
161
+ // Create example markdown agent
162
+ const yodaGreeterAgent = `---
163
+ model: low
164
+ output: greeting
165
+ ---
166
+
167
+ # Greeting Task
168
+
169
+ Generate a friendly greeting for {{name}} in a yoda style. Prompt user for their actual {{name}} if you dont have it.
170
+
171
+ Once you have it create a yoda-greeting.md file in root dir with the greeting.
172
+
173
+ 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.
174
+ `;
175
+
176
+ const yodaNameCollectorAgent = `---
177
+ model: low
178
+ output: name
179
+ ---
180
+
181
+ # Name Collection Task
182
+
183
+ Ask for users name in a yoda style. Unless you have it already.
184
+
185
+ Keep it brief and warm.
186
+
187
+ 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.
188
+ `;
189
+
190
+ const yodaNameCollectorAgentFile = path.join(workflowDir, 'agents', 'yoda-name-collector.md');
191
+ fs.writeFileSync(yodaNameCollectorAgentFile, yodaNameCollectorAgent);
192
+
193
+ const yodaGreeterFile = path.join(workflowDir, 'agents', 'yoda-greeter.md');
194
+ fs.writeFileSync(yodaGreeterFile, yodaGreeterAgent);
195
+
196
+ console.log(` Created: ${path.relative(process.cwd(), yodaGreeterFile)}`);
197
+ console.log(` Created: ${path.relative(process.cwd(), yodaNameCollectorAgentFile)}`);
198
+
199
+ // Create initial state (native format)
200
+ const initialState = {
201
+ format: 'native',
202
+ status: 'IDLE',
203
+ memory: {},
204
+ _pendingInteraction: null,
205
+ _error: null,
206
+ startedAt: null,
207
+ lastUpdatedAt: new Date().toISOString()
208
+ };
209
+ const stateFile = path.join(workflowDir, 'state', 'current.json');
210
+ fs.writeFileSync(stateFile, JSON.stringify(initialState, null, 2));
211
+ console.log(` Created: ${path.relative(process.cwd(), stateFile)}`);
212
+
213
+ // Create empty history file
214
+ const historyFile = path.join(workflowDir, 'state', 'history.jsonl');
215
+ fs.writeFileSync(historyFile, '');
216
+ console.log(` Created: ${path.relative(process.cwd(), historyFile)}`);
217
+
218
+ // Create steering config
219
+ const steeringConfig = {
220
+ _comment: 'Steering configuration',
221
+ enabled: true,
222
+ globalPrompt: 'global.md'
223
+ };
224
+ const steeringFile = path.join(workflowDir, 'steering', 'config.json');
225
+ fs.writeFileSync(steeringFile, JSON.stringify(steeringConfig, null, 2));
226
+ console.log(` Created: ${path.relative(process.cwd(), steeringFile)}`);
227
+
228
+ // Create global.md steering prompt
229
+ const globalMd = `# Global Steering Prompt
230
+
231
+ This content is included with every agent execution in the ${workflowName} workflow.
232
+
233
+ ## Guidelines
234
+
235
+ - Process data carefully and validate inputs
236
+ - Return well-structured JSON when applicable
237
+ - Log meaningful progress messages
238
+ - Handle errors gracefully
239
+
240
+ ## Notes
241
+
242
+ This file is automatically loaded and passed to every agent in the workflow via \`context._steering.global\`.
243
+ `;
244
+ const globalMdFile = path.join(workflowDir, 'steering', 'global.md');
245
+ fs.writeFileSync(globalMdFile, globalMd);
246
+ console.log(` Created: ${path.relative(process.cwd(), globalMdFile)}`);
247
+
248
+ // Create mac-notification.js script
249
+ const macNotificationScript = `"use strict";
250
+
251
+ import { spawnSync } from "node:child_process";
252
+ import { existsSync } from "node:fs";
253
+
254
+ function escAppleScript(s) {
255
+ return String(s).replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"');
256
+ }
257
+
258
+ function notify(title = "Notification", message = "Everything finished!") {
259
+ const script = \`display notification "\${escAppleScript(message)}" with title "\${escAppleScript(title)}"\`;
260
+ spawnSync("osascript", ["-e", script], { stdio: "ignore" });
261
+
262
+ const soundPath = "/System/Library/Sounds/Glass.aiff";
263
+ const fallbackPath = "/System/Library/Sounds/Ping.aiff";
264
+
265
+ if (existsSync(soundPath)) {
266
+ spawnSync("afplay", [soundPath], { stdio: "ignore" });
267
+ } else if (existsSync(fallbackPath)) {
268
+ spawnSync("afplay", [fallbackPath], { stdio: "ignore" });
269
+ }
270
+ }
271
+
272
+ export { notify };
273
+ `;
274
+ const notificationFile = path.join(workflowDir, 'scripts', 'mac-notification.js');
275
+ fs.writeFileSync(notificationFile, macNotificationScript);
276
+ console.log(` Created: ${path.relative(process.cwd(), notificationFile)}`);
277
+
278
+ // Create README
279
+ const readme = `# ${workflowName}
280
+
281
+ A workflow created with agent-state-machine (native JS format).
282
+
283
+ ## Structure
284
+
285
+ \\\`\\\`\\\`
286
+ ${workflowName}/
287
+ ├── workflow.js # Native JS workflow (async/await)
288
+ ├── package.json # Sets "type": "module" for this workflow folder
289
+ ├── agents/ # Custom agents (.js/.mjs/.cjs or .md)
290
+ ├── interactions/ # Human-in-the-loop inputs (created at runtime)
291
+ ├── state/ # Runtime state (current.json, history.jsonl)
292
+ └── steering/ # Steering configuration
293
+ \\\`\\\`\\\`
294
+
295
+ ## Usage
296
+
297
+ Run the workflow:
298
+ \\\`\\\`\\\`bash
299
+ state-machine run ${workflowName}
300
+ \\\`\\\`\\\`
301
+
302
+ Resume a paused workflow:
303
+ \\\`\\\`\\\`bash
304
+ state-machine resume ${workflowName}
305
+ \\\`\\\`\\\`
306
+
307
+ Check status:
308
+ \\\`\\\`\\\`bash
309
+ state-machine status ${workflowName}
310
+ \\\`\\\`\\\`
311
+
312
+ View history:
313
+ \\\`\\\`\\\`bash
314
+ state-machine history ${workflowName}
315
+ \\\`\\\`\\\`
316
+
317
+ Reset state:
318
+ \\\`\\\`\\\`bash
319
+ state-machine reset ${workflowName}
320
+ \\\`\\\`\\\`
321
+
322
+ ## Writing Workflows
323
+
324
+ Edit \`workflow.js\` - write normal async JavaScript:
325
+
326
+ \\\`\\\`\\\`js
327
+ import { agent, memory, initialPrompt, parallel } from 'agent-state-machine';
328
+
329
+ export default async function() {
330
+ console.log('Starting project-builder workflow...');
331
+
332
+ // Example: Get user input (saved to memory)
333
+ const answer = await initialPrompt('Where do you live?');
334
+ console.log('Example prompt answer:', answer);
335
+
336
+ const userInfo = await agent('yoda-name-collector');
337
+ memory.userInfo = userInfo;
338
+
339
+ // Provide context
340
+ // const userInfo = await agent('yoda-name-collector', { name: 'Luke' });
341
+
342
+ console.log('Example agent memory.userInfo:', memory.userInfo || userInfo);
343
+
344
+ // Context is provided automatically
345
+ const { greeting } = await agent('yoda-greeter');
346
+ console.log('Example agent greeting:', greeting);
347
+
348
+ // Or you can provide context manually
349
+ // await agent('yoda-greeter', userInfo);
350
+
351
+ // Example: Parallel execution
352
+ // const [a, b] = await parallel([
353
+ // agent('example', { which: 'a' }),
354
+ // agent('example', { which: 'b' })
355
+ // ]);
356
+
357
+ notify(['project-builder', userInfo.name || userInfo + ' has been greeted!']);
358
+
359
+ console.log('Workflow completed!');
360
+ }
361
+ \\\`\\\`\\\`
362
+
363
+ ## Creating Agents
364
+
365
+ **JavaScript agent** (\`agents/my-agent.js\`):
366
+
367
+ \\\`\\\`\\\`js
368
+ import { llm } from 'agent-state-machine';
369
+
370
+ export default async function handler(context) {
371
+ const response = await llm(context, { model: 'smart', prompt: 'Hello!' });
372
+ return { greeting: response.text };
373
+ }
374
+ \\\`\\\`\\\`
375
+
376
+ **Markdown agent** (\`agents/greeter.md\`):
377
+
378
+ \\\`\\\`\\\`md
379
+ ---
380
+ model: fast
381
+ output: greeting
382
+ ---
383
+ Generate a greeting for {{name}}.
384
+ \\\`\\\`\\\`
385
+ `;
386
+ const readmeFile = path.join(workflowDir, 'README.md');
387
+ fs.writeFileSync(readmeFile, readme);
388
+ console.log(` Created: ${path.relative(process.cwd(), readmeFile)}`);
389
+
390
+ console.log('─'.repeat(40));
391
+ console.log(`\n✓ Workflow '${workflowName}' created successfully!\n`);
392
+ console.log('Next steps:');
393
+ console.log(` 1. Edit workflows/${workflowName}/workflow.js to implement your flow`);
394
+ console.log(` 2. Add custom agents in workflows/${workflowName}/agents/`);
395
+ console.log(` 3. Run: state-machine run ${workflowName}\n`);
396
+ }
397
+
398
+ export { setup };