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/README.md +299 -0
- package/bin/cli.js +246 -0
- package/lib/index.js +110 -0
- package/lib/index.mjs +9 -0
- package/lib/llm.js +472 -0
- package/lib/runtime/agent.js +359 -0
- package/lib/runtime/index.js +35 -0
- package/lib/runtime/memory.js +88 -0
- package/lib/runtime/parallel.js +66 -0
- package/lib/runtime/prompt.js +118 -0
- package/lib/runtime/runtime.js +387 -0
- package/lib/setup.js +398 -0
- package/lib/state-machine.js +1359 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# agent-state-machine
|
|
2
|
+
|
|
3
|
+
A workflow runner for building **linear, stateful agent workflows** in plain JavaScript.
|
|
4
|
+
|
|
5
|
+
You write normal `async/await` code. The runtime handles:
|
|
6
|
+
- **Auto-persisted** `memory` (saved to disk on mutation)
|
|
7
|
+
- **Human-in-the-loop** blocking via `initialPrompt()` or agent-driven interactions
|
|
8
|
+
- Local **JS agents** + **Markdown agents** (LLM-powered)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i agent-state-machine
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Global CLI:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm i -g agent-state-machine
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requirements: Node.js >= 16.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## CLI
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
state-machine --setup <workflow-name>
|
|
32
|
+
state-machine run <workflow-name>
|
|
33
|
+
state-machine resume <workflow-name>
|
|
34
|
+
state-machine status <workflow-name>
|
|
35
|
+
state-machine history <workflow-name> [limit]
|
|
36
|
+
state-machine reset <workflow-name>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Workflows live in:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
workflows/<name>/
|
|
43
|
+
├── workflow.js # Native JS workflow (async/await)
|
|
44
|
+
├── package.json # Sets "type": "module" for this workflow folder
|
|
45
|
+
├── agents/ # Custom agents (.js/.mjs/.cjs or .md)
|
|
46
|
+
├── interactions/ # Human-in-the-loop files (auto-created)
|
|
47
|
+
├── state/ # current.json, history.jsonl, generated-prompt.md
|
|
48
|
+
└── steering/ # global.md + config.json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Writing workflows (native JS)
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
/**
|
|
57
|
+
/**
|
|
58
|
+
* project-builder Workflow
|
|
59
|
+
*
|
|
60
|
+
* Native JavaScript workflow - write normal async/await code!
|
|
61
|
+
*
|
|
62
|
+
* Features:
|
|
63
|
+
* - memory object auto-persists to disk (use memory guards for idempotency)
|
|
64
|
+
* - Use standard JS control flow (if, for, etc.)
|
|
65
|
+
* - Interactive prompts pause and wait for user input
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
import { agent, memory, initialPrompt, parallel } from 'agent-state-machine';
|
|
69
|
+
import { notify } from './scripts/mac-notification.js';
|
|
70
|
+
|
|
71
|
+
// Model configuration (also supports models in a separate config export)
|
|
72
|
+
export const config = {
|
|
73
|
+
models: {
|
|
74
|
+
low: "gemini",
|
|
75
|
+
med: "codex --model gpt-5.2",
|
|
76
|
+
high: "claude -m claude-opus-4-20250514 -p",
|
|
77
|
+
},
|
|
78
|
+
apiKeys: {
|
|
79
|
+
gemini: process.env.GEMINI_API_KEY,
|
|
80
|
+
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
81
|
+
openai: process.env.OPENAI_API_KEY,
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default async function() {
|
|
86
|
+
console.log('Starting project-builder workflow...');
|
|
87
|
+
|
|
88
|
+
// Example: Get user input (saved to memory)
|
|
89
|
+
const answer = await initialPrompt('Where do you live?');
|
|
90
|
+
console.log('Example prompt answer:', answer);
|
|
91
|
+
|
|
92
|
+
const userInfo = await agent('yoda-name-collector');
|
|
93
|
+
memory.userInfo = userInfo;
|
|
94
|
+
|
|
95
|
+
// Provide context
|
|
96
|
+
// const userInfo = await agent('yoda-name-collector', { name: 'Luke' });
|
|
97
|
+
|
|
98
|
+
console.log('Example agent memory.userInfo:', memory.userInfo || userInfo);
|
|
99
|
+
|
|
100
|
+
// Context is provided automatically
|
|
101
|
+
const { greeting } = await agent('yoda-greeter');
|
|
102
|
+
console.log('Example agent greeting:', greeting);
|
|
103
|
+
|
|
104
|
+
// Or you can provide context manually
|
|
105
|
+
// await agent('yoda-greeter', userInfo);
|
|
106
|
+
|
|
107
|
+
// Example: Parallel execution
|
|
108
|
+
// const [a, b] = await parallel([
|
|
109
|
+
// agent('example', { which: 'a' }),
|
|
110
|
+
// agent('example', { which: 'b' })
|
|
111
|
+
// ]);
|
|
112
|
+
|
|
113
|
+
notify(['project-builder', userInfo.name || userInfo + ' has been greeted!']);
|
|
114
|
+
|
|
115
|
+
console.log('Workflow completed!');
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### How “resume” works
|
|
120
|
+
|
|
121
|
+
`resume` restarts your workflow from the top.
|
|
122
|
+
|
|
123
|
+
If the workflow needs human input, it will **block inline** in the terminal. You’ll be told which `interactions/<slug>.md` file to edit; after you fill it in, press `y` in the same terminal session to continue.
|
|
124
|
+
|
|
125
|
+
If the process is interrupted, running `state-machine resume <workflow-name>` will restart the execution. Use the `memory` object to store and skip work manually if needed.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Core API
|
|
130
|
+
|
|
131
|
+
### `agent(name, params?)`
|
|
132
|
+
|
|
133
|
+
Runs `workflows/<name>/agents/<agent>.(js|mjs|cjs)` or `<agent>.md`.
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
const out = await agent('review', { file: 'src/app.js' });
|
|
137
|
+
memory.lastReview = out;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `memory`
|
|
141
|
+
|
|
142
|
+
A persisted object for your workflow.
|
|
143
|
+
|
|
144
|
+
- Mutations auto-save to `workflows/<name>/state/current.json`.
|
|
145
|
+
- Use it as your “long-lived state” between runs.
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
memory.count = (memory.count || 0) + 1;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `initialPrompt(question, options?)`
|
|
152
|
+
|
|
153
|
+
Gets user input.
|
|
154
|
+
|
|
155
|
+
- In a TTY, it prompts in the terminal.
|
|
156
|
+
- Otherwise it creates `interactions/<slug>.md` and blocks until you confirm in the terminal.
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
const repo = await initialPrompt('What repo should I work on?', { slug: 'repo' });
|
|
160
|
+
memory.repo = repo;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### `parallel([...])` / `parallelLimit([...], limit)`
|
|
164
|
+
|
|
165
|
+
Run multiple `agent()` calls concurrently:
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
import { agent, parallel, parallelLimit } from 'agent-state-machine';
|
|
169
|
+
|
|
170
|
+
const [a, b] = await parallel([
|
|
171
|
+
agent('review', { file: 'src/a.js' }),
|
|
172
|
+
agent('review', { file: 'src/b.js' }),
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
const results = await parallelLimit(
|
|
176
|
+
['a.js', 'b.js', 'c.js'].map(f => agent('review', { file: f })),
|
|
177
|
+
2
|
|
178
|
+
);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Agents
|
|
184
|
+
|
|
185
|
+
Agents live in `workflows/<workflow>/agents/`.
|
|
186
|
+
|
|
187
|
+
### JavaScript agents
|
|
188
|
+
|
|
189
|
+
**ESM (`.js` / `.mjs`)**:
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
// workflows/<name>/agents/example.js
|
|
193
|
+
import { llm } from 'agent-state-machine';
|
|
194
|
+
|
|
195
|
+
export default async function handler(context) {
|
|
196
|
+
// context includes:
|
|
197
|
+
// - persisted memory (spread into the object)
|
|
198
|
+
// - params passed to agent(name, params)
|
|
199
|
+
// - context._steering (global steering prompt/config)
|
|
200
|
+
// - context._config (models/apiKeys/workflowDir)
|
|
201
|
+
return { ok: true };
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**CommonJS (`.cjs`)** (only if you prefer CJS):
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
// workflows/<name>/agents/example.cjs
|
|
209
|
+
async function handler(context) {
|
|
210
|
+
return { ok: true };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = handler;
|
|
214
|
+
module.exports.handler = handler;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
If you need to request human input from a JS agent, return an `_interaction` payload:
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
return {
|
|
221
|
+
_interaction: {
|
|
222
|
+
slug: 'approval',
|
|
223
|
+
targetKey: 'approval',
|
|
224
|
+
content: 'Please approve this change (yes/no).'
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The runtime will block execution and wait for your response in the terminal.
|
|
230
|
+
|
|
231
|
+
### Markdown agents (`.md`)
|
|
232
|
+
|
|
233
|
+
Markdown agents are LLM-backed prompt templates with optional frontmatter.
|
|
234
|
+
|
|
235
|
+
```md
|
|
236
|
+
---
|
|
237
|
+
model: smart
|
|
238
|
+
output: greeting
|
|
239
|
+
---
|
|
240
|
+
Generate a friendly greeting for {{name}}.
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Calling it:
|
|
244
|
+
|
|
245
|
+
```js
|
|
246
|
+
const { greeting } = await agent('greeter', { name: 'Sam' });
|
|
247
|
+
memory.greeting = greeting;
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Models & LLM execution
|
|
253
|
+
|
|
254
|
+
In your workflow’s `export const config = { models: { ... } }`, each model value can be:
|
|
255
|
+
|
|
256
|
+
### CLI command
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
export const config = {
|
|
260
|
+
models: {
|
|
261
|
+
smart: "claude -m claude-sonnet-4-20250514 -p"
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### API target
|
|
267
|
+
|
|
268
|
+
Format: `api:<provider>:<model>`
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
export const config = {
|
|
272
|
+
models: {
|
|
273
|
+
smart: "api:openai:gpt-4.1-mini"
|
|
274
|
+
},
|
|
275
|
+
apiKeys: {
|
|
276
|
+
openai: process.env.OPENAI_API_KEY
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The runtime writes the fully-built prompt to:
|
|
282
|
+
|
|
283
|
+
```text
|
|
284
|
+
workflows/<name>/state/generated-prompt.md
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## State & persistence
|
|
290
|
+
|
|
291
|
+
Native JS workflows persist to:
|
|
292
|
+
|
|
293
|
+
- `workflows/<name>/state/current.json` — status, memory, pending interaction
|
|
294
|
+
- `workflows/<name>/state/history.jsonl` — event log (newest entries first)
|
|
295
|
+
- `workflows/<name>/interactions/*.md` — human input files (when paused)
|
|
296
|
+
|
|
297
|
+
## License
|
|
298
|
+
|
|
299
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { pathToFileURL } from 'url';
|
|
6
|
+
import { WorkflowRuntime } from '../lib/index.js';
|
|
7
|
+
import { setup } from '../lib/setup.js';
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const command = args[0];
|
|
11
|
+
|
|
12
|
+
function printHelp() {
|
|
13
|
+
console.log(`
|
|
14
|
+
Agent State Machine CLI (Native JS Workflows Only)
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
state-machine --setup <workflow-name> Create a new workflow project
|
|
18
|
+
state-machine run <workflow-name> Run a workflow from the beginning
|
|
19
|
+
state-machine resume <workflow-name> Resume a paused workflow
|
|
20
|
+
state-machine status [workflow-name] Show current state (or list all)
|
|
21
|
+
state-machine history <workflow-name> [limit] Show execution history
|
|
22
|
+
state-machine reset <workflow-name> Reset workflow state
|
|
23
|
+
state-machine reset-hard <workflow-name> Hard reset (clear history/interactions)
|
|
24
|
+
state-machine list List all workflows
|
|
25
|
+
state-machine help Show this help
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--setup, -s Initialize a new workflow with directory structure
|
|
29
|
+
--help, -h Show help
|
|
30
|
+
|
|
31
|
+
Workflow Structure:
|
|
32
|
+
workflows/<name>/
|
|
33
|
+
├── workflow.js # Native JS workflow (async/await)
|
|
34
|
+
├── package.json # Sets "type": "module" for this workflow folder
|
|
35
|
+
├── agents/ # Custom agents (.js/.mjs/.cjs or .md)
|
|
36
|
+
├── interactions/ # Human-in-the-loop files (auto-created)
|
|
37
|
+
├── state/ # current.json, history.jsonl, generated-prompt.md
|
|
38
|
+
└── steering/ # global.md + config.json
|
|
39
|
+
`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function workflowsRoot() {
|
|
43
|
+
return path.join(process.cwd(), 'workflows');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveWorkflowDir(workflowName) {
|
|
47
|
+
return path.join(workflowsRoot(), workflowName);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveWorkflowEntry(workflowDir) {
|
|
51
|
+
const candidates = ['workflow.js', 'workflow.mjs'];
|
|
52
|
+
for (const f of candidates) {
|
|
53
|
+
const p = path.join(workflowDir, f);
|
|
54
|
+
if (fs.existsSync(p)) return p;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readState(workflowDir) {
|
|
60
|
+
const stateFile = path.join(workflowDir, 'state', 'current.json');
|
|
61
|
+
if (!fs.existsSync(stateFile)) return null;
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function summarizeStatus(state) {
|
|
70
|
+
if (!state) return ' [no state]';
|
|
71
|
+
|
|
72
|
+
const s = String(state.status || '').toUpperCase();
|
|
73
|
+
if (s === 'COMPLETED') return ' [completed]';
|
|
74
|
+
if (s === 'FAILED') return ' [failed - can resume]';
|
|
75
|
+
if (s === 'PAUSED') return ' [paused - can resume]';
|
|
76
|
+
if (s === 'RUNNING') return ' [running]';
|
|
77
|
+
if (s === 'IDLE') return ' [idle]';
|
|
78
|
+
return state.status ? ` [${state.status}]` : '';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function listWorkflows() {
|
|
82
|
+
const root = workflowsRoot();
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(root)) {
|
|
85
|
+
console.log('No workflows directory found.');
|
|
86
|
+
console.log('Run `state-machine --setup <name>` to create your first workflow.');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const workflows = fs
|
|
91
|
+
.readdirSync(root, { withFileTypes: true })
|
|
92
|
+
.filter((d) => d.isDirectory())
|
|
93
|
+
.map((d) => d.name)
|
|
94
|
+
.sort((a, b) => a.localeCompare(b));
|
|
95
|
+
|
|
96
|
+
if (workflows.length === 0) {
|
|
97
|
+
console.log('No workflows found.');
|
|
98
|
+
console.log('Run `state-machine --setup <name>` to create your first workflow.');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('\nAvailable Workflows:');
|
|
103
|
+
console.log('─'.repeat(40));
|
|
104
|
+
|
|
105
|
+
for (const name of workflows) {
|
|
106
|
+
const dir = resolveWorkflowDir(name);
|
|
107
|
+
const entry = resolveWorkflowEntry(dir);
|
|
108
|
+
const state = readState(dir);
|
|
109
|
+
|
|
110
|
+
const entryNote = entry ? '' : ' [missing workflow.js]';
|
|
111
|
+
const statusNote = summarizeStatus(state);
|
|
112
|
+
|
|
113
|
+
const pausedNote =
|
|
114
|
+
state && state._pendingInteraction && state._pendingInteraction.file
|
|
115
|
+
? ` [needs input: ${state._pendingInteraction.file}]`
|
|
116
|
+
: '';
|
|
117
|
+
|
|
118
|
+
console.log(` ${name}${entryNote}${statusNote}${pausedNote}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log('');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function runOrResume(workflowName) {
|
|
125
|
+
const workflowDir = resolveWorkflowDir(workflowName);
|
|
126
|
+
|
|
127
|
+
if (!fs.existsSync(workflowDir)) {
|
|
128
|
+
console.error(`Error: Workflow '${workflowName}' not found at ${workflowDir}`);
|
|
129
|
+
console.error(`Run: state-machine --setup ${workflowName}`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const entry = resolveWorkflowEntry(workflowDir);
|
|
134
|
+
if (!entry) {
|
|
135
|
+
console.error(`Error: No workflow entry found (expected workflow.js or workflow.mjs) in ${workflowDir}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const runtime = new WorkflowRuntime(workflowDir);
|
|
140
|
+
const workflowUrl = pathToFileURL(entry).href;
|
|
141
|
+
|
|
142
|
+
await runtime.runWorkflow(workflowUrl);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function main() {
|
|
146
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
147
|
+
printHelp();
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (command === '--setup' || command === '-s') {
|
|
152
|
+
const workflowName = args[1];
|
|
153
|
+
if (!workflowName) {
|
|
154
|
+
console.error('Error: Workflow name required');
|
|
155
|
+
console.error('Usage: state-machine --setup <workflow-name>');
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
await setup(workflowName);
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const workflowName = args[1];
|
|
163
|
+
|
|
164
|
+
switch (command) {
|
|
165
|
+
case 'run':
|
|
166
|
+
case 'resume':
|
|
167
|
+
if (!workflowName) {
|
|
168
|
+
console.error('Error: Workflow name required');
|
|
169
|
+
console.error(`Usage: state-machine ${command} <workflow-name>`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
await runOrResume(workflowName);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.error('Error:', err.message || String(err));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
case 'status':
|
|
181
|
+
if (!workflowName) {
|
|
182
|
+
listWorkflows();
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
{
|
|
186
|
+
const workflowDir = resolveWorkflowDir(workflowName);
|
|
187
|
+
const runtime = new WorkflowRuntime(workflowDir);
|
|
188
|
+
runtime.showStatus();
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
case 'history':
|
|
193
|
+
if (!workflowName) {
|
|
194
|
+
console.error('Error: Workflow name required');
|
|
195
|
+
console.error('Usage: state-machine history <workflow-name> [limit]');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
{
|
|
199
|
+
const limit = parseInt(args[2], 10) || 20;
|
|
200
|
+
const workflowDir = resolveWorkflowDir(workflowName);
|
|
201
|
+
const runtime = new WorkflowRuntime(workflowDir);
|
|
202
|
+
runtime.showHistory(limit);
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
|
|
206
|
+
case 'reset':
|
|
207
|
+
if (!workflowName) {
|
|
208
|
+
console.error('Error: Workflow name required');
|
|
209
|
+
console.error('Usage: state-machine reset <workflow-name>');
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
{
|
|
213
|
+
const workflowDir = resolveWorkflowDir(workflowName);
|
|
214
|
+
const runtime = new WorkflowRuntime(workflowDir);
|
|
215
|
+
runtime.reset();
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
|
|
219
|
+
case 'reset-hard':
|
|
220
|
+
if (!workflowName) {
|
|
221
|
+
console.error('Error: Workflow name required');
|
|
222
|
+
console.error('Usage: state-machine reset-hard <workflow-name>');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
{
|
|
226
|
+
const workflowDir = resolveWorkflowDir(workflowName);
|
|
227
|
+
const runtime = new WorkflowRuntime(workflowDir);
|
|
228
|
+
runtime.resetHard();
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
|
|
232
|
+
case 'list':
|
|
233
|
+
listWorkflows();
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
default:
|
|
237
|
+
console.error(`Unknown command: ${command}`);
|
|
238
|
+
printHelp();
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
main().catch((err) => {
|
|
244
|
+
console.error('Fatal error:', err);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /lib/index.js
|
|
3
|
+
*
|
|
4
|
+
* Public API (native JS workflows only)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { setup } from './setup.js';
|
|
8
|
+
import { llm, llmText, llmJSON, parseJSON, detectAvailableCLIs } from './llm.js';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
WorkflowRuntime,
|
|
12
|
+
agent,
|
|
13
|
+
executeAgent,
|
|
14
|
+
initialPrompt,
|
|
15
|
+
parallel,
|
|
16
|
+
parallelLimit,
|
|
17
|
+
getMemory,
|
|
18
|
+
getCurrentRuntime
|
|
19
|
+
} from './runtime/index.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Live memory proxy:
|
|
23
|
+
* - Reads/writes always target the *current* workflow runtime's memory proxy
|
|
24
|
+
* - Throws on writes if used outside a workflow run (prevents silent no-op)
|
|
25
|
+
*/
|
|
26
|
+
export const memory = new Proxy(
|
|
27
|
+
{},
|
|
28
|
+
{
|
|
29
|
+
get(_target, prop) {
|
|
30
|
+
const runtime = getCurrentRuntime();
|
|
31
|
+
if (!runtime) return undefined;
|
|
32
|
+
return runtime.memory[prop];
|
|
33
|
+
},
|
|
34
|
+
set(_target, prop, value) {
|
|
35
|
+
const runtime = getCurrentRuntime();
|
|
36
|
+
if (!runtime) {
|
|
37
|
+
throw new Error('memory can only be mutated within a running workflow');
|
|
38
|
+
}
|
|
39
|
+
runtime.memory[prop] = value;
|
|
40
|
+
return true;
|
|
41
|
+
},
|
|
42
|
+
deleteProperty(_target, prop) {
|
|
43
|
+
const runtime = getCurrentRuntime();
|
|
44
|
+
if (!runtime) {
|
|
45
|
+
throw new Error('memory can only be mutated within a running workflow');
|
|
46
|
+
}
|
|
47
|
+
delete runtime.memory[prop];
|
|
48
|
+
return true;
|
|
49
|
+
},
|
|
50
|
+
has(_target, prop) {
|
|
51
|
+
const runtime = getCurrentRuntime();
|
|
52
|
+
if (!runtime) return false;
|
|
53
|
+
const raw = runtime.memory?._raw || runtime._rawMemory || {};
|
|
54
|
+
return prop in raw;
|
|
55
|
+
},
|
|
56
|
+
ownKeys() {
|
|
57
|
+
const runtime = getCurrentRuntime();
|
|
58
|
+
if (!runtime) return [];
|
|
59
|
+
const raw = runtime.memory?._raw || runtime._rawMemory || {};
|
|
60
|
+
return Reflect.ownKeys(raw);
|
|
61
|
+
},
|
|
62
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
63
|
+
const runtime = getCurrentRuntime();
|
|
64
|
+
if (!runtime) return undefined;
|
|
65
|
+
const raw = runtime.memory?._raw || runtime._rawMemory || {};
|
|
66
|
+
if (!(prop in raw)) return undefined;
|
|
67
|
+
return {
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
setup,
|
|
77
|
+
llm,
|
|
78
|
+
llmText,
|
|
79
|
+
llmJSON,
|
|
80
|
+
parseJSON,
|
|
81
|
+
detectAvailableCLIs,
|
|
82
|
+
WorkflowRuntime,
|
|
83
|
+
agent,
|
|
84
|
+
executeAgent,
|
|
85
|
+
initialPrompt,
|
|
86
|
+
parallel,
|
|
87
|
+
parallelLimit,
|
|
88
|
+
getCurrentRuntime,
|
|
89
|
+
getMemory
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const api = {
|
|
93
|
+
setup,
|
|
94
|
+
llm,
|
|
95
|
+
llmText,
|
|
96
|
+
llmJSON,
|
|
97
|
+
parseJSON,
|
|
98
|
+
detectAvailableCLIs,
|
|
99
|
+
WorkflowRuntime,
|
|
100
|
+
agent,
|
|
101
|
+
executeAgent,
|
|
102
|
+
initialPrompt,
|
|
103
|
+
parallel,
|
|
104
|
+
parallelLimit,
|
|
105
|
+
getCurrentRuntime,
|
|
106
|
+
getMemory,
|
|
107
|
+
memory
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default api;
|