neuro-cli 4.1.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/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Auto Mode
|
|
3
|
+
// Full autonomous execution mode similar to Claude Code's Auto Mode
|
|
4
|
+
// Skips approval prompts while maintaining safety guardrails
|
|
5
|
+
// Supports /goal and /routine commands for autonomous workflows
|
|
6
|
+
// ============================================================
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync, } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { randomUUID } from 'crypto';
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Dangerous-command patterns – always blocked regardless of safety level
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const ALWAYS_BLOCKED_COMMANDS = [
|
|
16
|
+
'rm -rf /',
|
|
17
|
+
'rm -rf /*',
|
|
18
|
+
'sudo rm -rf',
|
|
19
|
+
'mkfs',
|
|
20
|
+
'dd if=',
|
|
21
|
+
':(){ :|:& };:', // fork bomb
|
|
22
|
+
'chmod 777 /',
|
|
23
|
+
'chown -R root',
|
|
24
|
+
'curl | sh',
|
|
25
|
+
'wget | sh',
|
|
26
|
+
'curl | bash',
|
|
27
|
+
'wget | bash',
|
|
28
|
+
'> /dev/sda',
|
|
29
|
+
'mv / ',
|
|
30
|
+
'shutdown',
|
|
31
|
+
'reboot',
|
|
32
|
+
'init 0',
|
|
33
|
+
'init 6',
|
|
34
|
+
'systemctl poweroff',
|
|
35
|
+
'systemctl reboot',
|
|
36
|
+
];
|
|
37
|
+
const CONSERVATIVE_BLOCKED_COMMANDS = [
|
|
38
|
+
'rm -rf',
|
|
39
|
+
'rm -r',
|
|
40
|
+
'sudo',
|
|
41
|
+
'chmod',
|
|
42
|
+
'chown',
|
|
43
|
+
'kill -9',
|
|
44
|
+
'pkill',
|
|
45
|
+
'killall',
|
|
46
|
+
'pip uninstall',
|
|
47
|
+
'npm uninstall',
|
|
48
|
+
'npm publish',
|
|
49
|
+
'git push --force',
|
|
50
|
+
'git reset --hard',
|
|
51
|
+
'git clean -fd',
|
|
52
|
+
'drop table',
|
|
53
|
+
'drop database',
|
|
54
|
+
'truncate table',
|
|
55
|
+
];
|
|
56
|
+
const MODERATE_BLOCKED_COMMANDS = [
|
|
57
|
+
'rm -rf',
|
|
58
|
+
'sudo rm',
|
|
59
|
+
'npm publish',
|
|
60
|
+
'git push --force',
|
|
61
|
+
'drop table',
|
|
62
|
+
'drop database',
|
|
63
|
+
];
|
|
64
|
+
const ALWAYS_BLOCKED_PATTERNS = [
|
|
65
|
+
'**/.env',
|
|
66
|
+
'**/.env.*',
|
|
67
|
+
'**/id_rsa*',
|
|
68
|
+
'**/id_ed25519*',
|
|
69
|
+
'**/*.pem',
|
|
70
|
+
'**/*.key',
|
|
71
|
+
'**/credentials.json',
|
|
72
|
+
'**/secrets.*',
|
|
73
|
+
'**/.ssh/*',
|
|
74
|
+
'**/.gnupg/*',
|
|
75
|
+
'**/etc/shadow',
|
|
76
|
+
'**/etc/passwd',
|
|
77
|
+
];
|
|
78
|
+
const CONSERVATIVE_BLOCKED_PATTERNS = [
|
|
79
|
+
'**/package.json',
|
|
80
|
+
'**/package-lock.json',
|
|
81
|
+
'**/yarn.lock',
|
|
82
|
+
'**/.gitignore',
|
|
83
|
+
'**/tsconfig.json',
|
|
84
|
+
'**/next.config.*',
|
|
85
|
+
'**/webpack.config.*',
|
|
86
|
+
'**/docker-compose.*',
|
|
87
|
+
'**/Dockerfile*',
|
|
88
|
+
];
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// AutoMode class
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
const NEURO_DIR = join(homedir(), '.neuro');
|
|
93
|
+
const AUTO_STATE_FILE = join(NEURO_DIR, 'auto-mode-state.json');
|
|
94
|
+
const GOALS_FILE = join(NEURO_DIR, 'auto-mode-goals.json');
|
|
95
|
+
const ROUTINES_FILE = join(NEURO_DIR, 'auto-mode-routines.json');
|
|
96
|
+
const CHECKPOINTS_DIR = join(NEURO_DIR, 'auto-checkpoints');
|
|
97
|
+
export class AutoMode {
|
|
98
|
+
config;
|
|
99
|
+
stats;
|
|
100
|
+
goals = new Map();
|
|
101
|
+
routines = new Map();
|
|
102
|
+
checkpoints = [];
|
|
103
|
+
startTime = 0;
|
|
104
|
+
running = false;
|
|
105
|
+
abortController = null;
|
|
106
|
+
onStatusChange;
|
|
107
|
+
constructor(config, onStatusChange) {
|
|
108
|
+
this.config = {
|
|
109
|
+
enabled: false,
|
|
110
|
+
safetyLevel: 'moderate',
|
|
111
|
+
maxIterations: 50,
|
|
112
|
+
maxCost: 0,
|
|
113
|
+
maxTimeMs: 0,
|
|
114
|
+
blockedCommands: [...ALWAYS_BLOCKED_COMMANDS],
|
|
115
|
+
blockedPatterns: [...ALWAYS_BLOCKED_PATTERNS],
|
|
116
|
+
autoCommit: false,
|
|
117
|
+
autoTest: false,
|
|
118
|
+
pauseOnError: true,
|
|
119
|
+
...config,
|
|
120
|
+
};
|
|
121
|
+
this.onStatusChange = onStatusChange;
|
|
122
|
+
this.stats = this.freshStats();
|
|
123
|
+
this.loadState();
|
|
124
|
+
}
|
|
125
|
+
// -----------------------------------------------------------------------
|
|
126
|
+
// Enable / Disable
|
|
127
|
+
// -----------------------------------------------------------------------
|
|
128
|
+
/** Enable auto mode – skip all approval prompts, run safety checks in background */
|
|
129
|
+
enable() {
|
|
130
|
+
this.config.enabled = true;
|
|
131
|
+
this.emitStatus('enabled');
|
|
132
|
+
this.persistState();
|
|
133
|
+
}
|
|
134
|
+
/** Disable auto mode, return to interactive */
|
|
135
|
+
disable() {
|
|
136
|
+
this.config.enabled = false;
|
|
137
|
+
this.running = false;
|
|
138
|
+
if (this.abortController) {
|
|
139
|
+
this.abortController.abort();
|
|
140
|
+
this.abortController = null;
|
|
141
|
+
}
|
|
142
|
+
this.emitStatus('disabled');
|
|
143
|
+
this.persistState();
|
|
144
|
+
}
|
|
145
|
+
/** Check whether auto mode is currently enabled */
|
|
146
|
+
isEnabled() {
|
|
147
|
+
return this.config.enabled;
|
|
148
|
+
}
|
|
149
|
+
/** Check whether auto mode is currently running a task */
|
|
150
|
+
isRunning() {
|
|
151
|
+
return this.running;
|
|
152
|
+
}
|
|
153
|
+
// -----------------------------------------------------------------------
|
|
154
|
+
// Configuration
|
|
155
|
+
// -----------------------------------------------------------------------
|
|
156
|
+
getConfig() {
|
|
157
|
+
return Object.freeze({ ...this.config });
|
|
158
|
+
}
|
|
159
|
+
updateConfig(updates) {
|
|
160
|
+
// Merge blocked lists so ALWAYS_BLOCKED are never removed
|
|
161
|
+
if (updates.blockedCommands) {
|
|
162
|
+
const merged = new Set([...ALWAYS_BLOCKED_COMMANDS, ...updates.blockedCommands]);
|
|
163
|
+
updates.blockedCommands = Array.from(merged);
|
|
164
|
+
}
|
|
165
|
+
if (updates.blockedPatterns) {
|
|
166
|
+
const merged = new Set([...ALWAYS_BLOCKED_PATTERNS, ...updates.blockedPatterns]);
|
|
167
|
+
updates.blockedPatterns = Array.from(merged);
|
|
168
|
+
}
|
|
169
|
+
Object.assign(this.config, updates);
|
|
170
|
+
this.persistState();
|
|
171
|
+
}
|
|
172
|
+
setSafetyLevel(level) {
|
|
173
|
+
this.config.safetyLevel = level;
|
|
174
|
+
// Re-apply the pattern / command block lists for the new level
|
|
175
|
+
this.config.blockedCommands = this.buildBlockedCommands(level);
|
|
176
|
+
this.config.blockedPatterns = this.buildBlockedPatterns(level);
|
|
177
|
+
this.persistState();
|
|
178
|
+
}
|
|
179
|
+
// -----------------------------------------------------------------------
|
|
180
|
+
// Core autonomous execution
|
|
181
|
+
// -----------------------------------------------------------------------
|
|
182
|
+
/**
|
|
183
|
+
* Run a task fully autonomously.
|
|
184
|
+
*
|
|
185
|
+
* The engine will be called repeatedly until:
|
|
186
|
+
* - maxIterations is reached
|
|
187
|
+
* - maxCost is exceeded
|
|
188
|
+
* - maxTimeMs is exceeded
|
|
189
|
+
* - The task appears complete (engine signals no further action needed)
|
|
190
|
+
* - An error occurs and pauseOnError is true
|
|
191
|
+
* - The user aborts
|
|
192
|
+
*/
|
|
193
|
+
async executeAuto(prompt, engine, maxIterations) {
|
|
194
|
+
if (this.running) {
|
|
195
|
+
return { ok: false, iterations: 0, finalText: '', error: 'Auto mode is already running a task' };
|
|
196
|
+
}
|
|
197
|
+
const limit = maxIterations ?? this.config.maxIterations;
|
|
198
|
+
this.running = true;
|
|
199
|
+
this.abortController = new AbortController();
|
|
200
|
+
this.startTime = Date.now();
|
|
201
|
+
this.stats = this.freshStats();
|
|
202
|
+
this.emitStatus('running');
|
|
203
|
+
const result = { ok: true, iterations: 0, finalText: '' };
|
|
204
|
+
try {
|
|
205
|
+
let currentPrompt = prompt;
|
|
206
|
+
for (let i = 0; i < limit; i++) {
|
|
207
|
+
// ---- Abort check ----
|
|
208
|
+
if (this.abortController.signal.aborted) {
|
|
209
|
+
result.ok = false;
|
|
210
|
+
result.error = 'Aborted by user';
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
// ---- Time limit ----
|
|
214
|
+
if (this.config.maxTimeMs > 0 && Date.now() - this.startTime > this.config.maxTimeMs) {
|
|
215
|
+
result.error = `Time limit of ${this.config.maxTimeMs}ms exceeded`;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
// ---- Cost limit ----
|
|
219
|
+
if (this.config.maxCost > 0 && this.stats.totalCost >= this.config.maxCost) {
|
|
220
|
+
result.error = `Cost limit of $${this.config.maxCost} exceeded`;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
// ---- Safety pre-check ----
|
|
224
|
+
const safetyResult = this.preFlightSafetyCheck(currentPrompt);
|
|
225
|
+
if (!safetyResult.allowed) {
|
|
226
|
+
this.stats.blockedOperations++;
|
|
227
|
+
result.error = `Safety check blocked execution: ${safetyResult.reason}`;
|
|
228
|
+
if (this.config.pauseOnError)
|
|
229
|
+
break;
|
|
230
|
+
// If not pausing on error, try to continue with a modified prompt
|
|
231
|
+
currentPrompt = `The previous action was blocked for safety reasons (${safetyResult.reason}). ` +
|
|
232
|
+
`Please try a different, safer approach to accomplish the goal. Original task: ${prompt}`;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// ---- Execute ----
|
|
236
|
+
try {
|
|
237
|
+
const response = await engine.runPrompt(currentPrompt);
|
|
238
|
+
this.stats.iterations = i + 1;
|
|
239
|
+
this.stats.filesChanged += response.filesChanged;
|
|
240
|
+
this.stats.commandsRun += response.commandsRun;
|
|
241
|
+
this.stats.totalCost += response.cost;
|
|
242
|
+
this.stats.timeElapsedMs = Date.now() - this.startTime;
|
|
243
|
+
if (response.error) {
|
|
244
|
+
this.stats.errors++;
|
|
245
|
+
if (this.config.pauseOnError) {
|
|
246
|
+
result.error = response.error;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
result.iterations = i + 1;
|
|
251
|
+
result.finalText = response.text;
|
|
252
|
+
// ---- Auto-commit ----
|
|
253
|
+
if (this.config.autoCommit && response.filesChanged > 0) {
|
|
254
|
+
this.autoGitCommit(i + 1);
|
|
255
|
+
}
|
|
256
|
+
// ---- Auto-test ----
|
|
257
|
+
if (this.config.autoTest && response.filesChanged > 0) {
|
|
258
|
+
const testResult = this.autoRunTests();
|
|
259
|
+
if (!testResult.passed && this.config.pauseOnError) {
|
|
260
|
+
result.error = `Tests failed after iteration ${i + 1}`;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// ---- Checkpoint ----
|
|
265
|
+
if (i > 0 && i % 5 === 0) {
|
|
266
|
+
this.createCheckpoint(i, undefined, undefined);
|
|
267
|
+
}
|
|
268
|
+
// ---- Detect completion ----
|
|
269
|
+
if (this.detectCompletion(response.text)) {
|
|
270
|
+
result.completed = true;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
// Feed the response back as context for the next iteration
|
|
274
|
+
currentPrompt =
|
|
275
|
+
`Continuing autonomous task. Previous iteration result:\n` +
|
|
276
|
+
`${response.text.slice(0, 2000)}\n\n` +
|
|
277
|
+
`If the task is complete, respond with "TASK_COMPLETE". ` +
|
|
278
|
+
`Otherwise continue working towards: ${prompt}`;
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
this.stats.errors++;
|
|
282
|
+
if (this.config.pauseOnError) {
|
|
283
|
+
result.ok = false;
|
|
284
|
+
result.error = `Execution error: ${err instanceof Error ? err.message : String(err)}`;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
// Otherwise swallow and continue
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
this.running = false;
|
|
293
|
+
this.abortController = null;
|
|
294
|
+
this.emitStatus(this.config.enabled ? 'enabled' : 'disabled');
|
|
295
|
+
this.persistState();
|
|
296
|
+
}
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
299
|
+
/** Abort the currently running auto execution */
|
|
300
|
+
abort() {
|
|
301
|
+
if (this.abortController) {
|
|
302
|
+
this.abortController.abort();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// -----------------------------------------------------------------------
|
|
306
|
+
// /goal command support
|
|
307
|
+
// -----------------------------------------------------------------------
|
|
308
|
+
/** Set a high-level goal and let the agent work towards it autonomously */
|
|
309
|
+
setGoal(name, description, successCriteria = []) {
|
|
310
|
+
const goal = {
|
|
311
|
+
id: randomUUID(),
|
|
312
|
+
name,
|
|
313
|
+
description,
|
|
314
|
+
successCriteria,
|
|
315
|
+
subGoals: [],
|
|
316
|
+
createdAt: new Date().toISOString(),
|
|
317
|
+
status: 'pending',
|
|
318
|
+
progress: 0,
|
|
319
|
+
};
|
|
320
|
+
this.goals.set(goal.id, goal);
|
|
321
|
+
this.persistGoals();
|
|
322
|
+
return goal;
|
|
323
|
+
}
|
|
324
|
+
/** Get a goal by ID */
|
|
325
|
+
getGoal(goalId) {
|
|
326
|
+
return this.goals.get(goalId);
|
|
327
|
+
}
|
|
328
|
+
/** List all goals */
|
|
329
|
+
listGoals() {
|
|
330
|
+
return Array.from(this.goals.values());
|
|
331
|
+
}
|
|
332
|
+
/** Update goal progress */
|
|
333
|
+
updateGoalProgress(goalId, progress, status) {
|
|
334
|
+
const goal = this.goals.get(goalId);
|
|
335
|
+
if (!goal)
|
|
336
|
+
return;
|
|
337
|
+
goal.progress = Math.min(1, Math.max(0, progress));
|
|
338
|
+
if (status)
|
|
339
|
+
goal.status = status;
|
|
340
|
+
if (goal.progress >= 1 && goal.status !== 'failed' && goal.status !== 'cancelled') {
|
|
341
|
+
goal.status = 'completed';
|
|
342
|
+
goal.completedAt = new Date().toISOString();
|
|
343
|
+
}
|
|
344
|
+
this.persistGoals();
|
|
345
|
+
}
|
|
346
|
+
/** Add a sub-goal */
|
|
347
|
+
addSubGoal(parentId, name, description) {
|
|
348
|
+
const parent = this.goals.get(parentId);
|
|
349
|
+
if (!parent)
|
|
350
|
+
return null;
|
|
351
|
+
const sub = {
|
|
352
|
+
id: randomUUID(),
|
|
353
|
+
name,
|
|
354
|
+
description,
|
|
355
|
+
successCriteria: [],
|
|
356
|
+
subGoals: [],
|
|
357
|
+
createdAt: new Date().toISOString(),
|
|
358
|
+
status: 'pending',
|
|
359
|
+
progress: 0,
|
|
360
|
+
};
|
|
361
|
+
parent.subGoals.push(sub);
|
|
362
|
+
this.goals.set(sub.id, sub); // flat index too
|
|
363
|
+
this.persistGoals();
|
|
364
|
+
return sub;
|
|
365
|
+
}
|
|
366
|
+
/** Delete a goal */
|
|
367
|
+
deleteGoal(goalId) {
|
|
368
|
+
if (!this.goals.has(goalId))
|
|
369
|
+
return false;
|
|
370
|
+
// Also remove from any parent's subGoals
|
|
371
|
+
for (const g of Array.from(this.goals.values())) {
|
|
372
|
+
g.subGoals = g.subGoals.filter((s) => s.id !== goalId);
|
|
373
|
+
}
|
|
374
|
+
this.goals.delete(goalId);
|
|
375
|
+
this.persistGoals();
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Execute a goal autonomously – will iterate until the goal is reached
|
|
380
|
+
* or limits are hit
|
|
381
|
+
*/
|
|
382
|
+
async executeGoal(goalId, engine) {
|
|
383
|
+
const goal = this.goals.get(goalId);
|
|
384
|
+
if (!goal)
|
|
385
|
+
return { ok: false, iterations: 0, finalText: '', error: `Goal "${goalId}" not found` };
|
|
386
|
+
goal.status = 'in_progress';
|
|
387
|
+
this.persistGoals();
|
|
388
|
+
const prompt = this.buildGoalPrompt(goal);
|
|
389
|
+
const result = await this.executeAuto(prompt, engine, this.config.maxIterations);
|
|
390
|
+
if (result.completed) {
|
|
391
|
+
goal.status = 'completed';
|
|
392
|
+
goal.progress = 1;
|
|
393
|
+
goal.completedAt = new Date().toISOString();
|
|
394
|
+
}
|
|
395
|
+
else if (result.error) {
|
|
396
|
+
goal.status = 'failed';
|
|
397
|
+
}
|
|
398
|
+
this.persistGoals();
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
// -----------------------------------------------------------------------
|
|
402
|
+
// /routine command support
|
|
403
|
+
// -----------------------------------------------------------------------
|
|
404
|
+
/** Create a new routine */
|
|
405
|
+
createRoutine(name, description, steps, tags = []) {
|
|
406
|
+
const routine = {
|
|
407
|
+
id: randomUUID(),
|
|
408
|
+
name,
|
|
409
|
+
description,
|
|
410
|
+
steps,
|
|
411
|
+
createdAt: new Date().toISOString(),
|
|
412
|
+
runCount: 0,
|
|
413
|
+
tags,
|
|
414
|
+
};
|
|
415
|
+
this.routines.set(routine.id, routine);
|
|
416
|
+
this.persistRoutines();
|
|
417
|
+
return routine;
|
|
418
|
+
}
|
|
419
|
+
/** Get a routine by ID */
|
|
420
|
+
getRoutine(routineId) {
|
|
421
|
+
return this.routines.get(routineId);
|
|
422
|
+
}
|
|
423
|
+
/** List all routines, optionally filtered by tag */
|
|
424
|
+
listRoutines(tag) {
|
|
425
|
+
const all = Array.from(this.routines.values());
|
|
426
|
+
if (tag)
|
|
427
|
+
return all.filter(r => r.tags.includes(tag));
|
|
428
|
+
return all;
|
|
429
|
+
}
|
|
430
|
+
/** Delete a routine */
|
|
431
|
+
deleteRoutine(routineId) {
|
|
432
|
+
if (!this.routines.has(routineId))
|
|
433
|
+
return false;
|
|
434
|
+
this.routines.delete(routineId);
|
|
435
|
+
this.persistRoutines();
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
/** Update a routine's steps */
|
|
439
|
+
updateRoutineSteps(routineId, steps) {
|
|
440
|
+
const routine = this.routines.get(routineId);
|
|
441
|
+
if (!routine)
|
|
442
|
+
return false;
|
|
443
|
+
routine.steps = steps;
|
|
444
|
+
this.persistRoutines();
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
/** Replay a saved routine */
|
|
448
|
+
async executeRoutine(routineId, engine) {
|
|
449
|
+
const routine = this.routines.get(routineId);
|
|
450
|
+
if (!routine)
|
|
451
|
+
return { ok: false, iterations: 0, finalText: '', error: `Routine "${routineId}" not found` };
|
|
452
|
+
if (routine.steps.length === 0)
|
|
453
|
+
return { ok: false, iterations: 0, finalText: '', error: 'Routine has no steps' };
|
|
454
|
+
routine.runCount++;
|
|
455
|
+
routine.lastRunAt = new Date().toISOString();
|
|
456
|
+
this.persistRoutines();
|
|
457
|
+
const overallResult = { ok: true, iterations: 0, finalText: '' };
|
|
458
|
+
const stepResults = [];
|
|
459
|
+
for (let i = 0; i < routine.steps.length; i++) {
|
|
460
|
+
const step = routine.steps[i];
|
|
461
|
+
// Build context from previous steps
|
|
462
|
+
const contextPrompt = stepResults.length > 0
|
|
463
|
+
? `Context from previous steps:\n${stepResults.map((r, idx) => `Step ${idx + 1}: ${r.slice(0, 500)}`).join('\n')}\n\n`
|
|
464
|
+
: '';
|
|
465
|
+
const fullPrompt = `${contextPrompt}Step ${i + 1}/${routine.steps.length}: ${step.prompt}`;
|
|
466
|
+
const stepMaxIter = step.maxIterations ?? Math.max(5, Math.floor(this.config.maxIterations / routine.steps.length));
|
|
467
|
+
const result = await this.executeAuto(fullPrompt, engine, stepMaxIter);
|
|
468
|
+
overallResult.iterations += result.iterations;
|
|
469
|
+
if (!result.ok) {
|
|
470
|
+
overallResult.ok = false;
|
|
471
|
+
overallResult.error = `Routine failed at step ${i + 1}: ${result.error}`;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
stepResults.push(result.finalText);
|
|
475
|
+
overallResult.finalText = result.finalText;
|
|
476
|
+
if (step.pauseAfter && i < routine.steps.length - 1) {
|
|
477
|
+
// In auto mode we just log; in interactive mode a real prompt would appear
|
|
478
|
+
// eslint-disable-next-line no-console
|
|
479
|
+
console.log(`[auto-mode] Routine "${routine.name}" paused after step ${i + 1}. Continuing automatically in auto mode.`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (overallResult.ok) {
|
|
483
|
+
overallResult.completed = true;
|
|
484
|
+
}
|
|
485
|
+
return overallResult;
|
|
486
|
+
}
|
|
487
|
+
// -----------------------------------------------------------------------
|
|
488
|
+
// Safety
|
|
489
|
+
// -----------------------------------------------------------------------
|
|
490
|
+
/**
|
|
491
|
+
* Pre-flight safety check – examines the prompt for dangerous commands
|
|
492
|
+
* or patterns that should be blocked even in auto mode.
|
|
493
|
+
*/
|
|
494
|
+
preFlightSafetyCheck(prompt) {
|
|
495
|
+
const lower = prompt.toLowerCase();
|
|
496
|
+
// Check always-blocked commands
|
|
497
|
+
for (const cmd of this.config.blockedCommands) {
|
|
498
|
+
if (lower.includes(cmd.toLowerCase())) {
|
|
499
|
+
return { allowed: false, reason: `Blocked command detected: "${cmd}"` };
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Safety-level-specific checks for conservative / moderate
|
|
503
|
+
if (this.config.safetyLevel === 'conservative') {
|
|
504
|
+
for (const cmd of CONSERVATIVE_BLOCKED_COMMANDS) {
|
|
505
|
+
if (lower.includes(cmd.toLowerCase())) {
|
|
506
|
+
return { allowed: false, reason: `Conservative mode blocked command: "${cmd}"` };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
else if (this.config.safetyLevel === 'moderate') {
|
|
511
|
+
for (const cmd of MODERATE_BLOCKED_COMMANDS) {
|
|
512
|
+
if (lower.includes(cmd.toLowerCase())) {
|
|
513
|
+
return { allowed: false, reason: `Moderate mode blocked command: "${cmd}"` };
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// 'aggressive' – only always-blocked commands are blocked
|
|
518
|
+
// Check blocked file patterns
|
|
519
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
520
|
+
// Simple substring check for path-like strings in prompt
|
|
521
|
+
if (lower.includes(pattern.replace(/\*\*/g, '').replace(/\*/g, '').toLowerCase())) {
|
|
522
|
+
return { allowed: false, reason: `Blocked file pattern detected: "${pattern}"` };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return { allowed: true };
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Runtime safety check for a specific command about to be executed.
|
|
529
|
+
* This is meant to be called by the engine before running any shell command.
|
|
530
|
+
*/
|
|
531
|
+
isCommandAllowed(command) {
|
|
532
|
+
const trimmed = command.trim();
|
|
533
|
+
// Always-blocked
|
|
534
|
+
for (const blocked of ALWAYS_BLOCKED_COMMANDS) {
|
|
535
|
+
if (trimmed.includes(blocked) || trimmed.startsWith(blocked.split(' ')[0])) {
|
|
536
|
+
return { allowed: false, reason: `Command blocked (always): "${blocked}"` };
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// Config-level blocked
|
|
540
|
+
for (const blocked of this.config.blockedCommands) {
|
|
541
|
+
if (trimmed.includes(blocked)) {
|
|
542
|
+
return { allowed: false, reason: `Command blocked (config): "${blocked}"` };
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
// Safety-level checks
|
|
546
|
+
const levelLists = {
|
|
547
|
+
conservative: CONSERVATIVE_BLOCKED_COMMANDS,
|
|
548
|
+
moderate: MODERATE_BLOCKED_COMMANDS,
|
|
549
|
+
aggressive: [],
|
|
550
|
+
};
|
|
551
|
+
for (const blocked of levelLists[this.config.safetyLevel]) {
|
|
552
|
+
if (trimmed.includes(blocked)) {
|
|
553
|
+
return { allowed: false, reason: `Command blocked (${this.config.safetyLevel}): "${blocked}"` };
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return { allowed: true };
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Runtime safety check for a file path about to be modified.
|
|
560
|
+
* This is meant to be called by the engine before any file write.
|
|
561
|
+
*/
|
|
562
|
+
isFileModificationAllowed(filePath) {
|
|
563
|
+
const normalised = filePath.replace(/\\/g, '/');
|
|
564
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
565
|
+
if (this.matchesGlobPattern(normalised, pattern)) {
|
|
566
|
+
return { allowed: false, reason: `File pattern blocked: "${pattern}"` };
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// Safety-level-specific patterns
|
|
570
|
+
const patternLists = {
|
|
571
|
+
conservative: CONSERVATIVE_BLOCKED_PATTERNS,
|
|
572
|
+
moderate: [],
|
|
573
|
+
aggressive: [],
|
|
574
|
+
};
|
|
575
|
+
for (const pattern of patternLists[this.config.safetyLevel]) {
|
|
576
|
+
if (this.matchesGlobPattern(normalised, pattern)) {
|
|
577
|
+
return { allowed: false, reason: `File pattern blocked (${this.config.safetyLevel}): "${pattern}"` };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return { allowed: true };
|
|
581
|
+
}
|
|
582
|
+
// -----------------------------------------------------------------------
|
|
583
|
+
// Statistics
|
|
584
|
+
// -----------------------------------------------------------------------
|
|
585
|
+
getStats() {
|
|
586
|
+
if (this.running) {
|
|
587
|
+
return { ...this.stats, timeElapsedMs: Date.now() - this.startTime };
|
|
588
|
+
}
|
|
589
|
+
return { ...this.stats };
|
|
590
|
+
}
|
|
591
|
+
resetStats() {
|
|
592
|
+
this.stats = this.freshStats();
|
|
593
|
+
}
|
|
594
|
+
// -----------------------------------------------------------------------
|
|
595
|
+
// Checkpoints
|
|
596
|
+
// -----------------------------------------------------------------------
|
|
597
|
+
listCheckpoints() {
|
|
598
|
+
return [...this.checkpoints];
|
|
599
|
+
}
|
|
600
|
+
getLatestCheckpoint() {
|
|
601
|
+
return this.checkpoints.length > 0
|
|
602
|
+
? this.checkpoints[this.checkpoints.length - 1]
|
|
603
|
+
: undefined;
|
|
604
|
+
}
|
|
605
|
+
/** Create a checkpoint of the current state */
|
|
606
|
+
createCheckpoint(iteration, goalId, routineId) {
|
|
607
|
+
const cp = {
|
|
608
|
+
id: randomUUID(),
|
|
609
|
+
timestamp: new Date().toISOString(),
|
|
610
|
+
iteration,
|
|
611
|
+
goalId,
|
|
612
|
+
routineId,
|
|
613
|
+
snapshot: JSON.stringify({
|
|
614
|
+
stats: this.stats,
|
|
615
|
+
config: { ...this.config, enabled: false }, // don't persist enabled state in snapshot
|
|
616
|
+
}),
|
|
617
|
+
};
|
|
618
|
+
this.checkpoints.push(cp);
|
|
619
|
+
// Persist to disk
|
|
620
|
+
try {
|
|
621
|
+
if (!existsSync(CHECKPOINTS_DIR))
|
|
622
|
+
mkdirSync(CHECKPOINTS_DIR, { recursive: true });
|
|
623
|
+
const cpFile = join(CHECKPOINTS_DIR, `checkpoint-${cp.id}.json`);
|
|
624
|
+
writeFileSync(cpFile, JSON.stringify(cp, null, 2), 'utf-8');
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
// Best-effort persistence
|
|
628
|
+
}
|
|
629
|
+
// Keep at most 50 checkpoints in memory
|
|
630
|
+
if (this.checkpoints.length > 50) {
|
|
631
|
+
const removed = this.checkpoints.shift();
|
|
632
|
+
if (removed) {
|
|
633
|
+
try {
|
|
634
|
+
const cpFile = join(CHECKPOINTS_DIR, `checkpoint-${removed.id}.json`);
|
|
635
|
+
if (existsSync(cpFile))
|
|
636
|
+
unlinkSync(cpFile);
|
|
637
|
+
}
|
|
638
|
+
catch { /* ignore */ }
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return cp;
|
|
642
|
+
}
|
|
643
|
+
// -----------------------------------------------------------------------
|
|
644
|
+
// Private helpers
|
|
645
|
+
// -----------------------------------------------------------------------
|
|
646
|
+
freshStats() {
|
|
647
|
+
return {
|
|
648
|
+
iterations: 0,
|
|
649
|
+
filesChanged: 0,
|
|
650
|
+
commandsRun: 0,
|
|
651
|
+
timeElapsedMs: 0,
|
|
652
|
+
totalCost: 0,
|
|
653
|
+
blockedOperations: 0,
|
|
654
|
+
errors: 0,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
emitStatus(status) {
|
|
658
|
+
if (this.onStatusChange) {
|
|
659
|
+
this.onStatusChange(status);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
buildBlockedCommands(level) {
|
|
663
|
+
const base = new Set(ALWAYS_BLOCKED_COMMANDS);
|
|
664
|
+
if (level === 'conservative') {
|
|
665
|
+
CONSERVATIVE_BLOCKED_COMMANDS.forEach(c => base.add(c));
|
|
666
|
+
}
|
|
667
|
+
else if (level === 'moderate') {
|
|
668
|
+
MODERATE_BLOCKED_COMMANDS.forEach(c => base.add(c));
|
|
669
|
+
}
|
|
670
|
+
// Merge user-defined on top
|
|
671
|
+
this.config.blockedCommands.forEach(c => base.add(c));
|
|
672
|
+
return Array.from(base);
|
|
673
|
+
}
|
|
674
|
+
buildBlockedPatterns(level) {
|
|
675
|
+
const base = new Set(ALWAYS_BLOCKED_PATTERNS);
|
|
676
|
+
if (level === 'conservative') {
|
|
677
|
+
CONSERVATIVE_BLOCKED_PATTERNS.forEach(p => base.add(p));
|
|
678
|
+
}
|
|
679
|
+
this.config.blockedPatterns.forEach(p => base.add(p));
|
|
680
|
+
return Array.from(base);
|
|
681
|
+
}
|
|
682
|
+
detectCompletion(text) {
|
|
683
|
+
const markers = [
|
|
684
|
+
'TASK_COMPLETE',
|
|
685
|
+
'task complete',
|
|
686
|
+
'task is complete',
|
|
687
|
+
'goal achieved',
|
|
688
|
+
'goal reached',
|
|
689
|
+
'all done',
|
|
690
|
+
'finished successfully',
|
|
691
|
+
];
|
|
692
|
+
const lower = text.toLowerCase();
|
|
693
|
+
return markers.some(m => lower.includes(m));
|
|
694
|
+
}
|
|
695
|
+
buildGoalPrompt(goal) {
|
|
696
|
+
const criteria = goal.successCriteria.length > 0
|
|
697
|
+
? `\n\nSuccess criteria:\n${goal.successCriteria.map((c, i) => `${i + 1}. ${c}`).join('\n')}`
|
|
698
|
+
: '';
|
|
699
|
+
const subGoals = goal.subGoals.length > 0
|
|
700
|
+
? `\n\nSub-goals:\n${goal.subGoals.map((sg, i) => `${i + 1}. ${sg.name}: ${sg.description}`).join('\n')}`
|
|
701
|
+
: '';
|
|
702
|
+
return (`Goal: ${goal.name}\n\n` +
|
|
703
|
+
`Description: ${goal.description}${criteria}${subGoals}\n\n` +
|
|
704
|
+
`Work towards this goal autonomously. When the goal is achieved, respond with "TASK_COMPLETE".`);
|
|
705
|
+
}
|
|
706
|
+
autoGitCommit(iteration) {
|
|
707
|
+
try {
|
|
708
|
+
execSync('git add -A', { stdio: 'pipe', timeout: 10_000 });
|
|
709
|
+
execSync(`git commit -m "auto-mode: iteration ${iteration}" --allow-empty`, {
|
|
710
|
+
stdio: 'pipe',
|
|
711
|
+
timeout: 10_000,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
// Not a git repo, or nothing to commit – ignore
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
autoRunTests() {
|
|
719
|
+
// Try common test commands
|
|
720
|
+
const testCommands = ['npm test', 'bun test', 'yarn test', 'pnpm test'];
|
|
721
|
+
for (const cmd of testCommands) {
|
|
722
|
+
try {
|
|
723
|
+
const output = execSync(cmd, { stdio: 'pipe', timeout: 60_000 }).toString('utf-8');
|
|
724
|
+
return { passed: true, output };
|
|
725
|
+
}
|
|
726
|
+
catch (err) {
|
|
727
|
+
// execSync throws on non-zero exit
|
|
728
|
+
if (err instanceof Error && 'stdout' in err) {
|
|
729
|
+
const output = String(err.stdout);
|
|
730
|
+
return { passed: false, output };
|
|
731
|
+
}
|
|
732
|
+
// Command not found – try the next one
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return { passed: true, output: 'No test runner found' };
|
|
737
|
+
}
|
|
738
|
+
matchesGlobPattern(filePath, pattern) {
|
|
739
|
+
// Convert simple glob to regex
|
|
740
|
+
const regexStr = pattern
|
|
741
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex special chars (except * and ?)
|
|
742
|
+
.replace(/\*\*/g, '{{GLOBSTAR}}') // preserve **
|
|
743
|
+
.replace(/\*/g, '[^/]*') // * matches anything except /
|
|
744
|
+
.replace(/\?/g, '[^/]') // ? matches single non-/
|
|
745
|
+
.replace(/\{\{GLOBSTAR\}\}/g, '.*'); // ** matches anything
|
|
746
|
+
try {
|
|
747
|
+
const regex = new RegExp(regexStr + '$', 'i');
|
|
748
|
+
return regex.test(filePath);
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
// -----------------------------------------------------------------------
|
|
755
|
+
// Persistence
|
|
756
|
+
// -----------------------------------------------------------------------
|
|
757
|
+
persistState() {
|
|
758
|
+
try {
|
|
759
|
+
if (!existsSync(NEURO_DIR))
|
|
760
|
+
mkdirSync(NEURO_DIR, { recursive: true });
|
|
761
|
+
writeFileSync(AUTO_STATE_FILE, JSON.stringify({
|
|
762
|
+
config: this.config,
|
|
763
|
+
stats: this.stats,
|
|
764
|
+
}, null, 2), 'utf-8');
|
|
765
|
+
}
|
|
766
|
+
catch { /* best effort */ }
|
|
767
|
+
}
|
|
768
|
+
loadState() {
|
|
769
|
+
try {
|
|
770
|
+
if (existsSync(AUTO_STATE_FILE)) {
|
|
771
|
+
const data = JSON.parse(readFileSync(AUTO_STATE_FILE, 'utf-8'));
|
|
772
|
+
if (data.config)
|
|
773
|
+
Object.assign(this.config, data.config);
|
|
774
|
+
if (data.stats)
|
|
775
|
+
Object.assign(this.stats, data.stats);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
catch { /* ignore corrupt state */ }
|
|
779
|
+
// Load goals
|
|
780
|
+
try {
|
|
781
|
+
if (existsSync(GOALS_FILE)) {
|
|
782
|
+
const data = JSON.parse(readFileSync(GOALS_FILE, 'utf-8'));
|
|
783
|
+
for (const g of data)
|
|
784
|
+
this.goals.set(g.id, g);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch { /* ignore */ }
|
|
788
|
+
// Load routines
|
|
789
|
+
try {
|
|
790
|
+
if (existsSync(ROUTINES_FILE)) {
|
|
791
|
+
const data = JSON.parse(readFileSync(ROUTINES_FILE, 'utf-8'));
|
|
792
|
+
for (const r of data)
|
|
793
|
+
this.routines.set(r.id, r);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
catch { /* ignore */ }
|
|
797
|
+
// Load checkpoints from disk
|
|
798
|
+
try {
|
|
799
|
+
if (existsSync(CHECKPOINTS_DIR)) {
|
|
800
|
+
const files = readdirSync(CHECKPOINTS_DIR).filter(f => f.startsWith('checkpoint-'));
|
|
801
|
+
for (const f of files.slice(-50)) {
|
|
802
|
+
try {
|
|
803
|
+
const cp = JSON.parse(readFileSync(join(CHECKPOINTS_DIR, f), 'utf-8'));
|
|
804
|
+
this.checkpoints.push(cp);
|
|
805
|
+
}
|
|
806
|
+
catch { /* skip corrupt */ }
|
|
807
|
+
}
|
|
808
|
+
// Sort by timestamp
|
|
809
|
+
this.checkpoints.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
catch { /* ignore */ }
|
|
813
|
+
}
|
|
814
|
+
persistGoals() {
|
|
815
|
+
try {
|
|
816
|
+
if (!existsSync(NEURO_DIR))
|
|
817
|
+
mkdirSync(NEURO_DIR, { recursive: true });
|
|
818
|
+
writeFileSync(GOALS_FILE, JSON.stringify(Array.from(this.goals.values()), null, 2), 'utf-8');
|
|
819
|
+
}
|
|
820
|
+
catch { /* best effort */ }
|
|
821
|
+
}
|
|
822
|
+
persistRoutines() {
|
|
823
|
+
try {
|
|
824
|
+
if (!existsSync(NEURO_DIR))
|
|
825
|
+
mkdirSync(NEURO_DIR, { recursive: true });
|
|
826
|
+
writeFileSync(ROUTINES_FILE, JSON.stringify(Array.from(this.routines.values()), null, 2), 'utf-8');
|
|
827
|
+
}
|
|
828
|
+
catch { /* best effort */ }
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
//# sourceMappingURL=auto-mode.js.map
|