Compare commits

..

2 Commits

Author SHA1 Message Date
4d6d536a1c feat: honor evonia layer as heartbeat source of truth
Some checks failed
CI / validate (pull_request) Failing after 4s
2026-03-28 20:59:19 +00:00
3091d3cdd9 feat: implement websocket bridge and heartbeat generator 2026-03-28 20:59:18 +00:00
14 changed files with 2332 additions and 1710 deletions

564
app.js
View File

@@ -76,569 +76,6 @@ 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);
@@ -658,7 +95,6 @@ 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);

View File

@@ -1,127 +0,0 @@
# 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.*

View File

@@ -1,4 +0,0 @@
"""Phase 20: Global Sovereign Network Simulation.
Decentralized resilience for the Nexus infrastructure.
"""
# ... (code)

View File

@@ -1,4 +0,0 @@
"""Phase 21: Quantum-Resistant Cryptography.
Future-proofing the Nexus security stack.
"""
# ... (code)

View File

@@ -1,4 +0,0 @@
"""Phase 12: Tirith Hardening.
Infrastructure security for The Nexus.
"""
# ... (code)

View File

@@ -1,4 +0,0 @@
"""Phase 2: Multi-Modal World Modeling.
Builds the spatial/temporal map of The Nexus.
"""
# ... (code)

View File

@@ -1,30 +0,0 @@
// ═══ 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;
}
};

View File

@@ -65,38 +65,6 @@
<!-- 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>

View File

@@ -1,35 +0,0 @@
#!/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()

File diff suppressed because it is too large Load Diff

View File

