activity-simulator / src /index.js
abedelbahnasy55's picture
fix: timeouts, error handling, duplicate title fix
ca1b6c1
import loadConfig from './utils/config.js';
import logger from './utils/logger.js';
import HumanSchedule from './utils/humanSchedule.js';
import AIProvider from './services/aiProvider.js';
import GitHubService from './services/githubService.js';
import ContextMemory from './core/contextMemory.js';
import MultiRoleDeveloper from './core/multiRoleDeveloper.js';
import ConversationSimulator from './core/conversationSimulator.js';
import CodeReviewEngine from './core/codeReviewEngine.js';
import GitHubReactor from './core/githubReactor.js';
import IssueTracker from './core/issueTracker.js';
import PRWorkflow from './core/prWorkflow.js';
import TimeBehavior from './core/timeBehavior.js';
import SelfUpdater from './core/selfUpdater.js';
import HealthCheck from './core/healthCheck.js';
class HumanBehaviorEngine {
constructor() {
this.config = loadConfig();
this.memory = new ContextMemory({
persistFile: 'data/memory.json',
persistInterval: this.config.memory.persistIntervalMinutes * 60 * 1000,
maxHistorySize: this.config.memory.maxHistorySize,
maxDecisionsSize: this.config.memory.maxDecisionsSize,
});
this.schedule = new HumanSchedule(this.config);
this.timeBehavior = new TimeBehavior(this.config);
this.ai = new AIProvider(this.config);
this.github = new GitHubService(this.config);
this.developer = new MultiRoleDeveloper(this.config, this.memory);
this.conversation = new ConversationSimulator(this.ai, this.memory, this.developer);
this.reviewEngine = new CodeReviewEngine(this.ai, this.memory, this.developer);
this.reactor = new GitHubReactor(
this.github,
this.ai,
this.memory,
this.developer,
this.conversation,
this.reviewEngine
);
this.issueTracker = new IssueTracker(
this.github,
this.ai,
this.memory,
this.developer,
this.conversation
);
this.prWorkflow = new PRWorkflow(
this.github,
this.ai,
this.memory,
this.developer,
this.conversation,
this.reviewEngine
);
this.selfUpdater = new SelfUpdater(this.config, this.memory);
this.healthCheck = new HealthCheck();
this.running = false;
this._timeoutId = null;
this._cycleCount = 0;
this._startTime = null;
this._lastError = null;
this._consecutiveErrors = 0;
this._registerHealthChecks();
}
async start() {
logger.info('🚀 Starting Human Behavior Engine v3.0');
logger.info(`AI Provider: ${this.config.ai.provider}`);
logger.info(`Timezone: ${this.config.schedule.timezone}`);
logger.info(`Role: ${this.developer.currentRole}`);
this._startTime = Date.now();
await this.memory.initialize();
await this.developer.initialize();
await this.selfUpdater.initialize();
logger.info(`Timezone: ${this.config.schedule.timezone} (no work hours restriction)`);
this.running = true;
await this._runCycle();
}
async _runCycle() {
if (!this.running) return;
if (!this.timeBehavior.shouldAct()) {
const delay = this.timeBehavior.getNextActionDelay();
logger.debug(`Skipping action (intensity: ${this.timeBehavior.getIntensity().toFixed(2)})`);
this._timeoutId = setTimeout(() => this._runCycle(), delay);
return;
}
this._cycleCount++;
try {
const mood = this.timeBehavior.getMood();
const period = this.timeBehavior.getCurrentPeriod();
logger.info(`🔄 Cycle #${this._cycleCount} started (mood: ${mood}, period: ${period})`);
await this._loadProjectContext();
const actions = [
{ name: 'reactor', fn: () => this.reactor.react() },
{ name: 'issues', fn: () => this.issueTracker.processIssues() },
{ name: 'prs', fn: () => this.prWorkflow.processPRs() },
];
const maxActions = this.config.activity.maxActionsPerCycle;
const selectedActions = actions.slice(0, maxActions);
for (const action of selectedActions) {
try {
await action.fn();
} catch (error) {
logger.error(`Action "${action.name}" failed: ${error.message}`);
}
}
this._consecutiveErrors = 0;
if (this._cycleCount % 10 === 0) {
await this._runHealthCheck();
}
if (this._cycleCount % 50 === 0) {
await this.memory.forcePersist();
}
if (this._cycleCount % 100 === 0) {
await this.memory.cleanup();
}
if (this._cycleCount >= this.config.daemon.maxCyclesBeforeRestart) {
logger.info(`Reached max cycles (${this._cycleCount}), restarting cycle count`);
this._cycleCount = 0;
}
const delay = this.timeBehavior.getNextActionDelay();
logger.info(`⏳ Next cycle in ${Math.round(delay / 60000)} minutes`);
this._timeoutId = setTimeout(() => this._runCycle(), delay);
} catch (error) {
this._consecutiveErrors++;
this._lastError = error;
logger.error(`Cycle #${this._cycleCount} failed: ${error.message}`);
if (this._consecutiveErrors >= 5) {
logger.error('Too many consecutive errors, pausing for 1 hour');
this._timeoutId = setTimeout(() => {
this._consecutiveErrors = 0;
this._runCycle();
}, 60 * 60 * 1000);
} else {
const delay = Math.min(
this.timeBehavior.getNextActionDelay(),
30 * 60 * 1000
);
this._timeoutId = setTimeout(() => this._runCycle(), delay);
}
}
}
async _loadProjectContext() {
try {
const commits = await this.github.getCommitHistory('main', 10);
const files = await this.github.getRepositoryContent('src', 'main');
await this.developer.adaptRoleFromContext(
commits.map(c => c.commit.message).join(' '),
files || []
);
} catch (error) {
logger.warn(`Could not load project context: ${error.message}`);
}
}
async _runHealthCheck() {
const result = await this.healthCheck.runAll();
if (!result.allHealthy) {
logger.warn(`Health check failed: ${Object.entries(result.results)
.filter(([, r]) => r.status === 'unhealthy')
.map(([name]) => name)
.join(', ')}`);
}
await this.selfUpdater.adaptBehavior({
errorRate: this._consecutiveErrors / Math.max(this._cycleCount, 1),
successRate: 1 - (this._consecutiveErrors / Math.max(this._cycleCount, 1)),
avgResponseTime: 0,
});
}
_registerHealthChecks() {
this.healthCheck.registerCheck('memory', async () => {
return this.memory.getContextSummary();
});
this.healthCheck.registerCheck('ai', async () => {
return this.ai.getStats();
});
this.healthCheck.registerCheck('github', async () => {
return this.github.getStats();
});
this.healthCheck.registerCheck('developer', async () => {
return this.developer.getStats();
});
}
_scheduleNextCycle() {
const nextWork = this.timeBehavior.getNextWorkTime();
const delay = nextWork.getTime() - Date.now();
logger.info(`📅 Scheduling next work session in ${Math.round(delay / 60000)} minutes`);
this._timeoutId = setTimeout(async () => {
this.running = true;
await this._runCycle();
}, Math.min(delay, 86400000));
}
async stop() {
logger.info('🛑 Stopping Human Behavior Engine');
this.running = false;
if (this._timeoutId) {
clearTimeout(this._timeoutId);
this._timeoutId = null;
}
await this.memory.forcePersist();
}
async getStatus() {
const isWorkHours = this.timeBehavior.isWorkHours();
const mood = this.timeBehavior.getMood();
const period = this.timeBehavior.getCurrentPeriod();
const dayName = this.timeBehavior.getDayOfWeekName();
const roleContext = this.developer.getRoleContext();
const uptime = this._startTime ? Date.now() - this._startTime : 0;
const uptimeHours = Math.floor(uptime / (1000 * 60 * 60));
const uptimeMinutes = Math.floor((uptime % (1000 * 60 * 60)) / (1000 * 60));
return {
running: this.running,
isWorkHours,
mood,
period,
day: dayName,
role: roleContext.role,
workingOn: this.memory.recall('currentTask') || 'idle',
nextWorkTime: isWorkHours ? 'Now' : this.timeBehavior.getNextWorkTime().toLocaleString(),
aiProvider: this.config.ai.provider,
timezone: this.config.schedule.timezone,
memory: this.memory.getContextSummary(),
uptime: `${uptimeHours}h ${uptimeMinutes}m`,
cycles: this._cycleCount,
consecutiveErrors: this._consecutiveErrors,
health: this.healthCheck.getLastResult(),
config: {
timezone: this.config.schedule.timezone,
noWorkHoursRestriction: this.config.schedule.noWorkHoursRestriction,
},
};
}
async runOnce() {
logger.info('Running single human behavior cycle');
try {
await this.memory.initialize();
} catch (e) {
logger.warn(`Memory init warning: ${e.message}`);
}
try {
await this.developer.initialize();
} catch (e) {
logger.warn(`Developer init warning: ${e.message}`);
}
const actions = [
{ name: 'context', fn: () => this._loadProjectContext() },
{ name: 'reactor', fn: () => this.reactor.react() },
{ name: 'issues', fn: () => this.issueTracker.processIssues() },
{ name: 'prs', fn: () => this.prWorkflow.processPRs() },
];
for (const action of actions) {
try {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`${action.name} timed out after 30s`)), 30000)
);
await Promise.race([action.fn(), timeout]);
} catch (error) {
logger.error(`Action "${action.name}" failed: ${error.message}`);
}
}
}
async exportMemory() {
return this.memory.export();
}
async importMemory(data) {
this.memory.import(data);
}
}
export default HumanBehaviorEngine;