abedelbahnasy55's picture
fix: allow longer activity intervals
3ca105d
import { z } from 'zod';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ConfigSchema = z.object({
github: z.object({
token: z.string().min(10, 'GITHUB_TOKEN must be a valid token'),
owner: z.string().min(1, 'GITHUB_OWNER is required'),
repo: z.string().min(1, 'GITHUB_REPO is required'),
}),
ai: z.object({
provider: z.enum(['ollama', 'huggingface', 'groq', 'openrouter', 'fallback']).default('fallback'),
ollama: z.object({
model: z.string().default('llama3.2'),
baseUrl: z.string().default('http://localhost:11434'),
timeout: z.number().min(1000).max(120000).default(60000),
maxRetries: z.number().min(0).max(10).default(3),
}),
huggingface: z.object({
apiKey: z.string().default(''),
model: z.string().default('mistralai/Mistral-7B-Instruct-v0.2'),
timeout: z.number().min(1000).max(120000).default(60000),
maxRetries: z.number().min(0).max(10).default(3),
}),
groq: z.object({
apiKey: z.string().default(''),
model: z.string().default('llama-3.1-8b-instant'),
timeout: z.number().min(1000).max(120000).default(30000),
maxRetries: z.number().min(0).max(10).default(3),
}),
openrouter: z.object({
apiKey: z.string().default(''),
model: z.string().default('meta-llama/llama-3.1-8b-instruct:free'),
timeout: z.number().min(1000).max(120000).default(30000),
maxRetries: z.number().min(0).max(10).default(3),
}),
}),
schedule: z.object({
timezone: z.string().default('Africa/Cairo'),
noWorkHoursRestriction: z.boolean().default(true),
}),
activity: z.object({
minIntervalMinutes: z.number().min(1).max(1440).default(5),
maxIntervalMinutes: z.number().min(1).max(1440).default(45),
maxCommitsPerSession: z.number().min(1).max(50).default(8),
maxFilesPerCommit: z.number().min(1).max(20).default(3),
bugProbability: z.number().min(0).max(1).default(0.15),
typoProbability: z.number().min(0).max(1).default(0.1),
maxActionsPerCycle: z.number().min(1).max(10).default(3),
}),
daemon: z.object({
enabled: z.boolean().default(true),
checkIntervalMinutes: z.number().min(1).max(120).default(10),
maxCyclesBeforeRestart: z.number().min(10).max(1000).default(100),
}),
memory: z.object({
persistToFile: z.boolean().default(true),
persistIntervalMinutes: z.number().min(1).max(60).default(15),
maxHistorySize: z.number().min(100).max(10000).default(500),
maxDecisionsSize: z.number().min(50).max(5000).default(200),
}),
logging: z.object({
level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
file: z.string().default('logs/simulator.log'),
maxFileSize: z.number().min(1000000).max(100000000).default(10000000),
}),
});
export function loadConfig() {
const rawConfig = {
github: {
token: process.env.GITHUB_TOKEN,
owner: process.env.GITHUB_OWNER,
repo: process.env.GITHUB_REPO,
},
ai: {
provider: process.env.AI_PROVIDER || 'fallback',
ollama: {
model: process.env.OLLAMA_MODEL || 'llama3.2',
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
timeout: parseInt(process.env.OLLAMA_TIMEOUT) || 60000,
maxRetries: parseInt(process.env.OLLAMA_MAX_RETRIES) || 3,
},
huggingface: {
apiKey: process.env.HUGGINGFACE_API_KEY || '',
model: process.env.HUGGINGFACE_MODEL || 'mistralai/Mistral-7B-Instruct-v0.2',
timeout: parseInt(process.env.HUGGINGFACE_TIMEOUT) || 60000,
maxRetries: parseInt(process.env.HUGGINGFACE_MAX_RETRIES) || 3,
},
groq: {
apiKey: process.env.GROQ_API_KEY || '',
model: process.env.GROQ_MODEL || 'llama-3.1-8b-instant',
timeout: parseInt(process.env.GROQ_TIMEOUT) || 30000,
maxRetries: parseInt(process.env.GROQ_MAX_RETRIES) || 3,
},
openrouter: {
apiKey: process.env.OPENROUTER_API_KEY || '',
model: process.env.OPENROUTER_MODEL || 'meta-llama/llama-3.1-8b-instruct:free',
timeout: parseInt(process.env.OPENROUTER_TIMEOUT) || 30000,
maxRetries: parseInt(process.env.OPENROUTER_MAX_RETRIES) || 3,
},
},
schedule: {
timezone: process.env.TIMEZONE || 'Africa/Cairo',
noWorkHoursRestriction: process.env.NO_WORK_HOURS_RESTRICTION !== 'false',
},
activity: {
minIntervalMinutes: _parseInt(process.env.MIN_ACTIVITY_INTERVAL_MINUTES, 5),
maxIntervalMinutes: _parseInt(process.env.MAX_ACTIVITY_INTERVAL_MINUTES, 45),
maxCommitsPerSession: _parseInt(process.env.MAX_COMMITS_PER_SESSION, 8),
maxFilesPerCommit: _parseInt(process.env.MAX_FILES_PER_COMMIT, 3),
bugProbability: _parseFloat(process.env.BUG_PROBABILITY, 0.15),
typoProbability: _parseFloat(process.env.TYPO_PROBABILITY, 0.1),
maxActionsPerCycle: _parseInt(process.env.MAX_ACTIONS_PER_CYCLE, 3),
},
daemon: {
enabled: process.env.DAEMON_ENABLED !== 'false',
checkIntervalMinutes: _parseInt(process.env.DAEMON_CHECK_INTERVAL_MINUTES, 10),
maxCyclesBeforeRestart: _parseInt(process.env.MAX_CYCLES_BEFORE_RESTART, 100),
},
memory: {
persistToFile: process.env.MEMORY_PERSIST_TO_FILE !== 'false',
persistIntervalMinutes: _parseInt(process.env.MEMORY_PERSIST_INTERVAL_MINUTES, 15),
maxHistorySize: _parseInt(process.env.MEMORY_MAX_HISTORY_SIZE, 500),
maxDecisionsSize: _parseInt(process.env.MEMORY_MAX_DECISIONS_SIZE, 200),
},
logging: {
level: process.env.LOG_LEVEL || 'info',
file: process.env.LOG_FILE || 'logs/simulator.log',
maxFileSize: _parseInt(process.env.LOG_MAX_FILE_SIZE, 10000000),
},
};
try {
return ConfigSchema.parse(rawConfig);
} catch (error) {
const formattedErrors = error.errors
.map(e => ` - ${e.path.join('.')}: ${e.message}`)
.join('\n');
throw new Error(`Invalid configuration:\n${formattedErrors}`);
}
}
export async function saveConfig(config, filePath = '.env') {
const envContent = `GITHUB_TOKEN=${config.github.token}
GITHUB_OWNER=${config.github.owner}
GITHUB_REPO=${config.github.repo}
AI_PROVIDER=${config.ai.provider}
OLLAMA_MODEL=${config.ai.ollama.model}
OLLAMA_BASE_URL=${config.ai.ollama.baseUrl}
HUGGINGFACE_API_KEY=${config.ai.huggingface.apiKey}
HUGGINGFACE_MODEL=${config.ai.huggingface.model}
TIMEZONE=${config.schedule.timezone}
NO_WORK_HOURS_RESTRICTION=true
MIN_ACTIVITY_INTERVAL_MINUTES=${config.activity.minIntervalMinutes}
MAX_ACTIVITY_INTERVAL_MINUTES=${config.activity.maxIntervalMinutes}
MAX_COMMITS_PER_SESSION=${config.activity.maxCommitsPerSession}
MAX_FILES_PER_COMMIT=${config.activity.maxFilesPerCommit}
BUG_PROBABILITY=${config.activity.bugProbability}
TYPO_PROBABILITY=${config.activity.typoProbability}
DAEMON_ENABLED=${config.daemon.enabled}
DAEMON_CHECK_INTERVAL_MINUTES=${config.daemon.checkIntervalMinutes}
MEMORY_PERSIST_TO_FILE=${config.memory.persistToFile}
MEMORY_PERSIST_INTERVAL_MINUTES=${config.memory.persistIntervalMinutes}
LOG_LEVEL=${config.logging.level}
LOG_FILE=${config.logging.file}
`;
await fs.writeFile(filePath, envContent, 'utf8');
}
export function validateConfig(config) {
try {
ConfigSchema.parse(config);
return { valid: true, errors: [] };
} catch (error) {
return {
valid: false,
errors: error.errors.map(e => `${e.path.join('.')}: ${e.message}`),
};
}
}
function _parseInt(value, defaultValue) {
if (value === undefined || value === null || value === '') return defaultValue;
const parsed = parseInt(value, 10);
return isNaN(parsed) ? defaultValue : parsed;
}
function _parseFloat(value, defaultValue) {
if (value === undefined || value === null || value === '') return defaultValue;
const parsed = parseFloat(value);
return isNaN(parsed) ? defaultValue : parsed;
}
export default loadConfig;