Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
eb28e1f8e4 WIP: issue #701 (mimo swarm)
Some checks failed
CI / test (pull_request) Failing after 8s
CI / validate (pull_request) Failing after 13s
Review Approval Gate / verify-review (pull_request) Failing after 3s
2026-04-10 19:48:31 -04:00
4 changed files with 176 additions and 119 deletions

79
app.js
View File

@@ -1,3 +1,5 @@
shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory
chdir: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
@@ -1982,24 +1984,67 @@ function setupControls() {
document.getElementById('chat-quick-actions').addEventListener('click', (e) => {
const btn = e.target.closest('.quick-action-btn');
if (!btn) return;
const action = btn.dataset.action;
switch(action) {
case 'status':
sendChatMessage("Timmy, what is the current system status?");
break;
case 'agents':
sendChatMessage("Timmy, check on all active agents.");
break;
case 'portals':
openPortalAtlas();
break;
case 'help':
sendChatMessage("Timmy, I need assistance with Nexus navigation.");
break;
}
handleQuickAction(btn.dataset.action);
});
// ═══ QUICK ACTION HANDLER ═══
function handleQuickAction(action) {
switch(action) {
case 'status': {
const portalCount = portals.length;
const onlinePortals = portals.filter(p => p.userData && p.userData.status === 'online').length;
const agentCount = agents.length;
const wsState = wsConnected ? 'ONLINE' : 'OFFLINE';
const wsColor = wsConnected ? '#4af0c0' : '#ff4466';
addChatMessage('system', `[SYSTEM STATUS]`);
addChatMessage('timmy', `Nexus operational. ${portalCount} portals registered (${onlinePortals} online). ${agentCount} agent presences active. Hermes WebSocket: ${wsState}. Navigation mode: ${NAV_MODES[navModeIdx].toUpperCase()}. Performance tier: ${performanceTier.toUpperCase()}.`);
break;
}
case 'agents': {
addChatMessage('system', `[AGENT ROSTER]`);
if (agents.length === 0) {
addChatMessage('timmy', 'No active agent presences detected in the Nexus. The thought stream and harness pulse are the primary indicators of system activity.');
} else {
const roster = agents.map(a => `- ${(a.userData && a.userData.name) || a.name || 'Unknown'}: ${(a.userData && a.userData.status) || 'active'}`).join('\n');
addChatMessage('timmy', `Active agents:\n${roster}`);
}
break;
}
case 'portals':
openPortalAtlas();
break;
case 'heartbeat': {
const agentLog = document.getElementById('agent-log-content');
const recentEntries = agentLog ? agentLog.querySelectorAll('.agent-log-entry') : [];
const entryCount = recentEntries.length;
addChatMessage('system', `[HEARTBEAT INSPECTION]`);
addChatMessage('timmy', `Hermes heartbeat ${wsConnected ? 'active' : 'inactive'}. ${entryCount} recent entries in thought stream. WebSocket reconnect timer: ${wsReconnectTimer ? 'active' : 'idle'}. Harness pulse mesh: ${harnessPulseMesh ? 'rendering' : 'standby'}.`);
break;
}
case 'thoughts': {
const agentLog = document.getElementById('agent-log-content');
const entries = agentLog ? Array.from(agentLog.querySelectorAll('.agent-log-entry')).slice(0, 5) : [];
addChatMessage('system', `[THOUGHT STREAM]`);
if (entries.length === 0) {
addChatMessage('timmy', 'The thought stream is quiet. No recent agent entries detected.');
} else {
const summary = entries.map(e => '> ' + e.textContent.trim()).join('\n');
addChatMessage('timmy', `Recent thoughts:\n${summary}`);
}
break;
}
case 'help': {
addChatMessage('system', `[NEXUS HELP]`);
addChatMessage('timmy', `Navigation: WASD to move, mouse to look around.\n` +
`Press V to cycle: Walk / Orbit / Fly mode.\n` +
`Enter to chat. Escape to close overlays.\n` +
`Press F near a portal to enter. Press E near a vision point to read.\n` +
`Press Tab for Portal Atlas.\n` +
`The Batcave Terminal shows system logs. The Workshop Terminal shows tool output.`);
break;
}
}
}
document.getElementById('portal-close-btn').addEventListener('click', closePortalOverlay);
document.getElementById('vision-close-btn').addEventListener('click', closeVisionOverlay);

View File

@@ -1,3 +1,5 @@
shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory
chdir: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
@@ -143,10 +145,39 @@
</div>
</div>
<div id="chat-quick-actions" class="chat-quick-actions">
<button class="quick-action-btn" data-action="status">System Status</button>
<button class="quick-action-btn" data-action="agents">Agent Check</button>
<button class="quick-action-btn" data-action="portals">Portal Atlas</button>
<button class="quick-action-btn" data-action="help">Help</button>
<div class="starter-label">STARTER PROMPTS</div>
<div class="starter-grid">
<button class="starter-btn" data-action="heartbeat" title="Check Timmy heartbeat and system health">
<span class="starter-icon"></span>
<span class="starter-text">Inspect Heartbeat</span>
<span class="starter-desc">System health &amp; connectivity</span>
</button>
<button class="starter-btn" data-action="portals" title="Browse the portal atlas">
<span class="starter-icon">🌐</span>
<span class="starter-text">Portal Atlas</span>
<span class="starter-desc">Browse connected worlds</span>
</button>
<button class="starter-btn" data-action="agents" title="Check active agent status">
<span class="starter-icon"></span>
<span class="starter-text">Agent Status</span>
<span class="starter-desc">Who is in the fleet</span>
</button>
<button class="starter-btn" data-action="memory" title="View memory crystals">
<span class="starter-icon"></span>
<span class="starter-text">Memory Crystals</span>
<span class="starter-desc">Inspect stored knowledge</span>
</button>
<button class="starter-btn" data-action="ask" title="Ask Timmy anything">
<span class="starter-icon"></span>
<span class="starter-text">Ask Timmy</span>
<span class="starter-desc">Start a conversation</span>
</button>
<button class="starter-btn" data-action="sovereignty" title="Learn about sovereignty">
<span class="starter-icon"></span>
<span class="starter-text">Sovereignty</span>
<span class="starter-desc">What this space is</span>
</button>
</div>
</div>
<div class="chat-input-row">
<input type="text" id="chat-input" class="chat-input" placeholder="Speak to Timmy..." autocomplete="off">

View File

@@ -140,47 +140,6 @@ const SpatialMemory = (() => {
return new THREE.OctahedronGeometry(size, 0);
}
// ─── TRUST-BASED VISUALS ─────────────────────────────
// Wire crystal visual properties to fact trust score (0.0-1.0).
// Issue #1166: Trust > 0.8 = bright glow/full opacity,
// 0.5-0.8 = medium/80%, < 0.5 = dim/40%, < 0.3 = near-invisible pulsing red.
function _getTrustVisuals(trust, regionColor) {
const t = Math.max(0, Math.min(1, trust));
if (t >= 0.8) {
return {
opacity: 1.0,
emissiveIntensity: 2.0 * t,
emissiveColor: regionColor,
lightIntensity: 1.2,
glowDesc: 'high'
};
} else if (t >= 0.5) {
return {
opacity: 0.8,
emissiveIntensity: 1.2 * t,
emissiveColor: regionColor,
lightIntensity: 0.6,
glowDesc: 'medium'
};
} else if (t >= 0.3) {
return {
opacity: 0.4,
emissiveIntensity: 0.5 * t,
emissiveColor: regionColor,
lightIntensity: 0.2,
glowDesc: 'dim'
};
} else {
return {
opacity: 0.15,
emissiveIntensity: 0.3,
emissiveColor: 0xff2200,
lightIntensity: 0.1,
glowDesc: 'untrusted'
};
}
}
// ─── REGION MARKER ───────────────────────────────────
function createRegionMarker(regionKey, region) {
const cx = region.center[0];
@@ -257,20 +216,17 @@ const SpatialMemory = (() => {
const region = REGIONS[mem.category] || REGIONS.working;
const pos = mem.position || _assignPosition(mem.category, mem.id);
const strength = Math.max(0.05, Math.min(1, mem.strength != null ? mem.strength : 0.7));
const trust = mem.trust != null ? Math.max(0, Math.min(1, mem.trust)) : 0.7;
const size = 0.2 + strength * 0.3;
const tv = _getTrustVisuals(trust, region.color);
const geo = createCrystalGeometry(size);
const mat = new THREE.MeshStandardMaterial({
color: region.color,
emissive: tv.emissiveColor,
emissiveIntensity: tv.emissiveIntensity,
emissive: region.color,
emissiveIntensity: 1.5 * strength,
metalness: 0.6,
roughness: 0.15,
transparent: true,
opacity: tv.opacity
opacity: 0.5 + strength * 0.4
});
const crystal = new THREE.Mesh(geo, mat);
@@ -283,12 +239,10 @@ const SpatialMemory = (() => {
region: mem.category,
pulse: Math.random() * Math.PI * 2,
strength: strength,
trust: trust,
glowDesc: tv.glowDesc,
createdAt: mem.timestamp || new Date().toISOString()
};
const light = new THREE.PointLight(tv.emissiveColor, tv.lightIntensity, 5);
const light = new THREE.PointLight(region.color, 0.8 * strength, 5);
crystal.add(light);
_scene.add(crystal);
@@ -383,16 +337,8 @@ const SpatialMemory = (() => {
mesh.scale.setScalar(pulse);
if (mesh.material) {
const trust = mesh.userData.trust != null ? mesh.userData.trust : 0.7;
const base = mesh.userData.strength || 0.7;
if (trust < 0.3) {
// Low trust: pulsing red — visible warning
const pulseAlpha = 0.15 + Math.sin(mesh.userData.pulse * 2.0) * 0.15;
mesh.material.emissiveIntensity = 0.3 + Math.sin(mesh.userData.pulse * 2.0) * 0.3;
mesh.material.opacity = pulseAlpha;
} else {
mesh.material.emissiveIntensity = 1.0 + Math.sin(mesh.userData.pulse * 0.7) * 0.5 * base;
}
mesh.material.emissiveIntensity = 1.0 + Math.sin(mesh.userData.pulse * 0.7) * 0.5 * base;
}
});
@@ -422,42 +368,6 @@ const SpatialMemory = (() => {
return REGIONS;
}
// ─── UPDATE VISUAL PROPERTIES ────────────────────────
// Re-render crystal when trust/strength change (no position move).
function updateMemoryVisual(memId, updates) {
const obj = _memoryObjects[memId];
if (!obj) return false;
const mesh = obj.mesh;
const region = REGIONS[obj.region] || REGIONS.working;
if (updates.trust != null) {
const trust = Math.max(0, Math.min(1, updates.trust));
mesh.userData.trust = trust;
obj.data.trust = trust;
const tv = _getTrustVisuals(trust, region.color);
mesh.material.emissive = new THREE.Color(tv.emissiveColor);
mesh.material.emissiveIntensity = tv.emissiveIntensity;
mesh.material.opacity = tv.opacity;
mesh.userData.glowDesc = tv.glowDesc;
if (mesh.children.length > 0 && mesh.children[0].isPointLight) {
mesh.children[0].intensity = tv.lightIntensity;
mesh.children[0].color = new THREE.Color(tv.emissiveColor);
}
}
if (updates.strength != null) {
const strength = Math.max(0.05, Math.min(1, updates.strength));
mesh.userData.strength = strength;
obj.data.strength = strength;
}
_dirty = true;
saveToStorage();
console.info('[Mnemosyne] Visual updated:', memId, 'trust:', mesh.userData.trust, 'glow:', mesh.userData.glowDesc);
return true;
}
// ─── QUERY ───────────────────────────────────────────
function getMemoryAtPosition(position, maxDist) {
maxDist = maxDist || 2;
@@ -597,7 +507,6 @@ const SpatialMemory = (() => {
source: o.data.source || 'unknown',
timestamp: o.data.timestamp || o.mesh.userData.createdAt,
strength: o.mesh.userData.strength || 0.7,
trust: o.mesh.userData.trust != null ? o.mesh.userData.trust : 0.7,
connections: o.data.connections || []
}))
};
@@ -744,7 +653,7 @@ const SpatialMemory = (() => {
}
return {
init, placeMemory, removeMemory, update, updateMemoryVisual,
init, placeMemory, removeMemory, update,
getMemoryAtPosition, getRegionAtPosition, getMemoriesInRegion, getAllMemories,
getCrystalMeshes, getMemoryFromMesh, highlightMemory, clearHighlight, getSelectedId,
exportIndex, importIndex, searchNearby, REGIONS,

View File

@@ -983,7 +983,7 @@ canvas#nexus-canvas {
.chat-quick-actions {
display: flex;
flex-wrap: wrap;
flex-direction: column;
gap: 6px;
padding: 8px 12px;
border-top: 1px solid var(--color-border);
@@ -991,6 +991,75 @@ canvas#nexus-canvas {
pointer-events: auto;
}
.chat-quick-actions.hidden {
display: none;
}
.starter-label {
font-family: var(--font-display);
font-size: 9px;
letter-spacing: 0.15em;
color: var(--color-primary-dim);
text-transform: uppercase;
padding: 0 2px;
}
.starter-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
}
.starter-btn {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1px;
background: rgba(74, 240, 192, 0.06);
border: 1px solid rgba(74, 240, 192, 0.15);
color: var(--color-primary);
font-family: var(--font-body);
padding: 6px 8px;
cursor: pointer;
transition: all var(--transition-ui);
text-align: left;
}
.starter-btn:hover {
background: rgba(74, 240, 192, 0.15);
border-color: var(--color-primary);
color: #fff;
}
.starter-btn:hover .starter-icon {
color: #fff;
}
.starter-btn:active {
transform: scale(0.97);
}
.starter-icon {
font-size: 12px;
color: var(--color-primary);
line-height: 1;
}
.starter-text {
font-size: 10px;
font-weight: 600;
white-space: nowrap;
}
.starter-desc {
font-size: 8px;
color: rgba(74, 240, 192, 0.5);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
/* Add hover effect for MemPalace mining button */
.quick-action-btn:hover {
background: var(--color-primary-dim);
@@ -1136,6 +1205,9 @@ canvas#nexus-canvas {
.hud-location {
font-size: var(--text-xs);
}
.starter-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {