Kraft102's picture
Initial deployment - WidgeTDC Cortex Backend v2.1.0
529090e
/**
* ╔═══════════════════════════════════════════════════════════════════════════╗
* β•‘ PREFRONTAL CORTEX SERVICE β•‘
* ║═══════════════════════════════════════════════════════════════════════════║
* β•‘ The system's executive function - strategic planning, goal management, β•‘
* β•‘ decision making, and coordinating other cognitive modules β•‘
* β•‘ β€’ Goal Management: Define and track objectives β•‘
* β•‘ β€’ Strategic Planning: Break goals into actionable plans β•‘
* β•‘ β€’ Decision Making: Evaluate options with trade-off analysis β•‘
* β•‘ β€’ Executive Control: Coordinate actions across modules β•‘
* β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
*/
import { neo4jAdapter } from '../adapters/Neo4jAdapter.js';
import { v4 as uuidv4 } from 'uuid';
// ═══════════════════════════════════════════════════════════════════════════
// Types
// ═══════════════════════════════════════════════════════════════════════════
export type GoalStatus = 'DRAFT' | 'ACTIVE' | 'IN_PROGRESS' | 'BLOCKED' | 'COMPLETED' | 'ABANDONED';
export type GoalPriority = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
export type GoalTimeframe = 'IMMEDIATE' | 'SHORT_TERM' | 'MEDIUM_TERM' | 'LONG_TERM';
export interface Goal {
id: string;
title: string;
description: string;
status: GoalStatus;
priority: GoalPriority;
timeframe: GoalTimeframe;
parentGoalId?: string; // For sub-goals
successCriteria: string[];
progress: number; // 0-100
blockers: string[];
dependencies: string[]; // Other goal IDs
tags: string[];
createdAt: string;
updatedAt: string;
targetDate?: string;
completedAt?: string;
}
export interface Plan {
id: string;
goalId: string;
title: string;
steps: PlanStep[];
status: 'DRAFT' | 'APPROVED' | 'EXECUTING' | 'COMPLETED' | 'FAILED';
estimatedEffort: string; // e.g., "2 hours", "3 days"
risks: PlanRisk[];
createdAt: string;
approvedAt?: string;
approvedBy?: string;
}
export interface PlanStep {
id: string;
order: number;
description: string;
actionType?: string;
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'SKIPPED' | 'FAILED';
dependencies: string[]; // Other step IDs
output?: string;
completedAt?: string;
}
export interface PlanRisk {
id: string;
description: string;
probability: 'LOW' | 'MEDIUM' | 'HIGH';
impact: 'LOW' | 'MEDIUM' | 'HIGH';
mitigation: string;
}
export interface Decision {
id: string;
question: string;
context: string;
options: DecisionOption[];
selectedOption?: string;
rationale?: string;
status: 'PENDING' | 'DECIDED' | 'IMPLEMENTED';
decidedAt?: string;
decidedBy?: string;
createdAt: string;
}
export interface DecisionOption {
id: string;
name: string;
description: string;
pros: string[];
cons: string[];
risks: string[];
score?: number; // Calculated score
}
// ═══════════════════════════════════════════════════════════════════════════
// Prefrontal Cortex Service
// ═══════════════════════════════════════════════════════════════════════════
class PrefrontalCortexService {
private static instance: PrefrontalCortexService;
private goals: Map<string, Goal> = new Map();
private plans: Map<string, Plan> = new Map();
private decisions: Map<string, Decision> = new Map();
// Focus tracking
private currentFocus: string | null = null;
private focusHistory: { goalId: string; startedAt: string; endedAt?: string }[] = [];
private constructor() {
this.loadFromNeo4j();
}
public static getInstance(): PrefrontalCortexService {
if (!PrefrontalCortexService.instance) {
PrefrontalCortexService.instance = new PrefrontalCortexService();
}
return PrefrontalCortexService.instance;
}
// ═══════════════════════════════════════════════════════════════════════
// Goal Management
// ═══════════════════════════════════════════════════════════════════════
/**
* Create a new goal
*/
public async createGoal(params: {
title: string;
description: string;
priority?: GoalPriority;
timeframe?: GoalTimeframe;
parentGoalId?: string;
successCriteria?: string[];
targetDate?: string;
tags?: string[];
}): Promise<Goal> {
const goal: Goal = {
id: `goal-${uuidv4()}`,
title: params.title,
description: params.description,
status: 'DRAFT',
priority: params.priority || 'MEDIUM',
timeframe: params.timeframe || 'MEDIUM_TERM',
parentGoalId: params.parentGoalId,
successCriteria: params.successCriteria || [],
progress: 0,
blockers: [],
dependencies: [],
tags: params.tags || [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
targetDate: params.targetDate
};
this.goals.set(goal.id, goal);
await this.persistGoal(goal);
console.error(`[PrefrontalCortex] 🎯 Created goal: ${goal.title}`);
return goal;
}
/**
* Activate a goal (move from DRAFT to ACTIVE)
*/
public async activateGoal(goalId: string): Promise<Goal | null> {
const goal = this.goals.get(goalId);
if (!goal) return null;
goal.status = 'ACTIVE';
goal.updatedAt = new Date().toISOString();
await this.persistGoal(goal);
return goal;
}
/**
* Update goal progress
*/
public async updateGoalProgress(goalId: string, progress: number, status?: GoalStatus): Promise<Goal | null> {
const goal = this.goals.get(goalId);
if (!goal) return null;
goal.progress = Math.min(100, Math.max(0, progress));
goal.updatedAt = new Date().toISOString();
if (status) {
goal.status = status;
} else if (progress >= 100) {
goal.status = 'COMPLETED';
goal.completedAt = new Date().toISOString();
} else if (progress > 0 && goal.status === 'ACTIVE') {
goal.status = 'IN_PROGRESS';
}
await this.persistGoal(goal);
return goal;
}
/**
* Add blocker to goal
*/
public async addBlocker(goalId: string, blocker: string): Promise<Goal | null> {
const goal = this.goals.get(goalId);
if (!goal) return null;
goal.blockers.push(blocker);
goal.status = 'BLOCKED';
goal.updatedAt = new Date().toISOString();
await this.persistGoal(goal);
console.error(`[PrefrontalCortex] 🚧 Goal blocked: ${goal.title} - ${blocker}`);
return goal;
}
/**
* Remove blocker from goal
*/
public async removeBlocker(goalId: string, blockerIndex: number): Promise<Goal | null> {
const goal = this.goals.get(goalId);
if (!goal) return null;
goal.blockers.splice(blockerIndex, 1);
if (goal.blockers.length === 0 && goal.status === 'BLOCKED') {
goal.status = goal.progress > 0 ? 'IN_PROGRESS' : 'ACTIVE';
}
goal.updatedAt = new Date().toISOString();
await this.persistGoal(goal);
return goal;
}
/**
* Get goals by status or priority
*/
public getGoals(filter?: {
status?: GoalStatus;
priority?: GoalPriority;
timeframe?: GoalTimeframe;
parentGoalId?: string;
}): Goal[] {
let results = Array.from(this.goals.values());
if (filter?.status) {
results = results.filter(g => g.status === filter.status);
}
if (filter?.priority) {
results = results.filter(g => g.priority === filter.priority);
}
if (filter?.timeframe) {
results = results.filter(g => g.timeframe === filter.timeframe);
}
if (filter?.parentGoalId !== undefined) {
results = results.filter(g => g.parentGoalId === filter.parentGoalId);
}
// Sort by priority
const priorityOrder = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
return results.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
}
/**
* Get sub-goals for a goal
*/
public getSubGoals(parentGoalId: string): Goal[] {
return this.getGoals({ parentGoalId });
}
// ═══════════════════════════════════════════════════════════════════════
// Strategic Planning
// ═══════════════════════════════════════════════════════════════════════
/**
* Create a plan for a goal
*/
public async createPlan(params: {
goalId: string;
title: string;
steps: Omit<PlanStep, 'id' | 'status' | 'completedAt'>[];
estimatedEffort: string;
risks?: Omit<PlanRisk, 'id'>[];
}): Promise<Plan> {
const plan: Plan = {
id: `plan-${uuidv4()}`,
goalId: params.goalId,
title: params.title,
steps: params.steps.map((s, i) => ({
...s,
id: `step-${uuidv4()}`,
order: i + 1,
status: 'PENDING' as const
})),
status: 'DRAFT',
estimatedEffort: params.estimatedEffort,
risks: (params.risks || []).map(r => ({ ...r, id: `risk-${uuidv4()}` })),
createdAt: new Date().toISOString()
};
this.plans.set(plan.id, plan);
await this.persistPlan(plan);
console.error(`[PrefrontalCortex] πŸ“‹ Created plan: ${plan.title} (${plan.steps.length} steps)`);
return plan;
}
/**
* Approve a plan
*/
public async approvePlan(planId: string, approvedBy: string): Promise<Plan | null> {
const plan = this.plans.get(planId);
if (!plan) return null;
plan.status = 'APPROVED';
plan.approvedAt = new Date().toISOString();
plan.approvedBy = approvedBy;
await this.persistPlan(plan);
return plan;
}
/**
* Update plan step status
*/
public async updatePlanStep(planId: string, stepId: string, status: PlanStep['status'], output?: string): Promise<Plan | null> {
const plan = this.plans.get(planId);
if (!plan) return null;
const step = plan.steps.find(s => s.id === stepId);
if (!step) return null;
step.status = status;
step.output = output;
if (status === 'COMPLETED' || status === 'SKIPPED' || status === 'FAILED') {
step.completedAt = new Date().toISOString();
}
// Update plan status
const allComplete = plan.steps.every(s => s.status === 'COMPLETED' || s.status === 'SKIPPED');
const anyFailed = plan.steps.some(s => s.status === 'FAILED');
const anyInProgress = plan.steps.some(s => s.status === 'IN_PROGRESS');
if (allComplete) {
plan.status = 'COMPLETED';
} else if (anyFailed) {
plan.status = 'FAILED';
} else if (anyInProgress) {
plan.status = 'EXECUTING';
}
// Update goal progress
const goal = this.goals.get(plan.goalId);
if (goal) {
const completedSteps = plan.steps.filter(s => s.status === 'COMPLETED').length;
goal.progress = Math.round((completedSteps / plan.steps.length) * 100);
await this.persistGoal(goal);
}
await this.persistPlan(plan);
return plan;
}
/**
* Get next step to execute in a plan
*/
public getNextStep(planId: string): PlanStep | null {
const plan = this.plans.get(planId);
if (!plan || plan.status !== 'APPROVED' && plan.status !== 'EXECUTING') return null;
// Find first pending step whose dependencies are met
for (const step of plan.steps) {
if (step.status !== 'PENDING') continue;
const depsComplete = step.dependencies.every(depId => {
const depStep = plan.steps.find(s => s.id === depId);
return depStep && (depStep.status === 'COMPLETED' || depStep.status === 'SKIPPED');
});
if (depsComplete) {
return step;
}
}
return null;
}
// ═══════════════════════════════════════════════════════════════════════
// Decision Making
// ═══════════════════════════════════════════════════════════════════════
/**
* Create a decision to be made
*/
public async createDecision(params: {
question: string;
context: string;
options: Omit<DecisionOption, 'id' | 'score'>[];
}): Promise<Decision> {
const decision: Decision = {
id: `decision-${uuidv4()}`,
question: params.question,
context: params.context,
options: params.options.map(o => ({
...o,
id: `option-${uuidv4()}`,
score: this.scoreOption(o)
})),
status: 'PENDING',
createdAt: new Date().toISOString()
};
this.decisions.set(decision.id, decision);
await this.persistDecision(decision);
console.error(`[PrefrontalCortex] ❓ Decision pending: ${decision.question}`);
return decision;
}
/**
* Score a decision option based on pros/cons/risks
*/
private scoreOption(option: Omit<DecisionOption, 'id' | 'score'>): number {
let score = 50; // Start neutral
// Pros add points
score += option.pros.length * 10;
// Cons subtract points
score -= option.cons.length * 8;
// Risks subtract based on severity (assumed from description length as proxy)
score -= option.risks.length * 5;
return Math.max(0, Math.min(100, score));
}
/**
* Make a decision
*/
public async makeDecision(decisionId: string, optionId: string, rationale: string, decidedBy: string): Promise<Decision | null> {
const decision = this.decisions.get(decisionId);
if (!decision) return null;
decision.selectedOption = optionId;
decision.rationale = rationale;
decision.status = 'DECIDED';
decision.decidedAt = new Date().toISOString();
decision.decidedBy = decidedBy;
await this.persistDecision(decision);
const option = decision.options.find(o => o.id === optionId);
console.error(`[PrefrontalCortex] βœ… Decision made: ${option?.name || optionId}`);
return decision;
}
/**
* Get pending decisions
*/
public getPendingDecisions(): Decision[] {
return Array.from(this.decisions.values()).filter(d => d.status === 'PENDING');
}
/**
* Get recommendation for a decision
*/
public getRecommendation(decisionId: string): { option: DecisionOption; rationale: string } | null {
const decision = this.decisions.get(decisionId);
if (!decision || decision.options.length === 0) return null;
// Sort by score
const sorted = [...decision.options].sort((a, b) => (b.score || 0) - (a.score || 0));
const best = sorted[0];
const rationale = `Recommended "${best.name}" based on: ${best.pros.length} pros vs ${best.cons.length} cons. Key advantages: ${best.pros.slice(0, 2).join(', ')}`;
return { option: best, rationale };
}
// ═══════════════════════════════════════════════════════════════════════
// Executive Control / Focus Management
// ═══════════════════════════════════════════════════════════════════════
/**
* Set current focus to a goal
*/
public setFocus(goalId: string): void {
if (this.currentFocus) {
// End previous focus
const lastFocus = this.focusHistory[this.focusHistory.length - 1];
if (lastFocus && !lastFocus.endedAt) {
lastFocus.endedAt = new Date().toISOString();
}
}
this.currentFocus = goalId;
this.focusHistory.push({
goalId,
startedAt: new Date().toISOString()
});
const goal = this.goals.get(goalId);
console.error(`[PrefrontalCortex] 🎯 Focus set: ${goal?.title || goalId}`);
}
/**
* Get current focus
*/
public getCurrentFocus(): Goal | null {
if (!this.currentFocus) return null;
return this.goals.get(this.currentFocus) || null;
}
/**
* Get executive summary
*/
public getExecutiveSummary(): {
currentFocus: Goal | null;
activeGoals: number;
blockedGoals: number;
pendingDecisions: number;
activePlans: number;
overallProgress: number;
} {
const goals = Array.from(this.goals.values());
const plans = Array.from(this.plans.values());
const activeGoals = goals.filter(g => g.status === 'ACTIVE' || g.status === 'IN_PROGRESS');
const totalProgress = activeGoals.reduce((sum, g) => sum + g.progress, 0);
return {
currentFocus: this.getCurrentFocus(),
activeGoals: activeGoals.length,
blockedGoals: goals.filter(g => g.status === 'BLOCKED').length,
pendingDecisions: this.getPendingDecisions().length,
activePlans: plans.filter(p => p.status === 'APPROVED' || p.status === 'EXECUTING').length,
overallProgress: activeGoals.length > 0 ? Math.round(totalProgress / activeGoals.length) : 0
};
}
// ═══════════════════════════════════════════════════════════════════════
// Persistence
// ═══════════════════════════════════════════════════════════════════════
private async persistGoal(goal: Goal): Promise<void> {
try {
await neo4jAdapter.executeQuery(`
MERGE (g:Goal {id: $id})
SET g.title = $title,
g.description = $description,
g.status = $status,
g.priority = $priority,
g.timeframe = $timeframe,
g.progress = $progress,
g.tags = $tags,
g.createdAt = $createdAt,
g.updatedAt = $updatedAt
`, {
id: goal.id,
title: goal.title,
description: goal.description,
status: goal.status,
priority: goal.priority,
timeframe: goal.timeframe,
progress: goal.progress,
tags: goal.tags,
createdAt: goal.createdAt,
updatedAt: goal.updatedAt
});
} catch (error) {
console.warn('[PrefrontalCortex] Failed to persist goal:', error);
}
}
private async persistPlan(plan: Plan): Promise<void> {
try {
await neo4jAdapter.executeQuery(`
MERGE (p:Plan {id: $id})
SET p.goalId = $goalId,
p.title = $title,
p.status = $status,
p.estimatedEffort = $estimatedEffort,
p.stepCount = $stepCount,
p.createdAt = $createdAt
`, {
id: plan.id,
goalId: plan.goalId,
title: plan.title,
status: plan.status,
estimatedEffort: plan.estimatedEffort,
stepCount: plan.steps.length,
createdAt: plan.createdAt
});
} catch (error) {
console.warn('[PrefrontalCortex] Failed to persist plan:', error);
}
}
private async persistDecision(decision: Decision): Promise<void> {
try {
await neo4jAdapter.executeQuery(`
MERGE (d:Decision {id: $id})
SET d.question = $question,
d.status = $status,
d.optionCount = $optionCount,
d.selectedOption = $selectedOption,
d.createdAt = $createdAt,
d.decidedAt = $decidedAt
`, {
id: decision.id,
question: decision.question,
status: decision.status,
optionCount: decision.options.length,
selectedOption: decision.selectedOption || '',
createdAt: decision.createdAt,
decidedAt: decision.decidedAt || ''
});
} catch (error) {
console.warn('[PrefrontalCortex] Failed to persist decision:', error);
}
}
private async loadFromNeo4j(): Promise<void> {
try {
const goalResults = await neo4jAdapter.executeQuery(`
MATCH (g:Goal)
WHERE g.status IN ['ACTIVE', 'IN_PROGRESS', 'BLOCKED']
RETURN g
ORDER BY g.priority
LIMIT 100
`);
for (const r of goalResults) {
const g = r.g.properties;
this.goals.set(g.id, {
id: g.id,
title: g.title,
description: g.description,
status: g.status,
priority: g.priority,
timeframe: g.timeframe,
progress: g.progress,
successCriteria: [],
blockers: [],
dependencies: [],
tags: g.tags || [],
createdAt: g.createdAt,
updatedAt: g.updatedAt
});
}
console.error(`[PrefrontalCortex] πŸ”„ Loaded ${goalResults.length} active goals`);
} catch (error) {
console.warn('[PrefrontalCortex] Failed to load from Neo4j:', error);
}
}
// ═══════════════════════════════════════════════════════════════════════
// Status
// ═══════════════════════════════════════════════════════════════════════
public getStatus(): {
totalGoals: number;
activePlans: number;
pendingDecisions: number;
currentFocus: string | null;
} {
return {
totalGoals: this.goals.size,
activePlans: Array.from(this.plans.values()).filter(p => p.status === 'EXECUTING').length,
pendingDecisions: this.getPendingDecisions().length,
currentFocus: this.currentFocus
};
}
}
export const prefrontalCortex = PrefrontalCortexService.getInstance();