@@ -1,284 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Cookie check</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
color-scheme: light dark;
}
body {
font-family: 'Inter', Helvetica, Arial, sans-serif;
background: light-dark(#F8F8F7, #191919);
color: light-dark(#1f1f1f, #e3e3e3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-height: 100vh;
margin: 0;
padding: 20px;
text-align: center;
}
.container {
background: light-dark(#FFFFFF, #1F1F1F);
padding: 32px;
border-radius: 16px;
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
max-width: min(80%, 500px);
width: 100%;
color: light-dark(#2B2D31, #D4D4D4);
}
h1 {
font-size: 20px;
font-weight: 500;
margin-top: 1rem;
margin-bottom: 1rem;
color: light-dark(#2B2D31, #D4D4D4);
}
p {
font-size: 14px;
color: light-dark(#2B2D31, #D4D4D4);
line-height: 21px;
margin: 0 0 1.5rem 0;
}
.icon {
margin-bottom: 1rem;
line-height: 0;
}
.button-container {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 2rem;
}
button {
background-color: light-dark(#fff, #323232);
color: light-dark(#2B2D31, #FCFCFC);
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
border-radius: 12px;
padding: 8px 12px;
font-size: 14px;
line-height: 21px;
cursor: pointer;
transition: background-color 0.2s;
font-weight: 400;
font-family: 'Inter', Helvetica, Arial, sans-serif;
width: 100%;
}
button:hover {
background-color: light-dark(#EAEAEB, #424242);
}
.hidden {
display: none;
}
/* Loading Spinner Animation */
.spinner {
margin: 0 auto 1.5rem auto;
width: 40px;
height: 40px;
border: 4px solid light-dark(#f0f0f0, #262626);
border-top: 4px solid light-dark(#076eff, #87a9ff); /* Blue color */
border-radius: 50%;
animation: spin 1s linear infinite;
}
.logo {
border-radius: 10px;
display: block;
margin: 0 auto 2rem auto;
}
.logo.hidden {
display: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="container">
<img
class="logo"
src="https://www.gstatic.com/aistudio/ai_studio_favicon_2_256x256.png"
alt="AI Studio Logo"
width="256"
height="256"
/>
<div class="spinner"></div>
<div id="error-ui" class="hidden">
<div class="icon">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="48px"
height="48px"
fill="#D73A49"
>
<path
d="M12,2C6.486,2,2,6.486,2,12s4.486,10,10,10s10-4.486,10-10S17.514,2,12,2z M13,17h-2v-2h2V17z M13,13h-2V7h2V13z"
/>
</svg>
</div>
<div id="stepOne" class="text-container">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="authInSeparateWindowButton" onclick="redirectToReturnUrl(true)">Authenticate in new window</button>
</div>
</div>
<div id="stepTwo" class="text-container hidden">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="interactButton" onclick="redirectToReturnUrl(false)">Close and continue</button>
</div>
</div>
<div id="stepThree" class="text-container hidden">
<h1>Almost there!</h1>
<p>
Grant permission for the required security cookie below.
</p>
<div class="button-container">
<button id="grantPermissionButton" onclick="grantStorageAccess()">Grant permission</button>
</div>
</div>
</div>
</div>
<script>
const AUTH_FLOW_TEST_COOKIE_NAME = '__SECURE-aistudio_auth_flow_may_set_cookies';
const COOKIE_VALUE = 'true';
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.startsWith(name + '=')) {
return cookie.substring(name.length + 1);
}
}
return null;
}
function setAuthFlowTestCookie() {
// Set the cookie's TTL to 1 minute. This is a short lived cookie because it is only used
// when the user does not have an auth token or their auth token needs to be reset.
// Making this cookie too long-lived allows the user to get into a state where they can't
// mint a new auth token.
document.cookie = `${AUTH_FLOW_TEST_COOKIE_NAME}=${COOKIE_VALUE}; Path=/; Secure; SameSite=None; Domain=${window.location.hostname}; Partitioned; Max-Age=60;`;
}
/**
* Returns true if the test cookie is set, false otherwise.
*/
function authFlowTestCookieIsSet() {
return getCookie(AUTH_FLOW_TEST_COOKIE_NAME) === COOKIE_VALUE;
}
/**
* Redirects to the return url. If autoClose is true, then the return url will be opened in a
* new window, and it will be closed automatically when the page loads.
*/
async function redirectToReturnUrl(autoClose) {
const initialReturnUrlStr = new URLSearchParams(window.location.search).get('return_url');
const returnUrl = initialReturnUrlStr ? new URL(initialReturnUrlStr) : null;
// Prevent potentially malicious URLs from being used
if (returnUrl.protocol.toLowerCase() === 'javascript:') {
console.error('Potentially malicious return URL blocked');
return;
}
if (autoClose) {
returnUrl.searchParams.set('__auto_close', '1');
const url = new URL(window.location.href);
url.searchParams.set('return_url', returnUrl.toString());
// Land on the cookie check page first, so the user can interact with it before proceeding
// to the return url where cookies can be set.
window.open(url.toString(), '_blank');
const hasAccess = await document.hasStorageAccess();
document.querySelector('#stepOne').classList.add('hidden');
if (!hasAccess) {
document.querySelector('#stepThree').classList.remove('hidden');
} else {
window.location.reload();
}
} else {
window.location.href = returnUrl.toString();
}
}
/**
* Grants the browser permission to set cookies. If successful, then it redirects to the
* return url.
*/
async function grantStorageAccess() {
try {
await document.requestStorageAccess();
redirectToReturnUrl(false);
} catch (err) {
console.log('error after button click: ', err);
}
}
/**
* Verifies that the browser can set cookies. If it can, then it redirects to the return url.
* If it can't, then it shows the error UI.
*/
function verifyCanSetCookies() {
setAuthFlowTestCookie();
if (authFlowTestCookieIsSet()) {
// Check if we are on the auto-close flow, and if so show the interact button.
const returnUrl = new URLSearchParams(window.location.search).get('return_url');
const autoClose = new URL(returnUrl).searchParams.has('__auto_close');
if (autoClose) {
document.querySelector('#stepOne').classList.add('hidden');
document.querySelector('#stepTwo').classList.remove('hidden');
} else {
redirectToReturnUrl(false);
return;
}
}
// The cookie could not be set, so initiate the recovery flow.
document.querySelector('.logo').classList.add('hidden');
document.querySelector('.spinner').classList.add('hidden');
document.querySelector('#error-ui').classList.remove('hidden');
}
// Start the cookie verification process.
verifyCanSetCookies();
</script>
</body>
</html>

View File

@@ -1,284 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Cookie check</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
color-scheme: light dark;
}
body {
font-family: 'Inter', Helvetica, Arial, sans-serif;
background: light-dark(#F8F8F7, #191919);
color: light-dark(#1f1f1f, #e3e3e3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-height: 100vh;
margin: 0;
padding: 20px;
text-align: center;
}
.container {
background: light-dark(#FFFFFF, #1F1F1F);
padding: 32px;
border-radius: 16px;
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
max-width: min(80%, 500px);
width: 100%;
color: light-dark(#2B2D31, #D4D4D4);
}
h1 {
font-size: 20px;
font-weight: 500;
margin-top: 1rem;
margin-bottom: 1rem;
color: light-dark(#2B2D31, #D4D4D4);
}
p {
font-size: 14px;
color: light-dark(#2B2D31, #D4D4D4);
line-height: 21px;
margin: 0 0 1.5rem 0;
}
.icon {
margin-bottom: 1rem;
line-height: 0;
}
.button-container {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 2rem;
}
button {
background-color: light-dark(#fff, #323232);
color: light-dark(#2B2D31, #FCFCFC);
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
border-radius: 12px;
padding: 8px 12px;
font-size: 14px;
line-height: 21px;
cursor: pointer;
transition: background-color 0.2s;
font-weight: 400;
font-family: 'Inter', Helvetica, Arial, sans-serif;
width: 100%;
}
button:hover {
background-color: light-dark(#EAEAEB, #424242);
}
.hidden {
display: none;
}
/* Loading Spinner Animation */
.spinner {
margin: 0 auto 1.5rem auto;
width: 40px;
height: 40px;
border: 4px solid light-dark(#f0f0f0, #262626);
border-top: 4px solid light-dark(#076eff, #87a9ff); /* Blue color */
border-radius: 50%;
animation: spin 1s linear infinite;
}
.logo {
border-radius: 10px;
display: block;
margin: 0 auto 2rem auto;
}
.logo.hidden {
display: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="container">
<img
class="logo"
src="https://www.gstatic.com/aistudio/ai_studio_favicon_2_256x256.png"
alt="AI Studio Logo"
width="256"
height="256"
/>
<div class="spinner"></div>
<div id="error-ui" class="hidden">
<div class="icon">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="48px"
height="48px"
fill="#D73A49"
>
<path
d="M12,2C6.486,2,2,6.486,2,12s4.486,10,10,10s10-4.486,10-10S17.514,2,12,2z M13,17h-2v-2h2V17z M13,13h-2V7h2V13z"
/>
</svg>
</div>
<div id="stepOne" class="text-container">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="authInSeparateWindowButton" onclick="redirectToReturnUrl(true)">Authenticate in new window</button>
</div>
</div>
<div id="stepTwo" class="text-container hidden">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="interactButton" onclick="redirectToReturnUrl(false)">Close and continue</button>
</div>
</div>
<div id="stepThree" class="text-container hidden">
<h1>Almost there!</h1>
<p>
Grant permission for the required security cookie below.
</p>
<div class="button-container">
<button id="grantPermissionButton" onclick="grantStorageAccess()">Grant permission</button>
</div>
</div>
</div>
</div>
<script>
const AUTH_FLOW_TEST_COOKIE_NAME = '__SECURE-aistudio_auth_flow_may_set_cookies';
const COOKIE_VALUE = 'true';
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.startsWith(name + '=')) {
return cookie.substring(name.length + 1);
}
}
return null;
}
function setAuthFlowTestCookie() {
// Set the cookie's TTL to 1 minute. This is a short lived cookie because it is only used
// when the user does not have an auth token or their auth token needs to be reset.
// Making this cookie too long-lived allows the user to get into a state where they can't
// mint a new auth token.
document.cookie = `${AUTH_FLOW_TEST_COOKIE_NAME}=${COOKIE_VALUE}; Path=/; Secure; SameSite=None; Domain=${window.location.hostname}; Partitioned; Max-Age=60;`;
}
/**
* Returns true if the test cookie is set, false otherwise.
*/
function authFlowTestCookieIsSet() {
return getCookie(AUTH_FLOW_TEST_COOKIE_NAME) === COOKIE_VALUE;
}
/**
* Redirects to the return url. If autoClose is true, then the return url will be opened in a
* new window, and it will be closed automatically when the page loads.
*/
async function redirectToReturnUrl(autoClose) {
const initialReturnUrlStr = new URLSearchParams(window.location.search).get('return_url');
const returnUrl = initialReturnUrlStr ? new URL(initialReturnUrlStr) : null;
// Prevent potentially malicious URLs from being used
if (returnUrl.protocol.toLowerCase() === 'javascript:') {
console.error('Potentially malicious return URL blocked');
return;
}
if (autoClose) {
returnUrl.searchParams.set('__auto_close', '1');
const url = new URL(window.location.href);
url.searchParams.set('return_url', returnUrl.toString());
// Land on the cookie check page first, so the user can interact with it before proceeding
// to the return url where cookies can be set.
window.open(url.toString(), '_blank');
const hasAccess = await document.hasStorageAccess();
document.querySelector('#stepOne').classList.add('hidden');
if (!hasAccess) {
document.querySelector('#stepThree').classList.remove('hidden');
} else {
window.location.reload();
}
} else {
window.location.href = returnUrl.toString();
}
}
/**
* Grants the browser permission to set cookies. If successful, then it redirects to the
* return url.
*/
async function grantStorageAccess() {
try {
await document.requestStorageAccess();
redirectToReturnUrl(false);
} catch (err) {
console.log('error after button click: ', err);
}
}
/**
* Verifies that the browser can set cookies. If it can, then it redirects to the return url.
* If it can't, then it shows the error UI.
*/
function verifyCanSetCookies() {
setAuthFlowTestCookie();
if (authFlowTestCookieIsSet()) {
// Check if we are on the auto-close flow, and if so show the interact button.
const returnUrl = new URLSearchParams(window.location.search).get('return_url');
const autoClose = new URL(returnUrl).searchParams.has('__auto_close');
if (autoClose) {
document.querySelector('#stepOne').classList.add('hidden');
document.querySelector('#stepTwo').classList.remove('hidden');
} else {
redirectToReturnUrl(false);
return;
}
}
// The cookie could not be set, so initiate the recovery flow.
document.querySelector('.logo').classList.add('hidden');
document.querySelector('.spinner').classList.add('hidden');
document.querySelector('#error-ui').classList.remove('hidden');
}
// Start the cookie verification process.
verifyCanSetCookies();
</script>
</body>
</html>

203
server.ts Normal file
View File

@@ -0,0 +1,203 @@
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();

View File

@@ -977,75 +977,3 @@ 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; }