387 lines
11 KiB
JavaScript
387 lines
11 KiB
JavaScript
|
|
export class SymbolicEngine {
|
|
constructor() {
|
|
this.facts = new Map();
|
|
this.factIndices = new Map();
|
|
this.factMask = 0n;
|
|
this.rules = [];
|
|
this.reasoningLog = [];
|
|
}
|
|
|
|
addFact(key, value) {
|
|
this.facts.set(key, value);
|
|
if (!this.factIndices.has(key)) {
|
|
this.factIndices.set(key, BigInt(this.factIndices.size));
|
|
}
|
|
const bitIndex = this.factIndices.get(key);
|
|
if (value) {
|
|
this.factMask |= (1n << bitIndex);
|
|
} else {
|
|
this.factMask &= ~(1n << bitIndex);
|
|
}
|
|
}
|
|
|
|
addRule(condition, action, description) {
|
|
this.rules.push({ condition, action, description });
|
|
}
|
|
|
|
reason() {
|
|
this.rules.forEach(rule => {
|
|
if (rule.condition(this.facts)) {
|
|
const result = rule.action(this.facts);
|
|
if (result) {
|
|
this.logReasoning(rule.description, result);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
logReasoning(ruleDesc, outcome) {
|
|
const entry = { timestamp: Date.now(), rule: ruleDesc, outcome: outcome };
|
|
this.reasoningLog.unshift(entry);
|
|
if (this.reasoningLog.length > 5) this.reasoningLog.pop();
|
|
|
|
const container = document.getElementById('symbolic-log-content');
|
|
if (container) {
|
|
const logDiv = document.createElement('div');
|
|
logDiv.className = 'symbolic-log-entry';
|
|
logDiv.innerHTML = `<span class=\symbolic-rule\>[RULE] ${ruleDesc}</span><span class=\symbolic-outcome\>→ ${outcome}</span>`;
|
|
container.prepend(logDiv);
|
|
if (container.children.length > 5) container.lastElementChild.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class AgentFSM {
|
|
constructor(agentId, initialState, blackboard = null) {
|
|
this.agentId = agentId;
|
|
this.state = initialState;
|
|
this.transitions = {};
|
|
this.blackboard = blackboard;
|
|
if (this.blackboard) {
|
|
this.blackboard.write(`agent_${this.agentId}_state`, this.state, 'AgentFSM');
|
|
}
|
|
}
|
|
|
|
addTransition(fromState, toState, condition) {
|
|
if (!this.transitions[fromState]) this.transitions[fromState] = [];
|
|
this.transitions[fromState].push({ toState, condition });
|
|
}
|
|
|
|
update(facts) {
|
|
const possibleTransitions = this.transitions[this.state] || [];
|
|
for (const transition of possibleTransitions) {
|
|
if (transition.condition(facts)) {
|
|
const oldState = this.state;
|
|
this.state = transition.toState;
|
|
console.log(`[FSM] Agent ${this.agentId} transitioning: ${oldState} -> ${this.state}`);
|
|
if (this.blackboard) {
|
|
this.blackboard.write(`agent_${this.agentId}_state`, this.state, 'AgentFSM');
|
|
this.blackboard.write(`agent_${this.agentId}_last_transition`, { from: oldState, to: this.state, timestamp: Date.now() }, 'AgentFSM');
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class KnowledgeGraph {
|
|
constructor() {
|
|
this.nodes = new Map();
|
|
this.edges = [];
|
|
}
|
|
|
|
addNode(id, type, metadata = {}) {
|
|
this.nodes.set(id, { id, type, ...metadata });
|
|
}
|
|
|
|
addEdge(from, to, relation) {
|
|
this.edges.push({ from, to, relation });
|
|
}
|
|
|
|
query(from, relation) {
|
|
return this.edges
|
|
.filter(e => e.from === from && e.relation === relation)
|
|
.map(e => this.nodes.get(e.to));
|
|
}
|
|
}
|
|
|
|
export class Blackboard {
|
|
constructor() {
|
|
this.data = {};
|
|
this.subscribers = [];
|
|
}
|
|
|
|
write(key, value, source) {
|
|
const oldValue = this.data[key];
|
|
this.data[key] = value;
|
|
this.notify(key, value, oldValue, source);
|
|
}
|
|
|
|
read(key) { return this.data[key]; }
|
|
|
|
subscribe(callback) { this.subscribers.push(callback); }
|
|
|
|
notify(key, value, oldValue, source) {
|
|
this.subscribers.forEach(sub => sub(key, value, oldValue, source));
|
|
const container = document.getElementById('blackboard-log-content');
|
|
if (container) {
|
|
const entry = document.createElement('div');
|
|
entry.className = 'blackboard-entry';
|
|
entry.innerHTML = `<span class=\bb-source\>[${source}]</span> <span class=\bb-key\>${key}</span>: <span class=\bb-value\>${JSON.stringify(value)}</span>`;
|
|
container.prepend(entry);
|
|
if (container.children.length > 8) container.lastElementChild.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class SymbolicPlanner {
|
|
constructor() {
|
|
this.actions = [];
|
|
this.currentPlan = [];
|
|
}
|
|
|
|
addAction(name, preconditions, effects) {
|
|
this.actions.push({ name, preconditions, effects });
|
|
}
|
|
|
|
heuristic(state, goal) {
|
|
let h = 0;
|
|
for (let key in goal) {
|
|
if (state[key] !== goal[key]) {
|
|
h += Math.abs((state[key] || 0) - (goal[key] || 0));
|
|
}
|
|
}
|
|
return h;
|
|
}
|
|
|
|
findPlan(initialState, goalState) {
|
|
let openSet = [{ state: initialState, plan: [], g: 0, h: this.heuristic(initialState, goalState) }];
|
|
let visited = new Map();
|
|
visited.set(JSON.stringify(initialState), 0);
|
|
|
|
while (openSet.length > 0) {
|
|
openSet.sort((a, b) => (a.g + a.h) - (b.g + b.h));
|
|
let { state, plan, g } = openSet.shift();
|
|
|
|
if (this.isGoalReached(state, goalState)) return plan;
|
|
|
|
for (let action of this.actions) {
|
|
if (this.arePreconditionsMet(state, action.preconditions)) {
|
|
let nextState = { ...state, ...action.effects };
|
|
let stateStr = JSON.stringify(nextState);
|
|
let nextG = g + 1;
|
|
|
|
if (!visited.has(stateStr) || nextG < visited.get(stateStr)) {
|
|
visited.set(stateStr, nextG);
|
|
openSet.push({
|
|
state: nextState,
|
|
plan: [...plan, action.name],
|
|
g: nextG,
|
|
h: this.heuristic(nextState, goalState)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
isGoalReached(state, goal) {
|
|
for (let key in goal) {
|
|
if (state[key] !== goal[key]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
arePreconditionsMet(state, preconditions) {
|
|
for (let key in preconditions) {
|
|
if (state[key] < preconditions[key]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
logPlan(plan) {
|
|
this.currentPlan = plan;
|
|
const container = document.getElementById('planner-log-content');
|
|
if (container) {
|
|
container.innerHTML = '';
|
|
if (!plan || plan.length === 0) {
|
|
container.innerHTML = '<div class=\planner-empty\>NO ACTIVE PLAN</div>';
|
|
return;
|
|
}
|
|
plan.forEach((step, i) => {
|
|
const div = document.createElement('div');
|
|
div.className = 'planner-step';
|
|
div.innerHTML = `<span class=\step-num\>${i+1}.</span> ${step}`;
|
|
container.appendChild(div);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export class HTNPlanner {
|
|
constructor() {
|
|
this.methods = {};
|
|
this.primitiveTasks = {};
|
|
}
|
|
|
|
addMethod(taskName, preconditions, subtasks) {
|
|
if (!this.methods[taskName]) this.methods[taskName] = [];
|
|
this.methods[taskName].push({ preconditions, subtasks });
|
|
}
|
|
|
|
addPrimitiveTask(taskName, preconditions, effects) {
|
|
this.primitiveTasks[taskName] = { preconditions, effects };
|
|
}
|
|
|
|
findPlan(initialState, tasks) {
|
|
return this.decompose(initialState, tasks, []);
|
|
}
|
|
|
|
decompose(state, tasks, plan) {
|
|
if (tasks.length === 0) return plan;
|
|
const [task, ...remainingTasks] = tasks;
|
|
if (this.primitiveTasks[task]) {
|
|
const { preconditions, effects } = this.primitiveTasks[task];
|
|
if (this.arePreconditionsMet(state, preconditions)) {
|
|
const nextState = { ...state, ...effects };
|
|
return this.decompose(nextState, remainingTasks, [...plan, task]);
|
|
}
|
|
return null;
|
|
}
|
|
const methods = this.methods[task] || [];
|
|
for (const method of methods) {
|
|
if (this.arePreconditionsMet(state, method.preconditions)) {
|
|
const result = this.decompose(state, [...method.subtasks, ...remainingTasks], plan);
|
|
if (result) return result;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
arePreconditionsMet(state, preconditions) {
|
|
for (const key in preconditions) {
|
|
if (state[key] < (preconditions[key] || 0)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class CaseBasedReasoner {
|
|
constructor() {
|
|
this.caseLibrary = [];
|
|
}
|
|
|
|
addCase(situation, action, outcome) {
|
|
this.caseLibrary.push({ situation, action, outcome, timestamp: Date.now() });
|
|
}
|
|
|
|
findSimilarCase(currentSituation) {
|
|
let bestMatch = null;
|
|
let maxSimilarity = -1;
|
|
this.caseLibrary.forEach(c => {
|
|
let similarity = this.calculateSimilarity(currentSituation, c.situation);
|
|
if (similarity > maxSimilarity) {
|
|
maxSimilarity = similarity;
|
|
bestMatch = c;
|
|
}
|
|
});
|
|
return maxSimilarity > 0.7 ? bestMatch : null;
|
|
}
|
|
|
|
calculateSimilarity(s1, s2) {
|
|
let score = 0, total = 0;
|
|
for (let key in s1) {
|
|
if (s2[key] !== undefined) {
|
|
score += 1 - Math.abs(s1[key] - s2[key]);
|
|
total += 1;
|
|
}
|
|
}
|
|
return total > 0 ? score / total : 0;
|
|
}
|
|
|
|
logCase(c) {
|
|
const container = document.getElementById('cbr-log-content');
|
|
if (container) {
|
|
const div = document.createElement('div');
|
|
div.className = 'cbr-entry';
|
|
div.innerHTML = `
|
|
<div class=\cbr-match\>SIMILAR CASE FOUND (${(this.calculateSimilarity(symbolicEngine.facts, c.situation) * 100).toFixed(0)}%)</div>
|
|
<div class=\cbr-action\>SUGGESTED: ${c.action}</div>
|
|
<div class=\cbr-outcome\>PREVIOUS OUTCOME: ${c.outcome}</div>
|
|
`;
|
|
container.prepend(div);
|
|
if (container.children.length > 3) container.lastElementChild.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class NeuroSymbolicBridge {
|
|
constructor(symbolicEngine, blackboard) {
|
|
this.engine = symbolicEngine;
|
|
this.blackboard = blackboard;
|
|
this.perceptionLog = [];
|
|
}
|
|
|
|
perceive(rawState) {
|
|
const concepts = [];
|
|
if (rawState.stability < 0.4 && rawState.energy > 60) concepts.push('UNSTABLE_OSCILLATION');
|
|
if (rawState.energy < 30 && rawState.activePortals > 2) concepts.push('CRITICAL_DRAIN_PATTERN');
|
|
concepts.forEach(concept => {
|
|
this.engine.addFact(concept, true);
|
|
this.logPerception(concept);
|
|
});
|
|
return concepts;
|
|
}
|
|
|
|
logPerception(concept) {
|
|
const container = document.getElementById('neuro-bridge-log-content');
|
|
if (container) {
|
|
const div = document.createElement('div');
|
|
div.className = 'neuro-bridge-entry';
|
|
div.innerHTML = `<span class=\neuro-icon\>🧠</span> <span class=\neuro-concept\>${concept}</span>`;
|
|
container.prepend(div);
|
|
if (container.children.length > 5) container.lastElementChild.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class MetaReasoningLayer {
|
|
constructor(planner, blackboard) {
|
|
this.planner = planner;
|
|
this.blackboard = blackboard;
|
|
this.reasoningCache = new Map();
|
|
this.performanceMetrics = { totalReasoningTime: 0, calls: 0 };
|
|
}
|
|
|
|
getCachedPlan(stateKey) {
|
|
const cached = this.reasoningCache.get(stateKey);
|
|
if (cached && (Date.now() - cached.timestamp < 10000)) return cached.plan;
|
|
return null;
|
|
}
|
|
|
|
cachePlan(stateKey, plan) {
|
|
this.reasoningCache.set(stateKey, { plan, timestamp: Date.now() });
|
|
}
|
|
|
|
reflect() {
|
|
const avgTime = this.performanceMetrics.totalReasoningTime / (this.performanceMetrics.calls || 1);
|
|
const container = document.getElementById('meta-log-content');
|
|
if (container) {
|
|
container.innerHTML = `
|
|
<div class=\meta-stat\>CACHE SIZE: ${this.reasoningCache.size}</div>
|
|
<div class=\meta-stat\>AVG LATENCY: ${avgTime.toFixed(2)}ms</div>
|
|
<div class=\meta-stat\>STATUS: ${avgTime > 50 ? 'OPTIMIZING' : 'NOMINAL'}</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
track(startTime) {
|
|
const duration = performance.now() - startTime;
|
|
this.performanceMetrics.totalReasoningTime += duration;
|
|
this.performanceMetrics.calls++;
|
|
}
|
|
}
|