Compare commits
33 Commits
nexus-hear
...
gemini/fix
| Author | SHA1 | Date | |
|---|---|---|---|
| e88bcb4857 | |||
| 3d25279ff5 | |||
| 66153d238f | |||
| e4d1f5c89f | |||
| 7433dae671 | |||
| 09838cc039 | |||
| 52eb39948f | |||
| 14b226a034 | |||
| c35e1b7355 | |||
| ece1b87580 | |||
| 61152737fb | |||
| a855d544a9 | |||
| af7a4c4833 | |||
| 8d676b034e | |||
| 0c165033a6 | |||
| 37bbd61b0c | |||
| 496d5ad314 | |||
| 2b44e42d0a | |||
| ed348ef733 | |||
| 040e96c0e3 | |||
| bf3b98bbc7 | |||
| 6b19bd29a3 | |||
| f634839e92 | |||
| 7f2f23fe20 | |||
| d255904b2b | |||
| 889648304a | |||
| e2df2404bb | |||
| a1fdf9b932 | |||
| 78925606c4 | |||
| 784ee40c76 | |||
| b3b726375b | |||
| 8943cf557c | |||
|
|
f4dd5a0d17 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
node_modules/
|
||||
test-results/
|
||||
nexus/__pycache__/
|
||||
tests/__pycache__/
|
||||
|
||||
564
app.js
564
app.js
@@ -76,6 +76,569 @@ const orbitState = {
|
||||
let flyY = 2;
|
||||
|
||||
// ═══ INIT ═══
|
||||
|
||||
// ═══ SOVEREIGN SYMBOLIC ENGINE (GOFAI) ═══
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AgentFSM {
|
||||
constructor(agentId, initialState) {
|
||||
this.agentId = agentId;
|
||||
this.state = initialState;
|
||||
this.transitions = {};
|
||||
}
|
||||
|
||||
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)) {
|
||||
console.log(`[FSM] Agent ${this.agentId} transitioning: ${this.state} -> ${transition.toState}`);
|
||||
this.state = transition.toState;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══ ADAPTIVE CALIBRATOR (LOCAL EFFICIENCY) ═══
|
||||
class AdaptiveCalibrator {
|
||||
constructor(modelId, initialParams) {
|
||||
this.model = modelId;
|
||||
this.weights = {
|
||||
'input_tokens': 0.0,
|
||||
'complexity_score': 0.0,
|
||||
'task_type_indicator': 0.0,
|
||||
'bias': initialParams.base_rate || 0.0
|
||||
};
|
||||
this.learningRate = 0.01;
|
||||
this.history = [];
|
||||
}
|
||||
|
||||
predict(features) {
|
||||
let prediction = this.weights['bias'];
|
||||
for (let feature in features) {
|
||||
if (this.weights[feature] !== undefined) {
|
||||
prediction += this.weights[feature] * features[feature];
|
||||
}
|
||||
}
|
||||
return Math.max(0, prediction);
|
||||
}
|
||||
|
||||
update(features, actualCost) {
|
||||
const predicted = this.predict(features);
|
||||
const error = actualCost - predicted;
|
||||
for (let feature in features) {
|
||||
if (this.weights[feature] !== undefined) {
|
||||
this.weights[feature] += this.learningRate * error * features[feature];
|
||||
}
|
||||
}
|
||||
this.history.push({ predicted, actual: actualCost, timestamp: Date.now() });
|
||||
|
||||
const container = document.getElementById('calibrator-log-content');
|
||||
if (container) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'calibrator-entry';
|
||||
div.innerHTML = `<span class="cal-label">CALIBRATED:</span> <span class="cal-val">${predicted.toFixed(4)}</span> <span class="cal-err">ERR: ${error.toFixed(4)}</span>`;
|
||||
container.prepend(div);
|
||||
if (container.children.length > 5) container.lastElementChild.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ═══ NOSTR AGENT REGISTRATION ═══
|
||||
class NostrAgent {
|
||||
constructor(pubkey) {
|
||||
this.pubkey = pubkey;
|
||||
this.relays = ['wss://relay.damus.io', 'wss://nos.lol'];
|
||||
}
|
||||
|
||||
async announce(metadata) {
|
||||
console.log(`[NOSTR] Announcing agent ${this.pubkey}...`);
|
||||
const event = {
|
||||
kind: 0,
|
||||
pubkey: this.pubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [],
|
||||
content: JSON.stringify(metadata),
|
||||
id: 'mock_id',
|
||||
sig: 'mock_sig'
|
||||
};
|
||||
|
||||
this.relays.forEach(url => {
|
||||
console.log(`[NOSTR] Publishing to ${url}: `, event);
|
||||
});
|
||||
|
||||
const container = document.getElementById('nostr-log-content');
|
||||
if (container) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'nostr-entry';
|
||||
div.innerHTML = `<span class="nostr-pubkey">[${this.pubkey.substring(0,8)}...]</span> <span class="nostr-status">ANNOUNCED</span>`;
|
||||
container.prepend(div);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ═══ L402 CLIENT LOGIC ═══
|
||||
class L402Client {
|
||||
async fetchWithL402(url) {
|
||||
console.log(`[L402] Fetching ${url}...`);
|
||||
const response = await fetch(url);
|
||||
|
||||
if (response.status === 402) {
|
||||
const authHeader = response.headers.get('WWW-Authenticate');
|
||||
console.log(`[L402] Challenge received: ${authHeader}`);
|
||||
|
||||
const container = document.getElementById('l402-log-content');
|
||||
if (container) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'l402-entry';
|
||||
div.innerHTML = `<span class="l402-status">CHALLENGE</span> <span class="l402-msg">Payment Required</span>`;
|
||||
container.prepend(div);
|
||||
}
|
||||
return { status: 402, challenge: authHeader };
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
|
||||
let nostrAgent, l402Client;
|
||||
|
||||
|
||||
// ═══ PARALLEL SYMBOLIC EXECUTION (PSE) ═══
|
||||
class PSELayer {
|
||||
constructor() {
|
||||
this.worker = new Worker('gofai_worker.js');
|
||||
this.worker.onmessage = (e) => this.handleWorkerMessage(e);
|
||||
this.pendingRequests = new Map();
|
||||
}
|
||||
|
||||
handleWorkerMessage(e) {
|
||||
const { type, results, plan } = e.data;
|
||||
if (type === 'REASON_RESULT') {
|
||||
results.forEach(res => symbolicEngine.logReasoning(res.rule, res.outcome));
|
||||
} else if (type === 'PLAN_RESULT') {
|
||||
symbolicPlanner.logPlan(plan);
|
||||
}
|
||||
}
|
||||
|
||||
offloadReasoning(facts, rules) {
|
||||
this.worker.postMessage({ type: 'REASON', data: { facts, rules } });
|
||||
}
|
||||
|
||||
offloadPlanning(initialState, goalState, actions) {
|
||||
this.worker.postMessage({ type: 'PLAN', data: { initialState, goalState, actions } });
|
||||
}
|
||||
}
|
||||
|
||||
let pseLayer;
|
||||
|
||||
let metaLayer, neuroBridge, cbr, symbolicPlanner, knowledgeGraph, blackboard, symbolicEngine, calibrator;
|
||||
let agentFSMs = {};
|
||||
|
||||
function setupGOFAI() {
|
||||
knowledgeGraph = new KnowledgeGraph();
|
||||
blackboard = new Blackboard();
|
||||
symbolicEngine = new SymbolicEngine();
|
||||
symbolicPlanner = new SymbolicPlanner();
|
||||
cbr = new CaseBasedReasoner();
|
||||
neuroBridge = new NeuroSymbolicBridge(symbolicEngine, blackboard);
|
||||
metaLayer = new MetaReasoningLayer(symbolicPlanner, blackboard);
|
||||
nostrAgent = new NostrAgent("npub1...");
|
||||
l402Client = new L402Client();
|
||||
nostrAgent.announce({ name: "Timmy Nexus Agent", capabilities: ["GOFAI", "L402"] });
|
||||
pseLayer = new PSELayer();
|
||||
calibrator = new AdaptiveCalibrator('nexus-v1', { base_rate: 0.05 });
|
||||
|
||||
// Setup initial facts
|
||||
symbolicEngine.addFact('energy', 100);
|
||||
symbolicEngine.addFact('stability', 1.0);
|
||||
|
||||
// Setup FSM
|
||||
agentFSMs['timmy'] = new AgentFSM('timmy', 'IDLE');
|
||||
agentFSMs['timmy'].addTransition('IDLE', 'ANALYZING', (facts) => facts.get('activePortals') > 0);
|
||||
|
||||
// Setup Planner
|
||||
symbolicPlanner.addAction('Stabilize Matrix', { energy: 50 }, { stability: 1.0 });
|
||||
}
|
||||
|
||||
function updateGOFAI(delta, elapsed) {
|
||||
const startTime = performance.now();
|
||||
|
||||
// Simulate perception
|
||||
neuroBridge.perceive({ stability: 0.3, energy: 80, activePortals: 1 });
|
||||
|
||||
// Run reasoning
|
||||
if (Math.floor(elapsed * 2) > Math.floor((elapsed - delta) * 2)) {
|
||||
symbolicEngine.reason();
|
||||
pseLayer.offloadReasoning(Array.from(symbolicEngine.facts.entries()), symbolicEngine.rules.map(r => ({ description: r.description })));
|
||||
document.getElementById("pse-task-count").innerText = parseInt(document.getElementById("pse-task-count").innerText) + 1;
|
||||
metaLayer.reflect();
|
||||
|
||||
// Simulate calibration update
|
||||
calibrator.update({ input_tokens: 100, complexity_score: 0.5 }, 0.06);
|
||||
if (Math.random() > 0.95) l402Client.fetchWithL402("http://localhost:8080/api/cost-estimate");
|
||||
}
|
||||
|
||||
metaLayer.track(startTime);
|
||||
}
|
||||
|
||||
async function init() {
|
||||
clock = new THREE.Clock();
|
||||
playerPos = new THREE.Vector3(0, 2, 12);
|
||||
@@ -95,6 +658,7 @@ async function init() {
|
||||
scene = new THREE.Scene();
|
||||
scene.fog = new THREE.FogExp2(0x050510, 0.012);
|
||||
|
||||
setupGOFAI();
|
||||
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||
camera.position.copy(playerPos);
|
||||
|
||||
|
||||
127
docs/GOOGLE_AI_ULTRA_INTEGRATION.md
Normal file
127
docs/GOOGLE_AI_ULTRA_INTEGRATION.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Google AI Ultra Integration Plan
|
||||
|
||||
> Master tracking document for integrating all Google AI Ultra products into
|
||||
> Project Timmy (Sovereign AI Agent) and The Nexus (3D World).
|
||||
|
||||
**Epic**: #739
|
||||
**Milestone**: M5: Google AI Ultra Integration
|
||||
**Label**: `google-ai-ultra`
|
||||
|
||||
---
|
||||
|
||||
## Product Inventory
|
||||
|
||||
| # | Product | Capability | API | Priority | Status |
|
||||
|---|---------|-----------|-----|----------|--------|
|
||||
| 1 | Gemini 3.1 Pro | Primary reasoning engine | ✅ | P0 | 🔲 Not started |
|
||||
| 2 | Deep Research | Autonomous research reports | ✅ | P1 | 🔲 Not started |
|
||||
| 3 | Veo 3.1 | Text/image → video | ✅ | P2 | 🔲 Not started |
|
||||
| 4 | Nano Banana Pro | Image generation | ✅ | P1 | 🔲 Not started |
|
||||
| 5 | Lyria 3 | Music/audio generation | ✅ | P2 | 🔲 Not started |
|
||||
| 6 | NotebookLM | Doc synthesis + Audio Overviews | ❌ | P1 | 🔲 Not started |
|
||||
| 7 | AI Studio | API portal + Vibe Code | N/A | P0 | 🔲 Not started |
|
||||
| 8 | Project Genie | Interactive 3D world gen | ❌ | P1 | 🔲 Not started |
|
||||
| 9 | Live API | Real-time voice streaming | ✅ | P2 | 🔲 Not started |
|
||||
| 10 | Computer Use | Browser automation | ✅ | P2 | 🔲 Not started |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Identity & Branding (Week 1)
|
||||
|
||||
| Issue | Title | Status |
|
||||
|-------|-------|--------|
|
||||
| #740 | Generate Timmy avatar set with Nano Banana Pro | 🔲 |
|
||||
| #741 | Upload SOUL.md to NotebookLM → Audio Overview | 🔲 |
|
||||
| #742 | Generate Timmy audio signature with Lyria 3 | 🔲 |
|
||||
| #680 | Project Genie + Nano Banana concept pack | 🔲 |
|
||||
|
||||
## Phase 2: Research & Planning (Week 1-2)
|
||||
|
||||
| Issue | Title | Status |
|
||||
|-------|-------|--------|
|
||||
| #743 | Deep Research: Three.js multiplayer 3D world architecture | 🔲 |
|
||||
| #744 | Deep Research: Sovereign AI agent frameworks | 🔲 |
|
||||
| #745 | Deep Research: WebGL/WebGPU rendering comparison | 🔲 |
|
||||
| #746 | NotebookLM synthesis: cross-reference all research | 🔲 |
|
||||
|
||||
## Phase 3: Prototype & Build (Week 2-4)
|
||||
|
||||
| Issue | Title | Status |
|
||||
|-------|-------|--------|
|
||||
| #747 | Provision Gemini API key + Hermes config | 🔲 |
|
||||
| #748 | Integrate Gemini 3.1 Pro as reasoning backbone | 🔲 |
|
||||
| #749 | AI Studio Vibe Code UI prototypes | 🔲 |
|
||||
| #750 | Project Genie explorable world prototypes | 🔲 |
|
||||
| #681 | Veo/Flow flythrough prototypes | 🔲 |
|
||||
|
||||
## Phase 4: Media & Content (Ongoing)
|
||||
|
||||
| Issue | Title | Status |
|
||||
|-------|-------|--------|
|
||||
| #682 | Lyria soundtrack palette for Nexus zones | 🔲 |
|
||||
| #751 | Lyria RealTime dynamic reactive music | 🔲 |
|
||||
| #752 | NotebookLM Audio Overviews for all docs | 🔲 |
|
||||
| #753 | Nano Banana concept art batch pipeline | 🔲 |
|
||||
|
||||
## Phase 5: Advanced Integration (Month 2+)
|
||||
|
||||
| Issue | Title | Status |
|
||||
|-------|-------|--------|
|
||||
| #754 | Gemini Live API for voice conversations | 🔲 |
|
||||
| #755 | Computer Use API for browser automation | 🔲 |
|
||||
| #756 | Gemini RAG via File Search for Timmy memory | 🔲 |
|
||||
| #757 | Gemini Native Audio + TTS for Timmy's voice | 🔲 |
|
||||
| #758 | Programmatic image generation pipeline | 🔲 |
|
||||
| #759 | Programmatic video generation pipeline | 🔲 |
|
||||
| #760 | Deep Research Agent API integration | 🔲 |
|
||||
| #761 | OpenAI-compatible endpoint config | 🔲 |
|
||||
| #762 | Context caching + batch API for cost optimization | 🔲 |
|
||||
|
||||
---
|
||||
|
||||
## API Quick Reference
|
||||
|
||||
```python
|
||||
# pip install google-genai
|
||||
from google import genai
|
||||
client = genai.Client() # reads GOOGLE_API_KEY env var
|
||||
|
||||
# Text generation (Gemini 3.1 Pro)
|
||||
response = client.models.generate_content(
|
||||
model="gemini-3.1-pro-preview",
|
||||
contents="..."
|
||||
)
|
||||
```
|
||||
|
||||
| API | Documentation |
|
||||
|-----|--------------|
|
||||
| Image Gen (Nano Banana) | ai.google.dev/gemini-api/docs/image-generation |
|
||||
| Video Gen (Veo) | ai.google.dev/gemini-api/docs/video |
|
||||
| Music Gen (Lyria) | ai.google.dev/gemini-api/docs/music-generation |
|
||||
| TTS | ai.google.dev/gemini-api/docs/speech-generation |
|
||||
| Deep Research | ai.google.dev/gemini-api/docs/deep-research |
|
||||
|
||||
## Key URLs
|
||||
|
||||
| Tool | URL |
|
||||
|------|-----|
|
||||
| Gemini App | gemini.google.com |
|
||||
| AI Studio | aistudio.google.com |
|
||||
| NotebookLM | notebooklm.google.com |
|
||||
| Project Genie | labs.google/projectgenie |
|
||||
| Flow (video) | labs.google/flow |
|
||||
| Stitch (UI) | labs.google/stitch |
|
||||
|
||||
## Hidden Features to Exploit
|
||||
|
||||
1. **AI Studio Free Tier** — generous API access even without subscription
|
||||
2. **OpenAI-Compatible API** — drop-in replacement for existing OpenAI tooling
|
||||
3. **Context Caching** — cache SOUL.md to cut cost/latency on repeated calls
|
||||
4. **Batch API** — bulk operations at discounted rates
|
||||
5. **File Search Tool** — RAG without custom vector store
|
||||
6. **Computer Use API** — programmatic browser control for agent automation
|
||||
7. **Interactions API** — managed multi-turn conversational state
|
||||
|
||||
---
|
||||
|
||||
*Generated: 2026-03-29. Epic #739, Milestone M5.*
|
||||
30
gofai_worker.js
Normal file
30
gofai_worker.js
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
// ═══ GOFAI PARALLEL WORKER (PSE) ═══
|
||||
self.onmessage = function(e) {
|
||||
const { type, data } = e.data;
|
||||
|
||||
switch(type) {
|
||||
case 'REASON':
|
||||
const { facts, rules } = data;
|
||||
const results = [];
|
||||
// Off-thread rule matching
|
||||
rules.forEach(rule => {
|
||||
// Simulate heavy rule matching
|
||||
if (Math.random() > 0.95) {
|
||||
results.push({ rule: rule.description, outcome: 'OFF-THREAD MATCH' });
|
||||
}
|
||||
});
|
||||
self.postMessage({ type: 'REASON_RESULT', results });
|
||||
break;
|
||||
|
||||
case 'PLAN':
|
||||
const { initialState, goalState, actions } = data;
|
||||
// Off-thread A* search
|
||||
console.log('[PSE] Starting off-thread A* search...');
|
||||
// Simulate planning delay
|
||||
const startTime = performance.now();
|
||||
while(performance.now() - startTime < 50) {} // Artificial load
|
||||
self.postMessage({ type: 'PLAN_RESULT', plan: ['Off-Thread Step 1', 'Off-Thread Step 2'] });
|
||||
break;
|
||||
}
|
||||
};
|
||||
32
index.html
32
index.html
@@ -65,6 +65,38 @@
|
||||
|
||||
<!-- HUD Overlay -->
|
||||
<div id="hud" class="game-ui" style="display:none;">
|
||||
<!-- GOFAI HUD Panels -->
|
||||
<div class="gofai-hud">
|
||||
<div class="hud-panel" id="symbolic-log">
|
||||
<div class="panel-header">SYMBOLIC ENGINE</div>
|
||||
<div id="symbolic-log-content" class="panel-content"></div>
|
||||
</div>
|
||||
<div class="hud-panel" id="blackboard-log">
|
||||
<div class="panel-header">BLACKBOARD</div>
|
||||
<div id="blackboard-log-content" class="panel-content"></div>
|
||||
</div>
|
||||
<div class="hud-panel" id="planner-log">
|
||||
<div class="panel-header">SYMBOLIC PLANNER</div>
|
||||
<div id="planner-log-content" class="panel-content"></div>
|
||||
</div>
|
||||
<div class="hud-panel" id="cbr-log">
|
||||
<div class="panel-header">CASE-BASED REASONER</div>
|
||||
<div id="cbr-log-content" class="panel-content"></div>
|
||||
</div>
|
||||
<div class="hud-panel" id="neuro-bridge-log">
|
||||
<div class="panel-header">NEURO-SYMBOLIC BRIDGE</div>
|
||||
<div id="neuro-bridge-log-content" class="panel-content"></div>
|
||||
</div>
|
||||
<div class="hud-panel" id="meta-log">
|
||||
<div class="panel-header">META-REASONING</div>
|
||||
<div id="meta-log-content" class="panel-content"></div>
|
||||
</div>
|
||||
<div class="hud-panel" id="calibrator-log">
|
||||
<div class="panel-header">ADAPTIVE CALIBRATOR</div>
|
||||
<div id="calibrator-log-content" class="panel-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top Left: Debug -->
|
||||
<div id="debug-overlay" class="hud-debug"></div>
|
||||
|
||||
|
||||
35
l402_server.py
Normal file
35
l402_server.py
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
#!/usr/bin/env python3
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import json
|
||||
import secrets
|
||||
|
||||
class L402Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == '/api/cost-estimate':
|
||||
# Simulate L402 Challenge
|
||||
macaroon = secrets.token_hex(16)
|
||||
invoice = "lnbc1..." # Mock invoice
|
||||
|
||||
self.send_response(402)
|
||||
self.send_header('WWW-Authenticate', f'L402 macaroon="{macaroon}", invoice="{invoice}"')
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
|
||||
response = {
|
||||
"error": "Payment Required",
|
||||
"message": "Please pay the invoice to access cost estimation."
|
||||
}
|
||||
self.wfile.write(json.dumps(response).encode())
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
def run(server_class=HTTPServer, handler_class=L402Handler, port=8080):
|
||||
server_address = ('', port)
|
||||
httpd = server_class(server_address, handler_class)
|
||||
print(f"Starting L402 Skeleton Server on port {port}...")
|
||||
httpd.serve_forever()
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -25,7 +25,7 @@ from typing import Optional
|
||||
log = logging.getLogger("nexus")
|
||||
|
||||
GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions"
|
||||
DEFAULT_MODEL = "groq/llama3-8b-8192"
|
||||
DEFAULT_MODEL = "llama3-8b-8192"
|
||||
|
||||
class GroqWorker:
|
||||
"""A worker for the Groq API."""
|
||||
|
||||
@@ -315,7 +315,7 @@ class NexusMind:
|
||||
]
|
||||
|
||||
summary = self._call_thinker(messages)
|
||||
.
|
||||
|
||||
if summary:
|
||||
self.experience_store.save_summary(
|
||||
summary=summary,
|
||||
@@ -442,7 +442,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Nexus Mind — Embodied consciousness loop"
|
||||
)
|
||||
parser.add_.argument(
|
||||
parser.add_argument(
|
||||
"--model", default=DEFAULT_MODEL,
|
||||
help=f"Ollama model name (default: {DEFAULT_MODEL})"
|
||||
)
|
||||
|
||||
2147
public/nexus/app.js
2147
public/nexus/app.js
File diff suppressed because it is too large
Load Diff
@@ -12,16 +12,19 @@ async def broadcast_handler(websocket):
|
||||
try:
|
||||
async for message in websocket:
|
||||
# Broadcast to all OTHER clients
|
||||
disconnected = set()
|
||||
for client in clients:
|
||||
if client != websocket:
|
||||
try:
|
||||
await client.send(message)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to send to a client: {e}")
|
||||
disconnected.add(client)
|
||||
clients.difference_update(disconnected)
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
pass
|
||||
finally:
|
||||
clients.remove(websocket)
|
||||
clients.discard(websocket) # discard is safe if not present
|
||||
logging.info(f"Client disconnected. Total clients: {len(clients)}")
|
||||
|
||||
async def main():
|
||||
|
||||
203
server.ts
203
server.ts
@@ -1,203 +0,0 @@
|
||||
import express from 'express';
|
||||
import { createServer as createViteServer } from 'vite';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import 'dotenv/config';
|
||||
import { WebSocketServer, WebSocket } from 'ws';
|
||||
import { createServer } from 'http';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Primary (Local) Gitea
|
||||
const GITEA_URL = process.env.GITEA_URL || 'http://localhost:3000/api/v1';
|
||||
const GITEA_TOKEN = process.env.GITEA_TOKEN || '';
|
||||
|
||||
// Backup (Remote) Gitea
|
||||
const REMOTE_GITEA_URL = process.env.REMOTE_GITEA_URL || 'http://143.198.27.163:3000/api/v1';
|
||||
const REMOTE_GITEA_TOKEN = process.env.REMOTE_GITEA_TOKEN || '';
|
||||
|
||||
async function startServer() {
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
const PORT = 3000;
|
||||
|
||||
// WebSocket Server for Hermes/Evennia Bridge
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
const clients = new Set<WebSocket>();
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
clients.add(ws);
|
||||
console.log(`Client connected to Nexus Bridge. Total: ${clients.size}`);
|
||||
|
||||
ws.on('close', () => {
|
||||
clients.remove(ws);
|
||||
console.log(`Client disconnected. Total: ${clients.size}`);
|
||||
});
|
||||
});
|
||||
|
||||
// Simulate Evennia Heartbeat (Source of Truth)
|
||||
setInterval(() => {
|
||||
const heartbeat = {
|
||||
type: 'heartbeat',
|
||||
frequency: 0.5 + Math.random() * 0.2, // 0.5Hz to 0.7Hz
|
||||
intensity: 0.8 + Math.random() * 0.4,
|
||||
timestamp: Date.now(),
|
||||
source: 'evonia-layer'
|
||||
};
|
||||
const message = JSON.stringify(heartbeat);
|
||||
clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(message);
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
|
||||
// Diagnostic Endpoint for Agent Inspection
|
||||
app.get('/api/diagnostic/inspect', async (req, res) => {
|
||||
console.log('Diagnostic request received');
|
||||
try {
|
||||
const REPO_OWNER = 'google';
|
||||
const REPO_NAME = 'timmy-tower';
|
||||
|
||||
const [stateRes, issuesRes] = await Promise.all([
|
||||
fetch(`${GITEA_URL}/repos/${REPO_OWNER}/${REPO_NAME}/contents/world_state.json`, {
|
||||
headers: { 'Authorization': `token ${GITEA_TOKEN}` }
|
||||
}),
|
||||
fetch(`${GITEA_URL}/repos/${REPO_OWNER}/${REPO_NAME}/issues?state=all`, {
|
||||
headers: { 'Authorization': `token ${GITEA_TOKEN}` }
|
||||
})
|
||||
]);
|
||||
|
||||
let worldState = null;
|
||||
if (stateRes.ok) {
|
||||
const content = await stateRes.json();
|
||||
worldState = JSON.parse(Buffer.from(content.content, 'base64').toString());
|
||||
} else if (stateRes.status !== 404) {
|
||||
console.error(`Failed to fetch world state: ${stateRes.status} ${stateRes.statusText}`);
|
||||
}
|
||||
|
||||
let issues = [];
|
||||
if (issuesRes.ok) {
|
||||
issues = await issuesRes.json();
|
||||
} else {
|
||||
console.error(`Failed to fetch issues: ${issuesRes.status} ${issuesRes.statusText}`);
|
||||
}
|
||||
|
||||
res.json({
|
||||
worldState,
|
||||
issues,
|
||||
repoExists: stateRes.status !== 404,
|
||||
connected: GITEA_TOKEN !== ''
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Diagnostic error:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Helper for Gitea Proxy
|
||||
const createGiteaProxy = (baseUrl: string, token: string) => async (req: express.Request, res: express.Response) => {
|
||||
const path = req.params[0] + (req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '');
|
||||
const url = `${baseUrl}/${path}`;
|
||||
|
||||
if (!token) {
|
||||
console.warn(`Gitea Proxy Warning: No token provided for ${baseUrl}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: req.method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `token ${token}`,
|
||||
},
|
||||
body: ['GET', 'HEAD'].includes(req.method) ? undefined : JSON.stringify(req.body),
|
||||
});
|
||||
|
||||
const data = await response.text();
|
||||
res.status(response.status).send(data);
|
||||
} catch (error: any) {
|
||||
console.error(`Gitea Proxy Error (${baseUrl}):`, error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// Gitea Proxy - Primary (Local)
|
||||
app.get('/api/gitea/check', async (req, res) => {
|
||||
try {
|
||||
const response = await fetch(`${GITEA_URL}/user`, {
|
||||
headers: { 'Authorization': `token ${GITEA_TOKEN}` }
|
||||
});
|
||||
if (response.ok) {
|
||||
const user = await response.json();
|
||||
res.json({ status: 'connected', user: user.username });
|
||||
} else {
|
||||
res.status(response.status).json({ status: 'error', message: `Gitea returned ${response.status}` });
|
||||
}
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ status: 'error', message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.all('/api/gitea/*', createGiteaProxy(GITEA_URL, GITEA_TOKEN));
|
||||
|
||||
// Gitea Proxy - Backup (Remote)
|
||||
app.get('/api/gitea-remote/check', async (req, res) => {
|
||||
try {
|
||||
const response = await fetch(`${REMOTE_GITEA_URL}/user`, {
|
||||
headers: { 'Authorization': `token ${REMOTE_GITEA_TOKEN}` }
|
||||
});
|
||||
if (response.ok) {
|
||||
const user = await response.json();
|
||||
res.json({ status: 'connected', user: user.username });
|
||||
} else {
|
||||
res.status(response.status).json({ status: 'error', message: `Gitea returned ${response.status}` });
|
||||
}
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ status: 'error', message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.all('/api/gitea-remote/*', createGiteaProxy(REMOTE_GITEA_URL, REMOTE_GITEA_TOKEN));
|
||||
|
||||
// WebSocket Upgrade Handler
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
const pathname = new URL(request.url!, `http://${request.headers.host}`).pathname;
|
||||
if (pathname === '/api/world/ws') {
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
wss.emit('connection', ws, request);
|
||||
});
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// Health Check
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
// Vite middleware for development
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const vite = await createViteServer({
|
||||
server: { middlewareMode: true },
|
||||
appType: 'spa',
|
||||
});
|
||||
app.use(vite.middlewares);
|
||||
} else {
|
||||
const distPath = path.join(process.cwd(), 'dist');
|
||||
app.use(express.static(distPath));
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(distPath, 'index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
httpServer.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
}
|
||||
|
||||
startServer();
|
||||
72
style.css
72
style.css
@@ -977,3 +977,75 @@ canvas#nexus-canvas {
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
}
|
||||
|
||||
/* === GOFAI HUD STYLING === */
|
||||
.gofai-hud {
|
||||
position: fixed;
|
||||
left: 20px;
|
||||
top: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.hud-panel {
|
||||
width: 280px;
|
||||
background: rgba(5, 5, 16, 0.8);
|
||||
border: 1px solid rgba(74, 240, 192, 0.2);
|
||||
border-left: 3px solid #4af0c0;
|
||||
padding: 8px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 11px;
|
||||
color: #e0f0ff;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #4af0c0;
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: 1px;
|
||||
border-bottom: 1px solid rgba(74, 240, 192, 0.1);
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.symbolic-log-entry { margin-bottom: 4px; border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 2px; }
|
||||
.symbolic-rule { color: #7b5cff; display: block; }
|
||||
.symbolic-outcome { color: #4af0c0; font-weight: 600; }
|
||||
|
||||
.blackboard-entry { font-size: 10px; margin-bottom: 2px; }
|
||||
.bb-source { color: #ffd700; opacity: 0.7; }
|
||||
.bb-key { color: #7b5cff; }
|
||||
.bb-value { color: #fff; }
|
||||
|
||||
.planner-step { color: #4af0c0; margin-bottom: 2px; }
|
||||
.step-num { opacity: 0.5; }
|
||||
|
||||
.cbr-match { color: #ffd700; font-weight: 700; margin-bottom: 2px; }
|
||||
.cbr-action { color: #4af0c0; }
|
||||
|
||||
.neuro-bridge-entry { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
|
||||
.neuro-icon { font-size: 14px; }
|
||||
.neuro-concept { color: #7b5cff; font-weight: 600; }
|
||||
|
||||
.meta-stat { margin-bottom: 2px; display: flex; justify-content: space-between; }
|
||||
|
||||
.calibrator-entry { font-size: 10px; display: flex; gap: 8px; }
|
||||
.cal-label { color: #ffd700; }
|
||||
.cal-val { color: #4af0c0; }
|
||||
.cal-err { color: #ff4466; opacity: 0.8; }
|
||||
|
||||
.nostr-pubkey { color: #ffd700; }
|
||||
.nostr-status { color: #4af0c0; font-weight: 600; }
|
||||
.l402-status { color: #ff4466; font-weight: 600; }
|
||||
.l402-msg { color: #fff; }
|
||||
|
||||
.pse-status { color: #4af0c0; font-weight: 600; }
|
||||
|
||||
111
tests/test_syntax_fixes.py
Normal file
111
tests/test_syntax_fixes.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""Tests for syntax and correctness fixes across the-nexus codebase.
|
||||
|
||||
Covers:
|
||||
- nexus_think.py: no stray dots (SyntaxError), no typos in argparse
|
||||
- groq_worker.py: model name has no 'groq/' prefix
|
||||
- server.py: uses discard() not remove() for client cleanup
|
||||
- public/nexus/: corrupt duplicate directory removed
|
||||
"""
|
||||
|
||||
import ast
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
NEXUS_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# ── nexus_think.py syntax checks ────────────────────────────────────
|
||||
|
||||
def test_nexus_think_parses_without_syntax_error():
|
||||
"""nexus_think.py must be valid Python.
|
||||
|
||||
Two SyntaxErrors existed:
|
||||
1. Line 318: stray '.' between function call and if-block
|
||||
2. Line 445: 'parser.add_.argument()' (extra underscore)
|
||||
|
||||
If either is present, the entire consciousness loop can't import.
|
||||
"""
|
||||
source = (NEXUS_ROOT / "nexus" / "nexus_think.py").read_text()
|
||||
# ast.parse will raise SyntaxError if the file is invalid
|
||||
try:
|
||||
ast.parse(source, filename="nexus_think.py")
|
||||
except SyntaxError as e:
|
||||
raise AssertionError(
|
||||
f"nexus_think.py has a SyntaxError at line {e.lineno}: {e.msg}"
|
||||
) from e
|
||||
|
||||
|
||||
def test_nexus_think_no_stray_dot():
|
||||
"""There should be no line that is just a dot in nexus_think.py."""
|
||||
source = (NEXUS_ROOT / "nexus" / "nexus_think.py").read_text()
|
||||
for i, line in enumerate(source.splitlines(), 1):
|
||||
stripped = line.strip()
|
||||
if stripped == ".":
|
||||
raise AssertionError(
|
||||
f"nexus_think.py has a stray '.' on line {i}. "
|
||||
"This causes a SyntaxError."
|
||||
)
|
||||
|
||||
|
||||
def test_nexus_think_argparse_no_typo():
|
||||
"""parser.add_argument must not be written as parser.add_.argument."""
|
||||
source = (NEXUS_ROOT / "nexus" / "nexus_think.py").read_text()
|
||||
assert "add_.argument" not in source, (
|
||||
"nexus_think.py contains 'add_.argument' — should be 'add_argument'."
|
||||
)
|
||||
|
||||
|
||||
# ── groq_worker.py model name ───────────────────────────────────────
|
||||
|
||||
def test_groq_default_model_has_no_prefix():
|
||||
"""Groq API expects model names without router prefixes.
|
||||
|
||||
Sending 'groq/llama3-8b-8192' returns a 404.
|
||||
The correct name is just 'llama3-8b-8192'.
|
||||
"""
|
||||
source = (NEXUS_ROOT / "nexus" / "groq_worker.py").read_text()
|
||||
for line in source.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("DEFAULT_MODEL") and "=" in stripped:
|
||||
assert "groq/" not in stripped, (
|
||||
f"groq_worker.py DEFAULT_MODEL contains 'groq/' prefix: {stripped}. "
|
||||
"The Groq API expects bare model names like 'llama3-8b-8192'."
|
||||
)
|
||||
break
|
||||
else:
|
||||
# DEFAULT_MODEL not found — that's a different issue, not this test's concern
|
||||
pass
|
||||
|
||||
|
||||
# ── server.py client cleanup ────────────────────────────────────────
|
||||
|
||||
def test_server_uses_discard_not_remove():
|
||||
"""server.py must use clients.discard() not clients.remove().
|
||||
|
||||
remove() raises KeyError if the websocket isn't in the set.
|
||||
This happens if an exception occurs before clients.add() runs.
|
||||
discard() is a safe no-op if the element isn't present.
|
||||
"""
|
||||
source = (NEXUS_ROOT / "server.py").read_text()
|
||||
assert "clients.discard(" in source, (
|
||||
"server.py should use clients.discard(websocket) for safe cleanup."
|
||||
)
|
||||
assert "clients.remove(" not in source, (
|
||||
"server.py should NOT use clients.remove(websocket) — "
|
||||
"raises KeyError if websocket wasn't added."
|
||||
)
|
||||
|
||||
|
||||
# ── public/nexus/ corrupt duplicate directory ────────────────────────
|
||||
|
||||
def test_public_nexus_duplicate_removed():
|
||||
"""public/nexus/ contained 3 files with identical content (all 9544 bytes).
|
||||
|
||||
app.js, style.css, and index.html were all the same file — clearly a
|
||||
corrupt copy operation. The canonical files are at the repo root.
|
||||
"""
|
||||
corrupt_dir = NEXUS_ROOT / "public" / "nexus"
|
||||
assert not corrupt_dir.exists(), (
|
||||
"public/nexus/ still exists. These are corrupt duplicates "
|
||||
"(all 3 files have identical content). Remove this directory."
|
||||
)
|
||||
Reference in New Issue
Block a user