agent-state-machine 2.1.8 → 2.2.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 +55 -5
- package/bin/cli.js +1 -140
- package/lib/config-utils.js +259 -0
- package/lib/file-tree.js +366 -0
- package/lib/index.js +109 -2
- package/lib/llm.js +35 -5
- package/lib/runtime/agent.js +146 -118
- package/lib/runtime/model-resolution.js +128 -0
- package/lib/runtime/runtime.js +13 -2
- package/lib/runtime/track-changes.js +252 -0
- package/package.json +1 -1
- package/templates/project-builder/agents/assumptions-clarifier.md +0 -8
- package/templates/project-builder/agents/code-reviewer.md +0 -8
- package/templates/project-builder/agents/code-writer.md +0 -10
- package/templates/project-builder/agents/requirements-clarifier.md +0 -7
- package/templates/project-builder/agents/roadmap-generator.md +0 -10
- package/templates/project-builder/agents/scope-clarifier.md +0 -6
- package/templates/project-builder/agents/security-clarifier.md +0 -9
- package/templates/project-builder/agents/security-reviewer.md +0 -12
- package/templates/project-builder/agents/task-planner.md +0 -10
- package/templates/project-builder/agents/test-planner.md +0 -9
- package/templates/project-builder/config.js +13 -1
- package/templates/starter/config.js +12 -1
- package/vercel-server/public/remote/assets/{index-Cunx4VJE.js → index-BOKpYANC.js} +32 -27
- package/vercel-server/public/remote/assets/index-DHL_iHQW.css +1 -0
- package/vercel-server/public/remote/index.html +2 -2
- package/vercel-server/ui/src/components/ContentCard.jsx +6 -6
- package/vercel-server/public/remote/assets/index-D02x57pS.css +0 -1
package/lib/runtime/agent.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createRequire } from 'module';
|
|
|
12
12
|
import { pathToFileURL } from 'url';
|
|
13
13
|
import { getCurrentRuntime } from './runtime.js';
|
|
14
14
|
import { formatInteractionPrompt } from './interaction.js';
|
|
15
|
+
import { withChangeTracking } from './track-changes.js';
|
|
15
16
|
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
17
18
|
|
|
@@ -164,25 +165,37 @@ async function executeJSAgent(runtime, agentPath, name, params, options = {}) {
|
|
|
164
165
|
_config: {
|
|
165
166
|
models: runtime.workflowConfig.models,
|
|
166
167
|
apiKeys: runtime.workflowConfig.apiKeys,
|
|
167
|
-
workflowDir: runtime.workflowDir
|
|
168
|
+
workflowDir: runtime.workflowDir,
|
|
169
|
+
projectRoot: runtime.workflowConfig.projectRoot
|
|
168
170
|
}
|
|
169
171
|
};
|
|
170
172
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
173
|
+
// Execute handler with optional file change tracking
|
|
174
|
+
const executeHandler = async () => {
|
|
175
|
+
let result = await handler(context);
|
|
176
|
+
let interactionDepth = 0;
|
|
177
|
+
|
|
178
|
+
// Handle interaction response from JS agent (support multiple rounds)
|
|
179
|
+
while (result && result._interaction) {
|
|
180
|
+
const interactionResponse = await handleInteraction(runtime, result._interaction, name);
|
|
181
|
+
const resumedContext = { ...context, userResponse: interactionResponse };
|
|
182
|
+
await logAgentStart(runtime, name);
|
|
183
|
+
result = await handler(resumedContext);
|
|
184
|
+
interactionDepth += 1;
|
|
185
|
+
if (interactionDepth > 5) {
|
|
186
|
+
throw new Error(`Agent ${name} exceeded maximum interaction depth`);
|
|
187
|
+
}
|
|
183
188
|
}
|
|
184
|
-
}
|
|
185
189
|
|
|
190
|
+
return result;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
let result;
|
|
194
|
+
if (runtime.workflowConfig.fileTracking !== false) {
|
|
195
|
+
result = await withChangeTracking(runtime, name, executeHandler);
|
|
196
|
+
} else {
|
|
197
|
+
result = await executeHandler();
|
|
198
|
+
}
|
|
186
199
|
|
|
187
200
|
// Clean internal properties from result
|
|
188
201
|
if (result && typeof result === 'object') {
|
|
@@ -191,6 +204,7 @@ async function executeJSAgent(runtime, agentPath, name, params, options = {}) {
|
|
|
191
204
|
delete cleanResult._config;
|
|
192
205
|
delete cleanResult._loop;
|
|
193
206
|
delete cleanResult._interaction;
|
|
207
|
+
delete cleanResult._files;
|
|
194
208
|
|
|
195
209
|
// If agent returned a context-like object, only return non-internal keys
|
|
196
210
|
const meaningfulKeys = Object.keys(cleanResult).filter((k) => !k.startsWith('_'));
|
|
@@ -235,127 +249,141 @@ async function executeMDAgent(runtime, agentPath, name, params, options = {}) {
|
|
|
235
249
|
? runtime.loadSteeringFiles(steeringNames)
|
|
236
250
|
: runtime.steering;
|
|
237
251
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const context = {
|
|
246
|
-
...currentParams,
|
|
247
|
-
_steering: steeringContext,
|
|
248
|
-
_config: {
|
|
249
|
-
models: runtime.workflowConfig.models,
|
|
250
|
-
apiKeys: runtime.workflowConfig.apiKeys,
|
|
251
|
-
workflowDir: runtime.workflowDir
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
// Interpolate variables in prompt
|
|
256
|
-
const interpolatedPrompt = interpolatePrompt(prompt, context);
|
|
252
|
+
// Build base config (used for all iterations)
|
|
253
|
+
const baseConfig = {
|
|
254
|
+
models: runtime.workflowConfig.models,
|
|
255
|
+
apiKeys: runtime.workflowConfig.apiKeys,
|
|
256
|
+
workflowDir: runtime.workflowDir,
|
|
257
|
+
projectRoot: runtime.workflowConfig.projectRoot
|
|
258
|
+
};
|
|
257
259
|
|
|
258
|
-
|
|
260
|
+
// Execute the MD agent core logic
|
|
261
|
+
const executeMDAgentCore = async () => {
|
|
262
|
+
let response = null;
|
|
263
|
+
let output = null;
|
|
264
|
+
let interactionDepth = 0;
|
|
265
|
+
let currentParams = params;
|
|
266
|
+
|
|
267
|
+
while (true) {
|
|
268
|
+
// Build context - only spread params, NOT memory (explicit context passing)
|
|
269
|
+
const context = {
|
|
270
|
+
...currentParams,
|
|
271
|
+
_steering: steeringContext,
|
|
272
|
+
_config: baseConfig
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Interpolate variables in prompt
|
|
276
|
+
const interpolatedPrompt = interpolatePrompt(prompt, context);
|
|
277
|
+
|
|
278
|
+
const model = config.model || 'fast';
|
|
279
|
+
|
|
280
|
+
const fullPrompt = buildPrompt(context, {
|
|
281
|
+
model,
|
|
282
|
+
prompt: interpolatedPrompt,
|
|
283
|
+
includeContext: config.includeContext !== 'false',
|
|
284
|
+
responseType: config.response
|
|
285
|
+
});
|
|
259
286
|
|
|
260
|
-
|
|
261
|
-
model,
|
|
262
|
-
prompt: interpolatedPrompt,
|
|
263
|
-
includeContext: config.includeContext !== 'false',
|
|
264
|
-
responseType: config.response
|
|
265
|
-
});
|
|
287
|
+
await logAgentStart(runtime, name, fullPrompt);
|
|
266
288
|
|
|
267
|
-
|
|
289
|
+
console.log(` Using model: ${model}`);
|
|
268
290
|
|
|
269
|
-
|
|
291
|
+
response = await llm(context, {
|
|
292
|
+
model: model,
|
|
293
|
+
prompt: interpolatedPrompt,
|
|
294
|
+
includeContext: config.includeContext !== 'false',
|
|
295
|
+
responseType: config.response
|
|
296
|
+
});
|
|
270
297
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
298
|
+
// Parse output based on format
|
|
299
|
+
output = response.text;
|
|
300
|
+
if (config.format === 'json') {
|
|
301
|
+
try {
|
|
302
|
+
output = parseJSON(response.text);
|
|
303
|
+
} catch {
|
|
304
|
+
console.warn(` Warning: Failed to parse JSON output`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
277
307
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
308
|
+
if (output && typeof output === 'object' && output._interaction) {
|
|
309
|
+
const interactionResponse = await handleInteraction(runtime, output._interaction, name);
|
|
310
|
+
currentParams = { ...params, userResponse: interactionResponse };
|
|
311
|
+
interactionDepth += 1;
|
|
312
|
+
if (interactionDepth > 5) {
|
|
313
|
+
throw new Error(`Agent ${name} exceeded maximum interaction depth`);
|
|
314
|
+
}
|
|
315
|
+
continue;
|
|
285
316
|
}
|
|
317
|
+
|
|
318
|
+
break;
|
|
286
319
|
}
|
|
287
320
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
321
|
+
// Check for interaction request
|
|
322
|
+
const parsedInteraction = parseInteractionRequest(response.text);
|
|
323
|
+
const structuredInteraction =
|
|
324
|
+
config.autoInteract !== 'false' && parsedInteraction.isInteraction;
|
|
325
|
+
|
|
326
|
+
// Check if agent returned an 'interact' object in its JSON response
|
|
327
|
+
const hasInteractKey = output && typeof output === 'object' && output.interact;
|
|
328
|
+
|
|
329
|
+
// Explicit interaction mode (format: interaction OR interaction: true)
|
|
330
|
+
// But only trigger if agent actually wants to interact (has interact key or parsed interaction)
|
|
331
|
+
const explicitInteraction =
|
|
332
|
+
config.format === 'interaction' ||
|
|
333
|
+
((config.interaction === 'true' || (typeof config.interaction === 'string' && config.interaction.length > 0)) &&
|
|
334
|
+
(hasInteractKey || structuredInteraction));
|
|
335
|
+
|
|
336
|
+
if (explicitInteraction || structuredInteraction) {
|
|
337
|
+
// Use interact object if present, otherwise fall back to parsed/raw
|
|
338
|
+
const interactionData = hasInteractKey ? output.interact : (structuredInteraction ? parsedInteraction : null);
|
|
339
|
+
|
|
340
|
+
const slugRaw =
|
|
341
|
+
interactionData?.slug ||
|
|
342
|
+
(typeof config.interaction === 'string' && config.interaction !== 'true'
|
|
343
|
+
? config.interaction
|
|
344
|
+
: null) ||
|
|
345
|
+
config.interactionSlug ||
|
|
346
|
+
config.interactionKey ||
|
|
347
|
+
name;
|
|
348
|
+
|
|
349
|
+
const slug = sanitizeSlug(slugRaw);
|
|
350
|
+
const targetKey = config.interactionKey || outputKey || slug;
|
|
351
|
+
|
|
352
|
+
// Build interaction object with full metadata
|
|
353
|
+
const interactionObj = hasInteractKey ? {
|
|
354
|
+
...output.interact,
|
|
355
|
+
slug,
|
|
356
|
+
targetKey
|
|
357
|
+
} : {
|
|
358
|
+
slug,
|
|
359
|
+
targetKey,
|
|
360
|
+
content: structuredInteraction ? parsedInteraction.question : response.text
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const userResponse = await handleInteraction(runtime, interactionObj, name);
|
|
364
|
+
|
|
365
|
+
// Return the user's response as the agent result
|
|
366
|
+
if (outputKey) {
|
|
367
|
+
return { [outputKey]: userResponse, _debug_prompt: response.fullPrompt };
|
|
294
368
|
}
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
369
|
|
|
298
|
-
|
|
299
|
-
|
|
370
|
+
return userResponse;
|
|
371
|
+
}
|
|
300
372
|
|
|
301
|
-
|
|
302
|
-
const parsedInteraction = parseInteractionRequest(response.text);
|
|
303
|
-
const structuredInteraction =
|
|
304
|
-
config.autoInteract !== 'false' && parsedInteraction.isInteraction;
|
|
305
|
-
|
|
306
|
-
// Check if agent returned an 'interact' object in its JSON response
|
|
307
|
-
const hasInteractKey = output && typeof output === 'object' && output.interact;
|
|
308
|
-
|
|
309
|
-
// Explicit interaction mode (format: interaction OR interaction: true)
|
|
310
|
-
// But only trigger if agent actually wants to interact (has interact key or parsed interaction)
|
|
311
|
-
const explicitInteraction =
|
|
312
|
-
config.format === 'interaction' ||
|
|
313
|
-
((config.interaction === 'true' || (typeof config.interaction === 'string' && config.interaction.length > 0)) &&
|
|
314
|
-
(hasInteractKey || structuredInteraction));
|
|
315
|
-
|
|
316
|
-
if (explicitInteraction || structuredInteraction) {
|
|
317
|
-
// Use interact object if present, otherwise fall back to parsed/raw
|
|
318
|
-
const interactionData = hasInteractKey ? output.interact : (structuredInteraction ? parsedInteraction : null);
|
|
319
|
-
|
|
320
|
-
const slugRaw =
|
|
321
|
-
interactionData?.slug ||
|
|
322
|
-
(typeof config.interaction === 'string' && config.interaction !== 'true'
|
|
323
|
-
? config.interaction
|
|
324
|
-
: null) ||
|
|
325
|
-
config.interactionSlug ||
|
|
326
|
-
config.interactionKey ||
|
|
327
|
-
name;
|
|
328
|
-
|
|
329
|
-
const slug = sanitizeSlug(slugRaw);
|
|
330
|
-
const targetKey = config.interactionKey || outputKey || slug;
|
|
331
|
-
|
|
332
|
-
// Build interaction object with full metadata
|
|
333
|
-
const interactionObj = hasInteractKey ? {
|
|
334
|
-
...output.interact,
|
|
335
|
-
slug,
|
|
336
|
-
targetKey
|
|
337
|
-
} : {
|
|
338
|
-
slug,
|
|
339
|
-
targetKey,
|
|
340
|
-
content: structuredInteraction ? parsedInteraction.question : response.text
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
const userResponse = await handleInteraction(runtime, interactionObj, name);
|
|
344
|
-
|
|
345
|
-
// Return the user's response as the agent result
|
|
373
|
+
// Return result object
|
|
346
374
|
if (outputKey) {
|
|
347
|
-
return { [outputKey]:
|
|
375
|
+
return { [outputKey]: output, _debug_prompt: response.fullPrompt };
|
|
348
376
|
}
|
|
349
377
|
|
|
350
|
-
return
|
|
351
|
-
}
|
|
378
|
+
return output;
|
|
379
|
+
};
|
|
352
380
|
|
|
353
|
-
//
|
|
354
|
-
if (
|
|
355
|
-
return
|
|
381
|
+
// Execute with optional file change tracking
|
|
382
|
+
if (runtime.workflowConfig.fileTracking !== false) {
|
|
383
|
+
return withChangeTracking(runtime, name, executeMDAgentCore);
|
|
384
|
+
} else {
|
|
385
|
+
return executeMDAgentCore();
|
|
356
386
|
}
|
|
357
|
-
|
|
358
|
-
return output;
|
|
359
387
|
}
|
|
360
388
|
|
|
361
389
|
/**
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /lib/runtime/model-resolution.js
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { askHuman } from './prompt.js';
|
|
8
|
+
import { createInteraction, parseInteractionResponse } from './interaction.js';
|
|
9
|
+
import { readModelFromConfig, writeModelToConfig } from '../config-utils.js';
|
|
10
|
+
|
|
11
|
+
function sanitizeSlug(value) {
|
|
12
|
+
return String(value)
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
15
|
+
.replace(/^-+|-+$/g, '')
|
|
16
|
+
.substring(0, 40) || 'model';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildModelSuggestions(availableCLIs = {}, existingModels = {}) {
|
|
20
|
+
const options = [];
|
|
21
|
+
const lookup = {};
|
|
22
|
+
|
|
23
|
+
const addOption = (key, label, description, value) => {
|
|
24
|
+
options.push({ key, label, description });
|
|
25
|
+
lookup[key] = value;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
Object.entries(existingModels).forEach(([key, value]) => {
|
|
29
|
+
const optionKey = `existing:${key}`;
|
|
30
|
+
const description = value ? `Maps to "${value}"` : 'Existing model mapping';
|
|
31
|
+
addOption(optionKey, `Use existing model: ${key}`, description, value);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (availableCLIs.claude) {
|
|
35
|
+
addOption('cli:claude', 'claude -p', 'Claude CLI (print mode)', 'claude -p');
|
|
36
|
+
}
|
|
37
|
+
if (availableCLIs.gemini) {
|
|
38
|
+
addOption('cli:gemini', 'gemini', 'Gemini CLI', 'gemini');
|
|
39
|
+
}
|
|
40
|
+
if (availableCLIs.codex) {
|
|
41
|
+
addOption('cli:codex', 'codex', 'Codex CLI (exec)', 'codex');
|
|
42
|
+
}
|
|
43
|
+
if (availableCLIs.ollama) {
|
|
44
|
+
addOption('cli:ollama', 'ollama run llama3.1', 'Ollama CLI (example model)', 'ollama run llama3.1');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
addOption(
|
|
48
|
+
'api:openai',
|
|
49
|
+
'api:openai:gpt-4.1-mini',
|
|
50
|
+
'OpenAI API (example model)',
|
|
51
|
+
'api:openai:gpt-4.1-mini'
|
|
52
|
+
);
|
|
53
|
+
addOption(
|
|
54
|
+
'api:anthropic',
|
|
55
|
+
'api:anthropic:claude-3-5-sonnet-20241022',
|
|
56
|
+
'Anthropic API (example model)',
|
|
57
|
+
'api:anthropic:claude-3-5-sonnet-20241022'
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return { options, lookup };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function promptForModelConfig(modelKey, existingModels = {}, availableCLIs = {}) {
|
|
64
|
+
const existingKeys = Object.keys(existingModels);
|
|
65
|
+
const existingSummary = existingKeys.length > 0
|
|
66
|
+
? `Existing models: ${existingKeys.join(', ')}`
|
|
67
|
+
: 'No models configured yet.';
|
|
68
|
+
|
|
69
|
+
const { options, lookup } = buildModelSuggestions(availableCLIs, existingModels);
|
|
70
|
+
const prompt = `Unknown model key: "${modelKey}"\n\nHow would you like to configure this model?\n${existingSummary}`;
|
|
71
|
+
const slug = `model-${sanitizeSlug(modelKey)}`;
|
|
72
|
+
const interaction = createInteraction('choice', slug, {
|
|
73
|
+
prompt,
|
|
74
|
+
options,
|
|
75
|
+
allowCustom: true,
|
|
76
|
+
multiSelect: false
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const answer = await askHuman(prompt, { slug, interaction });
|
|
80
|
+
const parsed = await parseInteractionResponse(interaction, answer);
|
|
81
|
+
|
|
82
|
+
if (parsed.isCustom && parsed.customText) {
|
|
83
|
+
return parsed.customText.trim();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (parsed.selectedKey) {
|
|
87
|
+
const mapped = lookup[parsed.selectedKey];
|
|
88
|
+
if (mapped) return mapped;
|
|
89
|
+
return String(parsed.selectedKey).trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (typeof parsed.text === 'string' && parsed.text.trim()) {
|
|
93
|
+
return parsed.text.trim();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof parsed.raw === 'string' && parsed.raw.trim()) {
|
|
97
|
+
return parsed.raw.trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
throw new Error('No model configuration provided.');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function resolveUnknownModel(modelKey, config, workflowDir, options = {}) {
|
|
104
|
+
if (!workflowDir) {
|
|
105
|
+
throw new Error('Cannot resolve model without a workflow directory.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const configFile = path.join(workflowDir, 'config.js');
|
|
109
|
+
if (!fs.existsSync(configFile)) {
|
|
110
|
+
throw new Error(`config.js not found in ${workflowDir}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const existing = readModelFromConfig(configFile, modelKey);
|
|
114
|
+
if (existing) {
|
|
115
|
+
return existing;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const existingModels = config?.models || {};
|
|
119
|
+
const availableCLIs = options.availableCLIs || {};
|
|
120
|
+
const modelValue = await promptForModelConfig(modelKey, existingModels, availableCLIs);
|
|
121
|
+
|
|
122
|
+
if (!modelValue) {
|
|
123
|
+
throw new Error('Model configuration cannot be empty.');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
writeModelToConfig(configFile, modelKey, modelValue);
|
|
127
|
+
return modelValue;
|
|
128
|
+
}
|
package/lib/runtime/runtime.js
CHANGED
|
@@ -12,6 +12,7 @@ import readline from 'readline';
|
|
|
12
12
|
import { pathToFileURL } from 'url';
|
|
13
13
|
import { createMemoryProxy } from './memory.js';
|
|
14
14
|
import { RemoteClient } from '../remote/client.js';
|
|
15
|
+
import { DEFAULT_IGNORE } from '../file-tree.js';
|
|
15
16
|
|
|
16
17
|
// Global runtime reference for agent() and memory access
|
|
17
18
|
// stored on globalThis to ensure singleton access across different module instances (CLI vs local)
|
|
@@ -74,7 +75,12 @@ export class WorkflowRuntime {
|
|
|
74
75
|
this.workflowConfig = {
|
|
75
76
|
models: {},
|
|
76
77
|
apiKeys: {},
|
|
77
|
-
description: ''
|
|
78
|
+
description: '',
|
|
79
|
+
// File tracking defaults
|
|
80
|
+
projectRoot: path.resolve(workflowDir, '../..'),
|
|
81
|
+
fileTracking: true,
|
|
82
|
+
fileTrackingIgnore: DEFAULT_IGNORE,
|
|
83
|
+
fileTrackingKeepDeleted: false
|
|
78
84
|
};
|
|
79
85
|
|
|
80
86
|
// Load steering
|
|
@@ -317,7 +323,12 @@ export class WorkflowRuntime {
|
|
|
317
323
|
this.workflowConfig = {
|
|
318
324
|
models: cfg.models || {},
|
|
319
325
|
apiKeys: cfg.apiKeys || {},
|
|
320
|
-
description: cfg.description || ''
|
|
326
|
+
description: cfg.description || '',
|
|
327
|
+
// File tracking configuration
|
|
328
|
+
projectRoot: cfg.projectRoot || path.resolve(this.workflowDir, '../..'),
|
|
329
|
+
fileTracking: cfg.fileTracking ?? true,
|
|
330
|
+
fileTrackingIgnore: cfg.fileTrackingIgnore || DEFAULT_IGNORE,
|
|
331
|
+
fileTrackingKeepDeleted: cfg.fileTrackingKeepDeleted ?? false
|
|
321
332
|
};
|
|
322
333
|
|
|
323
334
|
// Import workflow module
|