agent-state-machine 2.0.12 → 2.0.13
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/llm.js +11 -1
- package/lib/runtime/agent.js +124 -36
- package/lib/runtime/runtime.js +36 -0
- package/package.json +1 -1
package/lib/llm.js
CHANGED
|
@@ -51,6 +51,7 @@ export function buildPrompt(context, options) {
|
|
|
51
51
|
delete cleanContext._steering;
|
|
52
52
|
delete cleanContext._loop;
|
|
53
53
|
delete cleanContext._config;
|
|
54
|
+
delete cleanContext._memory;
|
|
54
55
|
|
|
55
56
|
// Add the actual prompt
|
|
56
57
|
parts.push('# Task\n\n');
|
|
@@ -70,13 +71,22 @@ export function buildPrompt(context, options) {
|
|
|
70
71
|
parts.push('{ "interact": "your question here" }\n\n');
|
|
71
72
|
parts.push('Only use this format when you genuinely need user input to proceed.\n\n---\n');
|
|
72
73
|
|
|
73
|
-
// Add global steering if available
|
|
74
|
+
// Add global steering if available (always first)
|
|
74
75
|
if (context._steering?.global) {
|
|
75
76
|
parts.push('# System Instructions\n');
|
|
76
77
|
parts.push(context._steering.global);
|
|
77
78
|
parts.push('\n---\n');
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
// Add additional steering files if available
|
|
82
|
+
if (context._steering?.additional && context._steering.additional.length > 0) {
|
|
83
|
+
parts.push('# Additional Guidelines\n');
|
|
84
|
+
for (const content of context._steering.additional) {
|
|
85
|
+
parts.push(content);
|
|
86
|
+
parts.push('\n---\n');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
return parts.join('\n');
|
|
81
91
|
}
|
|
82
92
|
|
package/lib/runtime/agent.js
CHANGED
|
@@ -17,50 +17,87 @@ const require = createRequire(import.meta.url);
|
|
|
17
17
|
/**
|
|
18
18
|
* Run an agent with context
|
|
19
19
|
* @param {string} name - Agent name (file basename)
|
|
20
|
-
* @param {object} params - Parameters passed to agent
|
|
20
|
+
* @param {object} params - Parameters passed to agent (default: {})
|
|
21
|
+
* @param {object} options - Agent execution options (default: {})
|
|
22
|
+
* @param {number|false} options.retry - Number of retries (default: 2, meaning 3 total attempts). Set to false to disable.
|
|
23
|
+
* @param {string|string[]} options.steering - Additional steering files to load from steering/ folder
|
|
21
24
|
*/
|
|
22
|
-
export async function agent(name, params = {}) {
|
|
25
|
+
export async function agent(name, params = {}, options = {}) {
|
|
23
26
|
const runtime = getCurrentRuntime();
|
|
24
27
|
if (!runtime) {
|
|
25
28
|
throw new Error('agent() must be called within a workflow context');
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
// Parse retry option: default is 2 retries (3 total attempts)
|
|
32
|
+
const retryCount = options.retry === false ? 0 : (options.retry ?? 2);
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
const result = await executeAgent(runtime, name, params);
|
|
34
|
+
let lastError;
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
|
37
|
+
try {
|
|
38
|
+
if (attempt > 0) {
|
|
39
|
+
console.log(` [Agent: ${name}] Retry attempt ${attempt}/${retryCount}...`);
|
|
40
|
+
} else {
|
|
41
|
+
console.log(` [Agent: ${name}] Starting...`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const result = await executeAgent(runtime, name, params, options);
|
|
45
|
+
|
|
46
|
+
if (result && typeof result === 'object' && result._debug_prompt) {
|
|
47
|
+
delete result._debug_prompt;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(` [Agent: ${name}] Completed`);
|
|
51
|
+
if (runtime._agentSuppressCompletion?.has(name)) {
|
|
52
|
+
runtime._agentSuppressCompletion.delete(name);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
runtime.prependHistory({
|
|
57
|
+
event: 'AGENT_COMPLETED',
|
|
58
|
+
agent: name,
|
|
59
|
+
output: result,
|
|
60
|
+
attempts: attempt + 1
|
|
61
|
+
});
|
|
36
62
|
|
|
37
|
-
console.log(` [Agent: ${name}] Completed`);
|
|
38
|
-
if (runtime._agentSuppressCompletion?.has(name)) {
|
|
39
|
-
runtime._agentSuppressCompletion.delete(name);
|
|
40
63
|
return result;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
lastError = error;
|
|
66
|
+
|
|
67
|
+
if (attempt < retryCount) {
|
|
68
|
+
console.error(` [Agent: ${name}] Error (attempt ${attempt + 1}/${retryCount + 1}): ${error.message}`);
|
|
69
|
+
runtime.prependHistory({
|
|
70
|
+
event: 'AGENT_RETRY',
|
|
71
|
+
agent: name,
|
|
72
|
+
attempt: attempt + 1,
|
|
73
|
+
error: error.message
|
|
74
|
+
});
|
|
75
|
+
}
|
|
41
76
|
}
|
|
77
|
+
}
|
|
42
78
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
79
|
+
// All retries exhausted - record failure
|
|
80
|
+
runtime.prependHistory({
|
|
81
|
+
event: 'AGENT_FAILED',
|
|
82
|
+
agent: name,
|
|
83
|
+
error: lastError.message,
|
|
84
|
+
attempts: retryCount + 1
|
|
85
|
+
});
|
|
48
86
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
87
|
+
// Store error in accessible location (not auto-spread to context)
|
|
88
|
+
runtime._agentErrors.push({
|
|
89
|
+
agent: name,
|
|
90
|
+
error: lastError.message,
|
|
91
|
+
timestamp: new Date().toISOString()
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
throw lastError;
|
|
58
95
|
}
|
|
59
96
|
|
|
60
97
|
/**
|
|
61
98
|
* Execute an agent (load and run)
|
|
62
99
|
*/
|
|
63
|
-
export async function executeAgent(runtime, name, params) {
|
|
100
|
+
export async function executeAgent(runtime, name, params, options = {}) {
|
|
64
101
|
const agentsDir = runtime.agentsDir;
|
|
65
102
|
|
|
66
103
|
// Try JS agents (.js/.mjs/.cjs)
|
|
@@ -72,14 +109,14 @@ export async function executeAgent(runtime, name, params) {
|
|
|
72
109
|
|
|
73
110
|
for (const p of jsCandidates) {
|
|
74
111
|
if (fs.existsSync(p)) {
|
|
75
|
-
return executeJSAgent(runtime, p, name, params);
|
|
112
|
+
return executeJSAgent(runtime, p, name, params, options);
|
|
76
113
|
}
|
|
77
114
|
}
|
|
78
115
|
|
|
79
116
|
// Try Markdown agent
|
|
80
117
|
const mdPath = path.join(agentsDir, `${name}.md`);
|
|
81
118
|
if (fs.existsSync(mdPath)) {
|
|
82
|
-
return executeMDAgent(runtime, mdPath, name, params);
|
|
119
|
+
return executeMDAgent(runtime, mdPath, name, params, options);
|
|
83
120
|
}
|
|
84
121
|
|
|
85
122
|
throw new Error(
|
|
@@ -94,7 +131,7 @@ export async function executeAgent(runtime, name, params) {
|
|
|
94
131
|
* - ESM (.js/.mjs): loaded via dynamic import with cache-bust for hot reload
|
|
95
132
|
* - CJS (.cjs): loaded via require() with cache clear
|
|
96
133
|
*/
|
|
97
|
-
async function executeJSAgent(runtime, agentPath, name, params) {
|
|
134
|
+
async function executeJSAgent(runtime, agentPath, name, params, options = {}) {
|
|
98
135
|
const ext = path.extname(agentPath).toLowerCase();
|
|
99
136
|
|
|
100
137
|
let agentModule;
|
|
@@ -119,11 +156,15 @@ async function executeJSAgent(runtime, agentPath, name, params) {
|
|
|
119
156
|
|
|
120
157
|
logAgentStart(runtime, name);
|
|
121
158
|
|
|
122
|
-
// Build context
|
|
159
|
+
// Build steering context (global + any additional files from options)
|
|
160
|
+
const steeringContext = options.steering
|
|
161
|
+
? runtime.loadSteeringFiles(options.steering)
|
|
162
|
+
: runtime.steering;
|
|
163
|
+
|
|
164
|
+
// Build context - only spread params, NOT memory (explicit context passing)
|
|
123
165
|
const context = {
|
|
124
|
-
...runtime._rawMemory,
|
|
125
166
|
...params,
|
|
126
|
-
_steering:
|
|
167
|
+
_steering: steeringContext,
|
|
127
168
|
_config: {
|
|
128
169
|
models: runtime.workflowConfig.models,
|
|
129
170
|
apiKeys: runtime.workflowConfig.apiKeys,
|
|
@@ -175,7 +216,7 @@ async function executeJSAgent(runtime, agentPath, name, params) {
|
|
|
175
216
|
/**
|
|
176
217
|
* Execute a Markdown agent (prompt-based)
|
|
177
218
|
*/
|
|
178
|
-
async function executeMDAgent(runtime, agentPath, name, params) {
|
|
219
|
+
async function executeMDAgent(runtime, agentPath, name, params, options = {}) {
|
|
179
220
|
const { llm, buildPrompt, parseJSON, parseInteractionRequest } = await import('../llm.js');
|
|
180
221
|
|
|
181
222
|
const content = fs.readFileSync(agentPath, 'utf-8');
|
|
@@ -184,11 +225,28 @@ async function executeMDAgent(runtime, agentPath, name, params) {
|
|
|
184
225
|
const outputKey = config.output || 'result';
|
|
185
226
|
const targetKey = config.interactionKey || outputKey;
|
|
186
227
|
|
|
187
|
-
//
|
|
228
|
+
// Combine steering from options (runtime call) and frontmatter (static)
|
|
229
|
+
let steeringNames = [];
|
|
230
|
+
|
|
231
|
+
if (options.steering) {
|
|
232
|
+
const optSteering = Array.isArray(options.steering) ? options.steering : [options.steering];
|
|
233
|
+
steeringNames.push(...optSteering);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (config.steering) {
|
|
237
|
+
const fmSteering = parseSteeringFrontmatter(config.steering);
|
|
238
|
+
steeringNames.push(...fmSteering);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Build steering context (global + any additional files)
|
|
242
|
+
const steeringContext = steeringNames.length > 0
|
|
243
|
+
? runtime.loadSteeringFiles(steeringNames)
|
|
244
|
+
: runtime.steering;
|
|
245
|
+
|
|
246
|
+
// Build context - only spread params, NOT memory (explicit context passing)
|
|
188
247
|
const context = {
|
|
189
|
-
...runtime._rawMemory,
|
|
190
248
|
...params,
|
|
191
|
-
_steering:
|
|
249
|
+
_steering: steeringContext,
|
|
192
250
|
_config: {
|
|
193
251
|
models: runtime.workflowConfig.models,
|
|
194
252
|
apiKeys: runtime.workflowConfig.apiKeys,
|
|
@@ -296,6 +354,36 @@ function parseMarkdownAgent(content) {
|
|
|
296
354
|
return { config: {}, prompt: content.trim() };
|
|
297
355
|
}
|
|
298
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Parse steering value from frontmatter
|
|
359
|
+
* Supports: "name", "name1, name2", "[name1, name2]", "['name1', 'name2']"
|
|
360
|
+
* @param {string} value - Frontmatter steering value
|
|
361
|
+
* @returns {string[]} Array of steering file names
|
|
362
|
+
*/
|
|
363
|
+
function parseSteeringFrontmatter(value) {
|
|
364
|
+
if (!value) return [];
|
|
365
|
+
|
|
366
|
+
const trimmed = value.trim();
|
|
367
|
+
|
|
368
|
+
// Handle array format: [a, b, c] or ["a", "b", "c"]
|
|
369
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
370
|
+
const inner = trimmed.slice(1, -1);
|
|
371
|
+
return inner.split(',')
|
|
372
|
+
.map(s => s.trim().replace(/^["']|["']$/g, ''))
|
|
373
|
+
.filter(Boolean);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Handle comma-separated: a, b, c
|
|
377
|
+
if (trimmed.includes(',')) {
|
|
378
|
+
return trimmed.split(',')
|
|
379
|
+
.map(s => s.trim())
|
|
380
|
+
.filter(Boolean);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Single value
|
|
384
|
+
return [trimmed];
|
|
385
|
+
}
|
|
386
|
+
|
|
299
387
|
/**
|
|
300
388
|
* Interpolate {{variables}} in prompt template
|
|
301
389
|
*/
|
package/lib/runtime/runtime.js
CHANGED
|
@@ -89,6 +89,9 @@ export class WorkflowRuntime {
|
|
|
89
89
|
// Agent interaction tracking for history logging
|
|
90
90
|
this._agentResumeFlags = new Set();
|
|
91
91
|
this._agentSuppressCompletion = new Set();
|
|
92
|
+
|
|
93
|
+
// Agent error tracking (not persisted to memory, but accessible during run)
|
|
94
|
+
this._agentErrors = [];
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
ensureDirectories() {
|
|
@@ -142,6 +145,39 @@ export class WorkflowRuntime {
|
|
|
142
145
|
return steering;
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Load a specific steering file by name
|
|
150
|
+
* @param {string} name - Name of the steering file (without .md extension)
|
|
151
|
+
* @returns {string} Content of the steering file, or empty string if not found
|
|
152
|
+
*/
|
|
153
|
+
loadSteeringFile(name) {
|
|
154
|
+
const filePath = path.join(this.steeringDir, `${name}.md`);
|
|
155
|
+
|
|
156
|
+
if (fs.existsSync(filePath)) {
|
|
157
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.warn(`${C.yellow}Warning: Steering file not found: ${name}.md${C.reset}`);
|
|
161
|
+
return '';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Load multiple steering files and combine with global
|
|
166
|
+
* @param {string|string[]} steeringNames - Names of steering files to load
|
|
167
|
+
* @returns {{ enabled: boolean, global: string, additional: string[] }}
|
|
168
|
+
*/
|
|
169
|
+
loadSteeringFiles(steeringNames) {
|
|
170
|
+
const names = Array.isArray(steeringNames) ? steeringNames : [steeringNames];
|
|
171
|
+
const additional = names
|
|
172
|
+
.map(name => this.loadSteeringFile(name))
|
|
173
|
+
.filter(content => content.length > 0);
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
...this.steering,
|
|
177
|
+
additional
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
145
181
|
/**
|
|
146
182
|
* Persist state to disk
|
|
147
183
|
*/
|