Compare commits
4 Commits
mimo/creat
...
mimo/build
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff3691e81e | ||
| 6c67002161 | |||
| 43699c83cf | |||
|
|
91f0bcb034 |
87
app.js
87
app.js
@@ -55,6 +55,11 @@ let _clickStartX = 0, _clickStartY = 0; // Mnemosyne: click-vs-drag detection
|
||||
let loadProgress = 0;
|
||||
let performanceTier = 'high';
|
||||
|
||||
/** Escape HTML entities for safe innerHTML insertion. */
|
||||
function escHtml(s) {
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// ═══ HERMES WS STATE ═══
|
||||
let hermesWs = null;
|
||||
let wsReconnectTimer = null;
|
||||
@@ -65,6 +70,8 @@ let evenniaConnected = false;
|
||||
let evenniaStaleTimer = null;
|
||||
const EVENNIA_STALE_MS = 60000; // mark stale after 60s without update
|
||||
let recentToolOutputs = [];
|
||||
let actionStreamEntries = []; // Evennia command/result flow for action stream panel
|
||||
let actionStreamRoom = ''; // Current room from movement events
|
||||
let workshopPanelCtx = null;
|
||||
let workshopPanelTexture = null;
|
||||
let workshopPanelCanvas = null;
|
||||
@@ -1579,15 +1586,22 @@ function createPortal(config) {
|
||||
// Label
|
||||
const labelCanvas = document.createElement('canvas');
|
||||
labelCanvas.width = 512;
|
||||
labelCanvas.height = 64;
|
||||
labelCanvas.height = 96;
|
||||
const lctx = labelCanvas.getContext('2d');
|
||||
lctx.font = 'bold 32px "Orbitron", sans-serif';
|
||||
lctx.fillStyle = '#' + portalColor.getHexString();
|
||||
lctx.textAlign = 'center';
|
||||
lctx.fillText(`◈ ${config.name.toUpperCase()}`, 256, 42);
|
||||
lctx.fillText(`◈ ${config.name.toUpperCase()}`, 256, 36);
|
||||
// Role tag (timmy/reflex/pilot) — defines portal ownership boundary
|
||||
if (config.role) {
|
||||
const roleColors = { timmy: '#4af0c0', reflex: '#ff4466', pilot: '#ffd700' };
|
||||
lctx.font = 'bold 18px "Orbitron", sans-serif';
|
||||
lctx.fillStyle = roleColors[config.role] || '#888888';
|
||||
lctx.fillText(config.role.toUpperCase(), 256, 68);
|
||||
}
|
||||
const labelTex = new THREE.CanvasTexture(labelCanvas);
|
||||
const labelMat = new THREE.MeshBasicMaterial({ map: labelTex, transparent: true, side: THREE.DoubleSide });
|
||||
const labelMesh = new THREE.Mesh(new THREE.PlaneGeometry(4, 0.5), labelMat);
|
||||
const labelMesh = new THREE.Mesh(new THREE.PlaneGeometry(4, 0.75), labelMat);
|
||||
labelMesh.position.y = 7.5;
|
||||
group.add(labelMesh);
|
||||
|
||||
@@ -2209,6 +2223,71 @@ function handleHermesMessage(data) {
|
||||
}
|
||||
} else if (data.type && data.type.startsWith('evennia.')) {
|
||||
handleEvenniaEvent(data);
|
||||
// Evennia event bridge — process command/result/room fields if present
|
||||
handleEvenniaEvent(data);
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// TIMMY ACTION STREAM — EVENNIA COMMAND FLOW
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
const MAX_ACTION_STREAM = 8;
|
||||
|
||||
/**
|
||||
* Add an entry to the action stream panel.
|
||||
* @param {'cmd'|'result'|'room'} type
|
||||
* @param {string} text
|
||||
*/
|
||||
function addActionStreamEntry(type, text) {
|
||||
const entry = { type, text, ts: Date.now() };
|
||||
actionStreamEntries.unshift(entry);
|
||||
if (actionStreamEntries.length > MAX_ACTION_STREAM) actionStreamEntries.pop();
|
||||
renderActionStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current room display in the action stream.
|
||||
* @param {string} room
|
||||
*/
|
||||
function setActionStreamRoom(room) {
|
||||
actionStreamRoom = room;
|
||||
const el = document.getElementById('action-stream-room');
|
||||
if (el) el.textContent = room ? `◈ ${room}` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the action stream panel entries.
|
||||
*/
|
||||
function renderActionStream() {
|
||||
const el = document.getElementById('action-stream-content');
|
||||
if (!el) return;
|
||||
el.innerHTML = actionStreamEntries.map(e => {
|
||||
const ts = new Date(e.ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
const cls = e.type === 'cmd' ? 'as-cmd' : e.type === 'result' ? 'as-result' : 'as-room';
|
||||
const prefix = e.type === 'cmd' ? '>' : e.type === 'result' ? '←' : '◈';
|
||||
return `<div class="as-entry ${cls}"><span class="as-prefix">${prefix}</span> <span class="as-text">${escHtml(e.text)}</span> <span class="as-ts">${ts}</span></div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Evennia-specific fields from Hermes WS messages.
|
||||
* Called from handleHermesMessage for any message carrying evennia metadata.
|
||||
*/
|
||||
function handleEvenniaEvent(data) {
|
||||
if (data.evennia_command) {
|
||||
addActionStreamEntry('cmd', data.evennia_command);
|
||||
}
|
||||
if (data.evennia_result) {
|
||||
const excerpt = typeof data.evennia_result === 'string'
|
||||
? data.evennia_result.substring(0, 120)
|
||||
: JSON.stringify(data.evennia_result).substring(0, 120);
|
||||
addActionStreamEntry('result', excerpt);
|
||||
}
|
||||
if (data.evennia_room) {
|
||||
setActionStreamRoom(data.evennia_room);
|
||||
addActionStreamEntry('room', `Moved to: ${data.evennia_room}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3088,6 +3167,8 @@ function populateAtlas() {
|
||||
<div class="atlas-card-footer">
|
||||
<div class="atlas-card-coord">X:${config.position.x} Z:${config.position.z}</div>
|
||||
<div class="atlas-card-action">${actionLabel} →</div>
|
||||
${config.role ? `<div class="atlas-card-role role-${config.role}">${config.role.toUpperCase()}</div>` : ''}
|
||||
<div class="atlas-card-type">${config.destination?.type?.toUpperCase() || 'UNKNOWN'}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
110
index.html
110
index.html
@@ -170,6 +170,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timmy Action Stream (Evennia command/result flow) -->
|
||||
<div id="action-stream" class="action-stream">
|
||||
<div class="action-stream-header">
|
||||
<span class="action-stream-icon">⚡</span> TIMMY ACTION STREAM
|
||||
</div>
|
||||
<div id="action-stream-room" class="action-stream-room"></div>
|
||||
<div id="action-stream-content" class="action-stream-content"></div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom: Chat Interface -->
|
||||
<div id="chat-panel" class="chat-panel">
|
||||
<div class="chat-header">
|
||||
@@ -375,6 +384,107 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="branch-policy" style="margin-top: 10px; font-size: 12px; color: #aaa;">
|
||||
<strong>BRANCH PROTECTION POLICY</strong><br>
|
||||
<ul style="margin:0; padding-left:15px;">
|
||||
<li>• Require PR for merge ✅</li>
|
||||
<li>• Require 1 approval ✅</li>
|
||||
<li>• Dismiss stale approvals ✅</li>
|
||||
<li>• Require CI ✅ (where available)</li>
|
||||
<li>• Block force push ✅</li>
|
||||
<li>• Block branch deletion ✅</li>
|
||||
<li>• Weekly audit for unreviewed merges ✅</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="mem-palace-container" class="mem-palace-ui">
|
||||
<div class="mem-palace-header">
|
||||
<span id="mem-palace-status">MEMPALACE</span>
|
||||
<button onclick="mineMemPalaceContent()" class="mem-palace-btn">Mine Chat</button>
|
||||
</div>
|
||||
<div class="mem-palace-stats">
|
||||
<div>Compression: <span id="compression-ratio">--</span>x</div>
|
||||
<div>Docs mined: <span id="docs-mined">0</span></div>
|
||||
<div>AAAK size: <span id="aaak-size">0B</span></div>
|
||||
</div>
|
||||
<div class="mem-palace-logs" id="mem-palace-logs"></div>
|
||||
</div>
|
||||
<div class="default-reviewers" style="margin-top: 8px; font-size: 12px; color: #aaa;">
|
||||
<strong>DEFAULT REVIEWERS</strong><br>
|
||||
<ul style="margin:0; padding-left:15px;">
|
||||
<li>• <span style="color:#4af0c0;">@perplexity</span> (QA gate on all repos)</li>
|
||||
<li>• <span style="color:#7b5cff;">@Timmy</span> (owner gate on hermes-agent)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="implementation-status" style="margin-top: 10px; font-size: 12px; color: #aaa;">
|
||||
<strong>IMPLEMENTATION STATUS</strong><br>
|
||||
<div style="margin-top: 5px; display: flex; flex-direction: column; gap: 2px;">
|
||||
<div>• <span style="color:#4af0c0;">hermes-agent</span>: Require PR + 1 approval + CI ✅</div>
|
||||
<div>• <span style="color:#7b5cff;">the-nexus</span>: Require PR + 1 approval ⚠️ (CI disabled)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mem-palace-status" style="position:fixed; right:24px; top:64px; background:rgba(74,240,192,0.1); color:#4af0c0; padding:6px 12px; border-radius:4px; font-family:'Orbitron', sans-serif; font-size:10px; letter-spacing:0.1em;">
|
||||
MEMPALACE INIT
|
||||
</div>
|
||||
<div>• <span style="color:#ffd700;">timmy-home</span>: Require PR + 1 approval ✅</div>
|
||||
<div>• <span style="color:#ab8d00;">timmy-config</span>: Require PR + 1 approval ✅</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mem-palace-container" class="mem-palace-ui">
|
||||
<div class="mem-palace-header">MemPalace <span id="mem-palace-status">Initializing...</span></div>
|
||||
<div class="mem-palace-stats">
|
||||
<div>Compression: <span id="compression-ratio">--</span>x</div>
|
||||
<div>Docs mined: <span id="docs-mined">0</span></div>
|
||||
<div>AAAK size: <span id="aaak-size">0B</span></div>
|
||||
</div>
|
||||
<div class="mem-palace-actions">
|
||||
<button id="mine-now-btn" class="mem-palace-btn" onclick="mineChatToMemPalace()">Mine Chat</button>
|
||||
<button class="mem-palace-btn" onclick="searchMemPalace()">Search</button>
|
||||
</div>
|
||||
<div id="mem-palace-logs" class="mem-palace-logs"></div>
|
||||
</div>
|
||||
<div id="mem-palace-controls" style="position:fixed; right:24px; top:54px; background:rgba(74,240,192,0.05); padding:4px 8px; font-family:'JetBrains Mono',monospace; font-size:11px; border-left:2px solid #4af0c0;">
|
||||
<button onclick="mineMemPalace()">Mine Chat</button>
|
||||
<button onclick="searchMemPalace()">Search</button>
|
||||
</div>
|
||||
<div id="mempalace-results" style="position:fixed; right:24px; top:84px; max-height:200px; overflow-y:auto; background:rgba(0,0,0,0.3); padding:8px; font-family:'JetBrains Mono',monospace; font-size:11px; color:#e0f0ff; border-left:2px solid #4af0c0;"></div>
|
||||
<div id="mem-palace-controls" style="position:fixed; right:24px; top:54px; background:rgba(74,240,192,0.05); padding:4px 8px; font-family:'JetBrains Mono',monospace; font-size:10px; border-left:2px solid #4af0c0;">
|
||||
<button class="mem-palace-mining-btn" onclick="mineChatToMemPalace()">Mine Chat</button>
|
||||
<button onclick="searchMemPalace()">Search</button>
|
||||
</div>
|
||||
<div id="mempalace-results" style="position:fixed; right:24px; top:84px; max-height:200px; overflow-y:auto; background:rgba(0,0,0,0.3); padding:8px; font-family:'JetBrains Mono',monospace; font-size:11px; color:#e0f0ff; border-left:2px solid #4af0c0;"></div>
|
||||
|
||||
```
|
||||
|
||||
index.html
|
||||
```html
|
||||
|
||||
<div class="branch-policy" style="margin-top: 10px; font-size: 12px; color: #aaa;">
|
||||
<strong>BRANCH PROTECTION POLICY</strong><br>
|
||||
<ul style="margin:0; padding-left:15px;">
|
||||
<li>• Require PR for merge ✅</li>
|
||||
<li>• Require 1 approval ✅</li>
|
||||
<li>• Dismiss stale approvals ✅</li>
|
||||
<li>• Require CI ✅ (where available)</li>
|
||||
<li>• Block force push ✅</li>
|
||||
<li>• Block branch deletion ✅</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="default-reviewers" style="margin-top: 8px;">
|
||||
<strong>DEFAULT REVIEWERS</strong><br>
|
||||
<ul style="margin:0; padding-left:15px;">
|
||||
<li>• <span style="color:#4af0c0;">@perplexity</span> (QA gate on all repos)</li>
|
||||
<li>• <span style="color:#7b5cff;">@Timmy</span> (owner gate on hermes-agent)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="implementation-status" style="margin-top: 10px;">
|
||||
<strong>IMPLEMENTATION STATUS</strong><br>
|
||||
<div style="margin-top: 5px; display: flex; flex-direction: column; gap: 2px;">
|
||||
<div>• <span style="color:#4af0c0;">hermes-agent</span>: Require PR + 1 approval + CI ✅</div>
|
||||
<div>• <span style="color:#7b5cff;">the-nexus</span>: Require PR + 1 approval ⚠<> (CI disabled)</div>
|
||||
<div>• <span style="color:#ffd700;">timmy-home</span>: Require PR + 1 approval ✅</div>
|
||||
<div>• <span style="color:#ab8d00;">timmy-config</span>: Require PR + 1 approval ✅</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="./app.js"></script>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"description": "The Vvardenfell harness. Ash storms and ancient mysteries.",
|
||||
"status": "online",
|
||||
"color": "#ff6600",
|
||||
"role": "pilot",
|
||||
"position": { "x": 15, "y": 0, "z": -10 },
|
||||
"rotation": { "y": -0.5 },
|
||||
"portal_type": "game-world",
|
||||
@@ -36,6 +37,7 @@
|
||||
"description": "Calradia battle harness. Massive armies, tactical command.",
|
||||
"status": "downloaded",
|
||||
"color": "#ffd700",
|
||||
"role": "pilot",
|
||||
"position": { "x": -15, "y": 0, "z": -10 },
|
||||
"rotation": { "y": 0.5 },
|
||||
"portal_type": "game-world",
|
||||
@@ -67,6 +69,7 @@
|
||||
"description": "The creative harness. Build, script, and manifest.",
|
||||
"status": "online",
|
||||
"color": "#4af0c0",
|
||||
"role": "timmy",
|
||||
"position": { "x": 0, "y": 0, "z": -20 },
|
||||
"rotation": { "y": 0 },
|
||||
"destination": {
|
||||
@@ -81,6 +84,7 @@
|
||||
"description": "The repository of all knowledge. History, logs, and ancient data.",
|
||||
"status": "online",
|
||||
"color": "#0066ff",
|
||||
"role": "timmy",
|
||||
"position": { "x": 25, "y": 0, "z": 0 },
|
||||
"rotation": { "y": -1.57 },
|
||||
"destination": {
|
||||
@@ -95,6 +99,7 @@
|
||||
"description": "A sanctuary for reflection and digital peace.",
|
||||
"status": "online",
|
||||
"color": "#ffd700",
|
||||
"role": "timmy",
|
||||
"position": { "x": -25, "y": 0, "z": 0 },
|
||||
"rotation": { "y": 1.57 },
|
||||
"destination": {
|
||||
@@ -109,6 +114,7 @@
|
||||
"description": "The open nexus. A place for agents to gather and connect.",
|
||||
"status": "online",
|
||||
"color": "#4af0c0",
|
||||
"role": "reflex",
|
||||
"position": { "x": 15, "y": 0, "z": 10 },
|
||||
"rotation": { "y": -2.5 },
|
||||
"destination": {
|
||||
@@ -123,6 +129,7 @@
|
||||
"description": "The transition point. Entry and exit from the Nexus core.",
|
||||
"status": "standby",
|
||||
"color": "#ff4466",
|
||||
"role": "reflex",
|
||||
"position": { "x": -15, "y": 0, "z": 10 },
|
||||
"rotation": { "y": 2.5 },
|
||||
"destination": {
|
||||
|
||||
81
style.css
81
style.css
@@ -384,6 +384,19 @@ canvas#nexus-canvas {
|
||||
color: rgba(160, 184, 208, 0.6);
|
||||
}
|
||||
|
||||
.atlas-card-role {
|
||||
font-family: var(--font-display);
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.atlas-card-role.role-timmy { color: #4af0c0; background: rgba(74, 240, 192, 0.12); border: 1px solid rgba(74, 240, 192, 0.3); }
|
||||
.atlas-card-role.role-reflex { color: #ff4466; background: rgba(255, 68, 102, 0.12); border: 1px solid rgba(255, 68, 102, 0.3); }
|
||||
.atlas-card-role.role-pilot { color: #ffd700; background: rgba(255, 215, 0, 0.12); border: 1px solid rgba(255, 215, 0, 0.3); }
|
||||
|
||||
.atlas-footer {
|
||||
padding: 15px 30px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
@@ -836,6 +849,70 @@ canvas#nexus-canvas {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Timmy Action Stream (Evennia command/result flow) — issue #729 */
|
||||
.action-stream {
|
||||
position: absolute;
|
||||
bottom: 200px;
|
||||
right: var(--space-3);
|
||||
width: 320px;
|
||||
max-height: 260px;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
backdrop-filter: blur(8px);
|
||||
border-left: 2px solid var(--color-gold);
|
||||
padding: var(--space-3);
|
||||
font-size: 10px;
|
||||
font-family: var(--font-mono);
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.action-stream-header {
|
||||
font-family: var(--font-display);
|
||||
color: var(--color-gold);
|
||||
letter-spacing: 0.1em;
|
||||
font-size: 10px;
|
||||
margin-bottom: var(--space-2);
|
||||
opacity: 0.9;
|
||||
}
|
||||
.action-stream-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.action-stream-room {
|
||||
color: var(--color-primary);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--space-1);
|
||||
opacity: 0.9;
|
||||
}
|
||||
.action-stream-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
.as-entry {
|
||||
animation: log-fade-in 0.4s ease-out forwards;
|
||||
opacity: 0;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.as-cmd .as-prefix { color: var(--color-gold); font-weight: 700; }
|
||||
.as-cmd .as-text { color: var(--color-gold); opacity: 0.85; }
|
||||
.as-result .as-prefix { color: var(--color-primary); font-weight: 700; }
|
||||
.as-result .as-text { color: var(--color-text-muted); }
|
||||
.as-room .as-prefix { color: var(--color-secondary); font-weight: 700; }
|
||||
.as-room .as-text { color: var(--color-secondary); opacity: 0.8; }
|
||||
.as-ts {
|
||||
color: var(--color-text-muted);
|
||||
opacity: 0.4;
|
||||
font-size: 9px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* Vision Hint */
|
||||
.vision-hint {
|
||||
position: absolute;
|
||||
@@ -1239,6 +1316,10 @@ canvas#nexus-canvas {
|
||||
.hud-agent-log {
|
||||
width: 220px;
|
||||
}
|
||||
.action-stream {
|
||||
width: 240px;
|
||||
bottom: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
Reference in New Issue
Block a user