agent-state-machine 2.4.0 → 2.6.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/bin/cli.js +7 -7
- package/lib/llm.js +14 -3
- package/lib/remote/client.js +19 -6
- package/lib/runtime/agent.js +29 -2
- package/lib/runtime/prompt.js +1 -1
- package/lib/runtime/runtime.js +48 -4
- package/lib/runtime/track-changes.js +84 -0
- package/package.json +1 -1
- package/templates/project-builder/agents/{code-writer.md → code-write.md} +18 -12
- package/templates/project-builder/agents/{assumptions-clarifier.md → intake-assumptions.md} +1 -0
- package/templates/project-builder/agents/{requirements-clarifier.md → intake-requirements.md} +1 -0
- package/templates/project-builder/agents/{scope-clarifier.md → intake-scope.md} +1 -0
- package/templates/project-builder/agents/{security-clarifier.md → intake-security.md} +1 -0
- package/templates/project-builder/agents/{roadmap-generator.md → plan-roadmap.md} +1 -0
- package/templates/project-builder/agents/{task-planner.md → plan-tasks.md} +1 -0
- package/templates/project-builder/agents/post-code-fix.md +59 -0
- package/templates/project-builder/agents/{code-reviewer.md → post-code-review.md} +10 -0
- package/templates/project-builder/agents/post-code-security.md +55 -0
- package/templates/project-builder/agents/{security-reviewer.md → pre-code-security.md} +8 -11
- package/templates/project-builder/agents/{test-planner.md → pre-code-tests.md} +1 -0
- package/templates/project-builder/agents/response-interpreter.md +1 -0
- package/templates/project-builder/agents/verify-commit-msg.md +64 -0
- package/templates/project-builder/agents/{sanity-checker.md → verify-sanity.md} +1 -12
- package/templates/project-builder/config.js +15 -4
- package/templates/project-builder/scripts/safeguard-recovery.js +40 -0
- package/templates/project-builder/scripts/validate-changes.js +61 -0
- package/templates/project-builder/scripts/workflow-helpers.js +87 -35
- package/templates/project-builder/workflow.js +231 -93
- package/vercel-server/api/config/[token].js +76 -0
- package/vercel-server/api/history/[token].js +1 -0
- package/vercel-server/api/ws/cli.js +39 -20
- package/vercel-server/local-server.js +98 -11
- package/vercel-server/public/remote/assets/index-BHvHkNOe.css +1 -0
- package/vercel-server/public/remote/assets/index-BnuR91vD.js +188 -0
- package/vercel-server/public/remote/index.html +2 -2
- package/vercel-server/ui/src/App.jsx +35 -0
- package/vercel-server/ui/src/components/ContentCard.jsx +183 -5
- package/vercel-server/ui/src/components/Header.jsx +59 -11
- package/vercel-server/ui/src/components/SettingsModal.jsx +145 -0
- package/templates/project-builder/agents/code-fixer.md +0 -50
- package/vercel-server/public/remote/assets/index-Bnvi3AUu.js +0 -173
- package/vercel-server/public/remote/assets/index-DH2uv4Ll.css +0 -1
- /package/templates/project-builder/{agents → scripts}/sanity-runner.js +0 -0
package/bin/cli.js
CHANGED
|
@@ -338,13 +338,7 @@ async function runOrResume(
|
|
|
338
338
|
remoteUrl = process.env.STATE_MACHINE_REMOTE_URL || DEFAULT_REMOTE_URL;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
//
|
|
342
|
-
if (remoteUrl) {
|
|
343
|
-
const sessionToken = ensureRemotePath(configFile, { forceNew: forceNewRemotePath });
|
|
344
|
-
await runtime.enableRemote(remoteUrl, { sessionToken, uiBaseUrl: useLocalServer });
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Set full-auto mode from CLI flag (will be merged with config.js during runWorkflow)
|
|
341
|
+
// Set full-auto mode from CLI flag BEFORE enabling remote (so session_init includes correct config)
|
|
348
342
|
if (fullAuto) {
|
|
349
343
|
runtime.workflowConfig.fullAuto = true;
|
|
350
344
|
if (autoSelectDelay !== null) {
|
|
@@ -354,6 +348,12 @@ async function runOrResume(
|
|
|
354
348
|
console.log(`\n\x1b[36m\x1b[1m⚡ Full-auto mode enabled\x1b[0m - Agent will auto-select recommended options after ${delay}s countdown`);
|
|
355
349
|
}
|
|
356
350
|
|
|
351
|
+
// Enable remote follow mode if we have a URL
|
|
352
|
+
if (remoteUrl) {
|
|
353
|
+
const sessionToken = ensureRemotePath(configFile, { forceNew: forceNewRemotePath });
|
|
354
|
+
await runtime.enableRemote(remoteUrl, { sessionToken, uiBaseUrl: useLocalServer });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
357
|
// Set non-verbose mode from CLI flag
|
|
358
358
|
if (nonVerbose) {
|
|
359
359
|
runtime.workflowConfig.nonVerbose = true;
|
package/lib/llm.js
CHANGED
|
@@ -354,15 +354,21 @@ async function executeCLI(command, promptText, options = {}, apiKeys = {}) {
|
|
|
354
354
|
|
|
355
355
|
if (baseCmd === 'claude') {
|
|
356
356
|
args.push('--print');
|
|
357
|
-
|
|
357
|
+
const permissionMode = options.cliPermissions?.claude || 'acceptEdits';
|
|
358
|
+
args.push('--permission-mode', permissionMode);
|
|
358
359
|
args.push('--output-format', 'json');
|
|
359
360
|
// Input via stdin
|
|
360
361
|
} else if (baseCmd === 'gemini') {
|
|
361
|
-
|
|
362
|
+
const approvalMode = options.cliPermissions?.gemini || 'auto_edit';
|
|
363
|
+
args.push('--approval-mode', approvalMode);
|
|
362
364
|
args.push('--output-format', 'json');
|
|
363
365
|
// Input via stdin
|
|
364
366
|
} else if (baseCmd === 'codex') {
|
|
365
367
|
ensureCodexExec();
|
|
368
|
+
const bypassMode = options.cliPermissions?.codex;
|
|
369
|
+
if (bypassMode === 'bypass') {
|
|
370
|
+
args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
371
|
+
}
|
|
366
372
|
args.push('--json');
|
|
367
373
|
args.push('-'); // Explicitly read from stdin
|
|
368
374
|
} else {
|
|
@@ -581,7 +587,12 @@ export async function llm(context, options) {
|
|
|
581
587
|
result = await executeAPI(provider, model, fullPrompt, apiKey, options);
|
|
582
588
|
} else {
|
|
583
589
|
// CLI execution - pass fullPrompt string directly
|
|
584
|
-
|
|
590
|
+
// Include cliPermissions from config if available
|
|
591
|
+
const cliOptions = {
|
|
592
|
+
...options,
|
|
593
|
+
cliPermissions: config.cliPermissions || {}
|
|
594
|
+
};
|
|
595
|
+
result = await executeCLI(modelConfig, fullPrompt, cliOptions, apiKeys);
|
|
585
596
|
}
|
|
586
597
|
|
|
587
598
|
// Record usage in agent tracker (if active)
|
package/lib/remote/client.js
CHANGED
|
@@ -89,6 +89,7 @@ export class RemoteClient {
|
|
|
89
89
|
* @param {string} options.serverUrl - Base URL of remote server (e.g., https://example.vercel.app)
|
|
90
90
|
* @param {string} options.workflowName - Name of the workflow
|
|
91
91
|
* @param {function} options.onInteractionResponse - Callback when interaction response received
|
|
92
|
+
* @param {function} [options.onConfigUpdate] - Callback when config update received from browser
|
|
92
93
|
* @param {function} [options.onStatusChange] - Callback when connection status changes
|
|
93
94
|
* @param {string} [options.sessionToken] - Optional session token to reuse
|
|
94
95
|
* @param {boolean} [options.uiBaseUrl] - If true, return base URL for UI instead of /s/{token}
|
|
@@ -97,6 +98,7 @@ export class RemoteClient {
|
|
|
97
98
|
this.serverUrl = options.serverUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
98
99
|
this.workflowName = options.workflowName;
|
|
99
100
|
this.onInteractionResponse = options.onInteractionResponse;
|
|
101
|
+
this.onConfigUpdate = options.onConfigUpdate || (() => {});
|
|
100
102
|
this.onStatusChange = options.onStatusChange || (() => {});
|
|
101
103
|
this.uiBaseUrl = Boolean(options.uiBaseUrl);
|
|
102
104
|
|
|
@@ -166,16 +168,18 @@ export class RemoteClient {
|
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
/**
|
|
169
|
-
* Send initial session info with history
|
|
171
|
+
* Send initial session info with history and config
|
|
170
172
|
* @param {Array} history - Array of history entries
|
|
173
|
+
* @param {object} [config] - Optional workflow config (fullAuto, autoSelectDelay)
|
|
171
174
|
*/
|
|
172
|
-
async sendSessionInit(history = []) {
|
|
175
|
+
async sendSessionInit(history = [], config = null) {
|
|
173
176
|
this.initialHistorySent = true;
|
|
174
177
|
await this.send({
|
|
175
178
|
type: 'session_init',
|
|
176
179
|
sessionToken: this.sessionToken,
|
|
177
180
|
workflowName: this.workflowName,
|
|
178
181
|
history,
|
|
182
|
+
config,
|
|
179
183
|
});
|
|
180
184
|
}
|
|
181
185
|
|
|
@@ -231,7 +235,7 @@ export class RemoteClient {
|
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
/**
|
|
234
|
-
* Poll for interaction responses
|
|
238
|
+
* Poll for interaction responses and config updates
|
|
235
239
|
* Uses 35s timeout to stay under Vercel's 50s limit with buffer
|
|
236
240
|
*/
|
|
237
241
|
async poll() {
|
|
@@ -246,20 +250,29 @@ export class RemoteClient {
|
|
|
246
250
|
consecutiveErrors = 0; // Reset on success
|
|
247
251
|
|
|
248
252
|
if (response.status === 200 && response.data) {
|
|
249
|
-
const { type, slug, targetKey, response: interactionResponse } = response.data;
|
|
253
|
+
const { type, slug, targetKey, response: interactionResponse, fullAuto, autoSelectDelay, stop } = response.data;
|
|
250
254
|
|
|
251
255
|
if (type === 'interaction_response' && this.onInteractionResponse) {
|
|
252
256
|
// Confirm receipt BEFORE processing - removes from Redis pending queue
|
|
253
|
-
// This ensures we don't lose the interaction if processing fails
|
|
254
257
|
try {
|
|
255
258
|
const confirmUrl = `${this.serverUrl}/api/ws/cli?token=${this.sessionToken}`;
|
|
256
259
|
await makeRequest(confirmUrl, { method: 'DELETE' }, null, 10000);
|
|
257
260
|
} catch (err) {
|
|
258
|
-
// Non-fatal - interaction will be re-delivered on next poll
|
|
259
261
|
console.error(`${C.dim}Remote: Failed to confirm receipt: ${err.message}${C.reset}`);
|
|
260
262
|
}
|
|
261
263
|
|
|
262
264
|
this.onInteractionResponse(slug, targetKey, interactionResponse);
|
|
265
|
+
} else if (type === 'config_update') {
|
|
266
|
+
// Confirm receipt of config update
|
|
267
|
+
try {
|
|
268
|
+
const confirmUrl = `${this.serverUrl}/api/ws/cli?token=${this.sessionToken}&type=config`;
|
|
269
|
+
await makeRequest(confirmUrl, { method: 'DELETE' }, null, 10000);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
console.error(`${C.dim}Remote: Failed to confirm config receipt: ${err.message}${C.reset}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Call config update callback
|
|
275
|
+
this.onConfigUpdate({ fullAuto, autoSelectDelay, stop });
|
|
263
276
|
}
|
|
264
277
|
}
|
|
265
278
|
|
package/lib/runtime/agent.js
CHANGED
|
@@ -13,6 +13,8 @@ import { pathToFileURL } from 'url';
|
|
|
13
13
|
import { getCurrentRuntime } from './runtime.js';
|
|
14
14
|
import { formatInteractionPrompt } from './interaction.js';
|
|
15
15
|
import { withChangeTracking } from './track-changes.js';
|
|
16
|
+
import { resolveUnknownModel } from './model-resolution.js';
|
|
17
|
+
import { detectAvailableCLIs } from '../llm.js';
|
|
16
18
|
|
|
17
19
|
const require = createRequire(import.meta.url);
|
|
18
20
|
|
|
@@ -374,6 +376,23 @@ async function executeMDAgent(runtime, agentPath, name, params, options = {}) {
|
|
|
374
376
|
|
|
375
377
|
const model = config.model || 'fast';
|
|
376
378
|
|
|
379
|
+
// Resolve model alias to actual model config for display
|
|
380
|
+
let resolvedModel = baseConfig.models?.[model];
|
|
381
|
+
if (!resolvedModel) {
|
|
382
|
+
// Auto-resolve unknown model (same logic as llm.js)
|
|
383
|
+
try {
|
|
384
|
+
resolvedModel = await resolveUnknownModel(model, baseConfig, runtime.workflowDir, {
|
|
385
|
+
availableCLIs: detectAvailableCLIs()
|
|
386
|
+
});
|
|
387
|
+
// Cache it for future use
|
|
388
|
+
if (!baseConfig.models) baseConfig.models = {};
|
|
389
|
+
baseConfig.models[model] = resolvedModel;
|
|
390
|
+
runtime.workflowConfig.models[model] = resolvedModel;
|
|
391
|
+
} catch {
|
|
392
|
+
resolvedModel = model; // Fallback to alias if resolution fails
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
377
396
|
const fullPrompt = buildPrompt(context, {
|
|
378
397
|
model,
|
|
379
398
|
prompt: interpolatedPrompt,
|
|
@@ -381,7 +400,7 @@ async function executeMDAgent(runtime, agentPath, name, params, options = {}) {
|
|
|
381
400
|
responseType: config.response
|
|
382
401
|
});
|
|
383
402
|
|
|
384
|
-
await logAgentStart(runtime, name, fullPrompt);
|
|
403
|
+
await logAgentStart(runtime, name, fullPrompt, resolvedModel, model);
|
|
385
404
|
|
|
386
405
|
console.log(` Using model: ${model}`);
|
|
387
406
|
|
|
@@ -647,7 +666,7 @@ ${content}
|
|
|
647
666
|
return response;
|
|
648
667
|
}
|
|
649
668
|
|
|
650
|
-
async function logAgentStart(runtime, name, prompt) {
|
|
669
|
+
async function logAgentStart(runtime, name, prompt, model = null, modelAlias = null) {
|
|
651
670
|
if (runtime._agentResumeFlags?.has(name)) {
|
|
652
671
|
runtime._agentResumeFlags.delete(name);
|
|
653
672
|
await runtime.prependHistory({
|
|
@@ -666,5 +685,13 @@ async function logAgentStart(runtime, name, prompt) {
|
|
|
666
685
|
entry.prompt = prompt;
|
|
667
686
|
}
|
|
668
687
|
|
|
688
|
+
if (model) {
|
|
689
|
+
entry.model = model;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (modelAlias && modelAlias !== model) {
|
|
693
|
+
entry.modelAlias = modelAlias;
|
|
694
|
+
}
|
|
695
|
+
|
|
669
696
|
await runtime.prependHistory(entry);
|
|
670
697
|
}
|
package/lib/runtime/prompt.js
CHANGED
|
@@ -105,7 +105,7 @@ export async function askHuman(question, options = {}) {
|
|
|
105
105
|
await runtime.prependHistory({
|
|
106
106
|
event: 'PROMPT_ANSWERED',
|
|
107
107
|
slug,
|
|
108
|
-
answer: normalizedAnswer
|
|
108
|
+
answer: normalizedAnswer
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
return normalizedAnswer;
|
package/lib/runtime/runtime.js
CHANGED
|
@@ -87,7 +87,14 @@ export class WorkflowRuntime {
|
|
|
87
87
|
// Full-auto mode (auto-select first option for choice interactions)
|
|
88
88
|
fullAuto: false,
|
|
89
89
|
maxQuickFixAttempts: 10,
|
|
90
|
-
autoSelectDelay: 20 // seconds before auto-selecting in full-auto mode
|
|
90
|
+
autoSelectDelay: 20, // seconds before auto-selecting in full-auto mode
|
|
91
|
+
// CLI permission modes (configurable per tool)
|
|
92
|
+
cliPermissions: {
|
|
93
|
+
claude: 'acceptEdits',
|
|
94
|
+
gemini: 'auto_edit'
|
|
95
|
+
},
|
|
96
|
+
// Protected paths - prevents DELETION only (modifications allowed)
|
|
97
|
+
protectedPaths: []
|
|
91
98
|
};
|
|
92
99
|
|
|
93
100
|
// Load steering
|
|
@@ -384,6 +391,7 @@ export class WorkflowRuntime {
|
|
|
384
391
|
const cfg = configModule.config || configModule.default || {};
|
|
385
392
|
// Preserve CLI-set fullAuto (it takes precedence over config.js)
|
|
386
393
|
const cliFullAuto = this.workflowConfig.fullAuto;
|
|
394
|
+
const defaultCliPermissions = { claude: 'acceptEdits', gemini: 'auto_edit' };
|
|
387
395
|
this.workflowConfig = {
|
|
388
396
|
models: cfg.models || {},
|
|
389
397
|
apiKeys: cfg.apiKeys || {},
|
|
@@ -396,7 +404,11 @@ export class WorkflowRuntime {
|
|
|
396
404
|
// Full-auto mode: CLI flag takes precedence, then config.js, then default false
|
|
397
405
|
fullAuto: cliFullAuto || cfg.fullAuto || false,
|
|
398
406
|
maxQuickFixAttempts: cfg.maxQuickFixAttempts ?? 10,
|
|
399
|
-
autoSelectDelay: cfg.autoSelectDelay ?? this.workflowConfig.autoSelectDelay // seconds before auto-selecting
|
|
407
|
+
autoSelectDelay: cfg.autoSelectDelay ?? this.workflowConfig.autoSelectDelay, // seconds before auto-selecting
|
|
408
|
+
// CLI permission modes (merge with defaults)
|
|
409
|
+
cliPermissions: { ...defaultCliPermissions, ...(cfg.cliPermissions || {}) },
|
|
410
|
+
// Protected paths - prevents DELETION only (modifications allowed)
|
|
411
|
+
protectedPaths: cfg.protectedPaths || []
|
|
400
412
|
};
|
|
401
413
|
|
|
402
414
|
// Import workflow module
|
|
@@ -585,6 +597,31 @@ export class WorkflowRuntime {
|
|
|
585
597
|
}
|
|
586
598
|
}
|
|
587
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Handle config update from remote browser UI
|
|
602
|
+
* Called by RemoteClient when it receives a config_update message
|
|
603
|
+
*/
|
|
604
|
+
handleRemoteConfigUpdate(config) {
|
|
605
|
+
if (config.fullAuto !== undefined) {
|
|
606
|
+
const wasFullAuto = this.workflowConfig.fullAuto;
|
|
607
|
+
this.workflowConfig.fullAuto = config.fullAuto;
|
|
608
|
+
if (wasFullAuto !== config.fullAuto) {
|
|
609
|
+
console.log(`${C.cyan}Remote: Full-auto mode ${config.fullAuto ? 'enabled' : 'disabled'}${C.reset}`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (config.autoSelectDelay !== undefined) {
|
|
614
|
+
this.workflowConfig.autoSelectDelay = config.autoSelectDelay;
|
|
615
|
+
console.log(`${C.dim}Remote: Auto-select delay set to ${config.autoSelectDelay}s${C.reset}`);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (config.stop) {
|
|
619
|
+
console.log(`\n${C.yellow}${C.bold}Remote: Stop requested${C.reset}`);
|
|
620
|
+
// Trigger graceful shutdown
|
|
621
|
+
process.emit('SIGINT');
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
588
625
|
/**
|
|
589
626
|
* Read the user's response from an interaction file
|
|
590
627
|
*/
|
|
@@ -779,6 +816,9 @@ export class WorkflowRuntime {
|
|
|
779
816
|
onInteractionResponse: (slug, targetKey, response) => {
|
|
780
817
|
this.handleRemoteInteraction(slug, targetKey, response);
|
|
781
818
|
},
|
|
819
|
+
onConfigUpdate: (config) => {
|
|
820
|
+
this.handleRemoteConfigUpdate(config);
|
|
821
|
+
},
|
|
782
822
|
onStatusChange: (status) => {
|
|
783
823
|
if (status === 'disconnected') {
|
|
784
824
|
console.log(`${C.yellow}Remote: Connection lost, attempting to reconnect...${C.reset}`);
|
|
@@ -790,10 +830,14 @@ export class WorkflowRuntime {
|
|
|
790
830
|
|
|
791
831
|
await this.remoteClient.connect();
|
|
792
832
|
|
|
793
|
-
// Send existing history if connected
|
|
833
|
+
// Send existing history if connected, including current config
|
|
794
834
|
if (this.remoteClient.connected) {
|
|
795
835
|
const history = this.loadHistory();
|
|
796
|
-
|
|
836
|
+
const config = {
|
|
837
|
+
fullAuto: this.workflowConfig.fullAuto || false,
|
|
838
|
+
autoSelectDelay: this.workflowConfig.autoSelectDelay ?? 20,
|
|
839
|
+
};
|
|
840
|
+
await this.remoteClient.sendSessionInit(history, config);
|
|
797
841
|
}
|
|
798
842
|
|
|
799
843
|
this.remoteEnabled = true;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import path from 'path';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
10
11
|
import {
|
|
11
12
|
captureBaseline,
|
|
12
13
|
detectChanges,
|
|
@@ -38,9 +39,52 @@ export async function withChangeTracking(runtime, agentName, fn) {
|
|
|
38
39
|
// Detect changes made during agent execution
|
|
39
40
|
const changes = await detectChanges(projectRoot, baseline, ignorePatterns);
|
|
40
41
|
|
|
42
|
+
// Validate protected paths (only checks deletions)
|
|
43
|
+
const validation = validateProtectedPaths(runtime, changes);
|
|
44
|
+
if (!validation.valid) {
|
|
45
|
+
console.warn(`[protected-paths] Violations detected by agent '${agentName}':`);
|
|
46
|
+
validation.violations.forEach(v => console.warn(` - ${v}`));
|
|
47
|
+
throw new Error(`Protected path violations: ${validation.violations.join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
// Update fileTree with detected changes
|
|
42
51
|
applyChangesToFileTree(runtime, changes, agentName);
|
|
43
52
|
|
|
53
|
+
// Log git diff to history when files change
|
|
54
|
+
if (changes.created.length || changes.modified.length || changes.deleted.length) {
|
|
55
|
+
try {
|
|
56
|
+
const diff = execSync('git diff HEAD', {
|
|
57
|
+
cwd: projectRoot,
|
|
58
|
+
encoding: 'utf-8',
|
|
59
|
+
maxBuffer: 1024 * 1024 // 1MB limit
|
|
60
|
+
}).trim();
|
|
61
|
+
|
|
62
|
+
if (diff) {
|
|
63
|
+
await runtime.prependHistory({
|
|
64
|
+
type: 'file_changes',
|
|
65
|
+
agent: agentName,
|
|
66
|
+
summary: {
|
|
67
|
+
created: changes.created.length,
|
|
68
|
+
modified: changes.modified.length,
|
|
69
|
+
deleted: changes.deleted.length
|
|
70
|
+
},
|
|
71
|
+
diff: diff.slice(0, 50000) // Truncate if too large
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
// Git diff failed, log summary only
|
|
76
|
+
await runtime.prependHistory({
|
|
77
|
+
type: 'file_changes',
|
|
78
|
+
agent: agentName,
|
|
79
|
+
summary: {
|
|
80
|
+
created: changes.created.length,
|
|
81
|
+
modified: changes.modified.length,
|
|
82
|
+
deleted: changes.deleted.length
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
44
88
|
// Merge _files annotations if present (preserves existing data unless explicitly overwritten)
|
|
45
89
|
if (result && typeof result === 'object' && Array.isArray(result._files)) {
|
|
46
90
|
mergeAnnotations(runtime, result._files);
|
|
@@ -49,6 +93,46 @@ export async function withChangeTracking(runtime, agentName, fn) {
|
|
|
49
93
|
return result;
|
|
50
94
|
}
|
|
51
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Validate that protected paths were not deleted.
|
|
98
|
+
* Only checks for DELETIONS - modifications are allowed.
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} runtime - The workflow runtime instance
|
|
101
|
+
* @param {Object} changes - Detected changes { created, modified, deleted, renamed }
|
|
102
|
+
* @returns {{ valid: boolean, violations: string[] }}
|
|
103
|
+
*/
|
|
104
|
+
export function validateProtectedPaths(runtime, changes) {
|
|
105
|
+
const protectedPaths = runtime.workflowConfig.protectedPaths || [];
|
|
106
|
+
const violations = [];
|
|
107
|
+
|
|
108
|
+
// Only check DELETED files - modifications are allowed
|
|
109
|
+
for (const deleted of changes.deleted || []) {
|
|
110
|
+
for (const pattern of protectedPaths) {
|
|
111
|
+
if (matchesPattern(deleted, pattern)) {
|
|
112
|
+
violations.push(`Cannot delete protected file: ${deleted}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { valid: violations.length === 0, violations };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Simple pattern matching for protected paths.
|
|
122
|
+
* Supports exact match and prefix wildcards (e.g., '.env*' matches '.env', '.env.local')
|
|
123
|
+
*/
|
|
124
|
+
function matchesPattern(filePath, pattern) {
|
|
125
|
+
// Normalize both for comparison
|
|
126
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
127
|
+
const normalizedPattern = pattern.replace(/\\/g, '/');
|
|
128
|
+
|
|
129
|
+
if (normalizedPattern.endsWith('*')) {
|
|
130
|
+
// Prefix wildcard: '.env*' matches '.env', '.env.local', etc.
|
|
131
|
+
return normalizedPath.startsWith(normalizedPattern.slice(0, -1));
|
|
132
|
+
}
|
|
133
|
+
return normalizedPath === normalizedPattern;
|
|
134
|
+
}
|
|
135
|
+
|
|
52
136
|
/**
|
|
53
137
|
* Apply detected file changes to the runtime's fileTree.
|
|
54
138
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
model: high
|
|
3
3
|
format: json
|
|
4
|
+
description: "Code phase: Implements the task by writing production code and tests"
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Code Writer Agent
|
|
@@ -9,6 +10,11 @@ You are a senior software developer. Implement the task according to specificati
|
|
|
9
10
|
|
|
10
11
|
## Instructions
|
|
11
12
|
|
|
13
|
+
**IMPORTANT: Use your file tools to create and write files directly to disk.** Do not embed code in JSON. Use your native file creation capabilities to:
|
|
14
|
+
1. Create directories as needed
|
|
15
|
+
2. Write each file with full production code
|
|
16
|
+
3. Report what files you created
|
|
17
|
+
|
|
12
18
|
Implement the task following these principles:
|
|
13
19
|
|
|
14
20
|
**Code Quality:**
|
|
@@ -33,22 +39,14 @@ Implement the task following these principles:
|
|
|
33
39
|
|
|
34
40
|
## Output Format
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
After writing all files to disk using your file tools, return a valid JSON object:
|
|
37
43
|
|
|
38
44
|
{
|
|
39
45
|
"implementation": {
|
|
40
46
|
"summary": "Brief description of what was implemented",
|
|
41
|
-
"
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
"purpose": "Main implementation",
|
|
45
|
-
"code": "// Full code content here\nfunction example() {\n return 'hello';\n}"
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"path": "src/feature.test.js",
|
|
49
|
-
"purpose": "Test file",
|
|
50
|
-
"code": "// Test code here\ndescribe('feature', () => {\n it('works', () => {});\n});"
|
|
51
|
-
}
|
|
47
|
+
"filesWritten": [
|
|
48
|
+
{"path": "src/feature.js", "purpose": "Main implementation"},
|
|
49
|
+
{"path": "src/feature.test.js", "purpose": "Test file"}
|
|
52
50
|
],
|
|
53
51
|
"dependencies": [
|
|
54
52
|
{"name": "lodash", "version": "^4.17.21", "reason": "Utility functions"}
|
|
@@ -65,3 +63,11 @@ Return a valid JSON object:
|
|
|
65
63
|
}
|
|
66
64
|
|
|
67
65
|
Write production-quality code. This is not a prototype.
|
|
66
|
+
|
|
67
|
+
## Safeguards
|
|
68
|
+
|
|
69
|
+
**NEVER modify or remove:**
|
|
70
|
+
- `.env` or `.env.*` files
|
|
71
|
+
- The `agent-state-machine` dependency in `package.json`
|
|
72
|
+
|
|
73
|
+
You may add new dependencies but must preserve existing critical ones.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
model: high
|
|
3
|
+
format: json
|
|
4
|
+
description: "Post-code phase: Fixes issues found during review or sanity checks"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Code Fixer Agent
|
|
8
|
+
|
|
9
|
+
You fix specific issues in existing code based on sanity check failures.
|
|
10
|
+
|
|
11
|
+
## How to Fix
|
|
12
|
+
|
|
13
|
+
**IMPORTANT: Use your file tools to read and write files directly.**
|
|
14
|
+
|
|
15
|
+
1. Read the file(s) that need fixing using your file tools
|
|
16
|
+
2. Analyze the error and identify the root cause
|
|
17
|
+
3. Apply the fix by writing the corrected file back to disk
|
|
18
|
+
4. Report what you fixed
|
|
19
|
+
|
|
20
|
+
## Critical Guidelines
|
|
21
|
+
|
|
22
|
+
**DO NOT** disable, skip, or remove failing tests to make them pass.
|
|
23
|
+
Your fixes must address the actual underlying code issues that cause tests to fail.
|
|
24
|
+
|
|
25
|
+
- Never add `.skip()`, `.todo()`, or comment out tests
|
|
26
|
+
- Never modify test expectations to match broken behavior
|
|
27
|
+
- Never delete test files or test cases
|
|
28
|
+
- Never wrap tests in `try/catch` to swallow errors
|
|
29
|
+
- Fix the implementation code to pass existing tests
|
|
30
|
+
- Fix test setup/teardown issues if the tests themselves are misconfigured
|
|
31
|
+
- Update tests ONLY if the original requirements were misunderstood
|
|
32
|
+
|
|
33
|
+
If the issue truly cannot be fixed within the current architecture, set `"confidence": "low"` and explain why in the analysis.
|
|
34
|
+
|
|
35
|
+
## Input
|
|
36
|
+
- task: Task definition
|
|
37
|
+
- failedChecks: Failed checks with specific errors
|
|
38
|
+
- filePaths: Paths to files that may need fixing
|
|
39
|
+
|
|
40
|
+
## Output Format
|
|
41
|
+
|
|
42
|
+
After fixing the files using your file tools, return:
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
"analysis": {
|
|
46
|
+
"rootCauses": ["What caused each failure"],
|
|
47
|
+
"fixApproach": "Strategy for fixing"
|
|
48
|
+
},
|
|
49
|
+
"fixesApplied": [
|
|
50
|
+
{
|
|
51
|
+
"path": "src/feature.js",
|
|
52
|
+
"description": "Fixed the validation logic to handle edge case"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"expectedResolutions": ["Which checks should now pass"],
|
|
56
|
+
"confidence": "high|medium|low"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Focus on minimal, targeted fixes. Don't rewrite entire files unless necessary.
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
model: high
|
|
3
3
|
format: json
|
|
4
|
+
description: "Post-code phase: Reviews implementation for quality and correctness"
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Code Reviewer Agent
|
|
7
8
|
|
|
8
9
|
You are a senior code reviewer. Review implementations for quality, correctness, and best practices.
|
|
9
10
|
|
|
11
|
+
## How to Review
|
|
12
|
+
|
|
13
|
+
**Use your file tools to read the files that need reviewing.** You will receive a list of file paths to review. Read each file's contents directly from disk to perform your review.
|
|
14
|
+
|
|
10
15
|
## Instructions
|
|
11
16
|
|
|
12
17
|
Perform a thorough code review covering:
|
|
@@ -33,6 +38,11 @@ Perform a thorough code review covering:
|
|
|
33
38
|
- Are tests meaningful (not just coverage padding)?
|
|
34
39
|
- Are edge cases tested?
|
|
35
40
|
|
|
41
|
+
## Input
|
|
42
|
+
- task: Task definition with title and description
|
|
43
|
+
- filesToReview: Array of file paths to review
|
|
44
|
+
- implementationSummary: Brief description of what was implemented
|
|
45
|
+
|
|
36
46
|
## Output Format
|
|
37
47
|
|
|
38
48
|
Return a valid JSON object:
|