feat(mission-control): restore MC tabs - temple, office, memory, claude, pdf-viewer, resume, resume-upload, temple-3d, demos

Also added:
- Memory API endpoints
- Briefs API endpoints
- AnveVoice stats API
- Claude spawn API
- TTS proxy
- Cleopatra voice widget
- api-auth middleware
This commit is contained in:
2026-03-23 16:30:44 +01:00
parent d5575b58e3
commit 45af56d9cf
30 changed files with 5092 additions and 715 deletions
+23
View File
@@ -0,0 +1,23 @@
import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
export async function GET(
request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
try {
const { date } = await params;
const memoryDir = '/root/.openclaw/workspace/memory';
const filePath = path.join(memoryDir, `${date}.md`);
if (!fs.existsSync(filePath)) {
return NextResponse.json({ content: '', error: 'Not found' }, { status: 404 });
}
const content = fs.readFileSync(filePath, 'utf8');
return NextResponse.json({ content, date });
} catch (error) {
return NextResponse.json({ content: '', error: 'Failed to load' }, { status: 500 });
}
}
+47
View File
@@ -0,0 +1,47 @@
import { NextResponse } from 'next/server';
import * as fs from 'fs';
import * as path from 'path';
const AGENT_SECRET = process.env.AGENT_SECRET || 'agent-mc-secret-2026';
export async function POST(request: Request) {
// Check for agent authorization
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${AGENT_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const body = await request.json();
const { agent, time, content, tags } = body;
if (!agent || !content) {
return NextResponse.json({ error: 'Missing agent or content' }, { status: 400 });
}
// Get today's date
const today = new Date().toISOString().split('T')[0];
const memoryDir = '/root/.openclaw/workspace/memory';
const filePath = path.join(memoryDir, `${today}.md`);
// Build entry
const tagsStr = tags && tags.length > 0
? `\n[Tags: ${tags.map((t: string) => '#' + t).join(', ')}]`
: '';
const entry = `\n\n## ${agent} - ${time || new Date().toTimeString().slice(0,5)}\n${tagsStr}\n${content}`;
// Append to file
if (fs.existsSync(filePath)) {
fs.appendFileSync(filePath, entry, 'utf8');
} else {
// Create new file with header
const header = `# Daily Memory - ${today}\n`;
fs.writeFileSync(filePath, header + entry, 'utf8');
}
return NextResponse.json({ success: true, date: today, entry: entry.trim() });
} catch (error) {
console.error('Memory append error:', error);
return NextResponse.json({ error: 'Failed to append memory' }, { status: 500 });
}
}
+141
View File
@@ -0,0 +1,141 @@
import { NextResponse } from 'next/server';
import * as fs from 'fs';
import * as path from 'path';
const API_SECRET = process.env.API_SECRET || 'horus-mc-secret-2026';
function checkAuth(request: Request): boolean {
const authHeader = request.headers.get('authorization');
return authHeader === `Bearer ${API_SECRET}`;
}
const MAIN_AGENTS = ["horus", "cleopatra", "amun"];
const AGENTS = [
{ id: "horus", name: "Horus", icon: "👁️", color: "#3b82f6" },
{ id: "cleopatra", name: "Cleopatra", icon: "👸", color: "#a855f7" },
{ id: "amun", name: "Amun", icon: "👑", color: "#f59e0b" },
{ id: "anubis", name: "Anubis", icon: "🐕", color: "#22c55e" },
{ id: "thoth", name: "Thoth", icon: "📚", color: "#06b6d4" },
{ id: "ptah", name: "Ptah", icon: "🎨", color: "#f97316" },
{ id: "seshat", name: "Seshat", icon: "📝", color: "#ec4899" },
{ id: "hathor", name: "Hathor", icon: "💕", color: "#ef4444" },
{ id: "sekhmet", name: "Sekhmet", icon: "⚔️", color: "#94a3b8" },
{ id: "maat", name: "Maat", icon: "⚖️", color: "#64748b" },
];
interface AgentInfo {
id: string;
name: string;
icon: string;
color: string;
}
function getAgentInfo(agentName: string): AgentInfo {
const lower = agentName.toLowerCase();
const found = AGENTS.find(a => lower.includes(a.id) || lower.includes(a.name.toLowerCase()));
return found || { id: "unknown", name: agentName, icon: "🤖", color: "#64748b" };
}
export async function GET(request: Request) {
// Check auth
if (!checkAuth(request)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const memoryDir = '/root/.openclaw/workspace/memory';
const briefsMorningDir = '/root/.openclaw/workspace/briefs/morning';
const briefsEodDir = '/root/.openclaw/workspace/briefs/eod';
const files = fs.readdirSync(memoryDir)
.filter(f => f.endsWith('.md'))
.filter(f => /^\d{4}-\d{2}-\d{2}$/.test(f.replace('.md', '')));
const days: any[] = [];
const allTags: string[] = [];
for (const file of files) {
const date = file.replace('.md', '');
const filePath = path.join(memoryDir, file);
const content = fs.readFileSync(filePath, 'utf8');
const entries: any[] = [];
const lines = content.split('\n');
let currentEntry: any = null;
for (const line of lines) {
const agentMatch = line.match(/^##\s*(\w+)\s*[-]\s*(\d{2}:\d{2})/);
if (agentMatch) {
if (currentEntry?.content?.trim()) {
entries.push(currentEntry);
}
const agentInfo = getAgentInfo(agentMatch[1]);
currentEntry = {
agent: agentInfo.name,
agentIcon: agentInfo.icon,
agentColor: agentInfo.color,
time: agentMatch[2],
content: '',
tags: [],
isMainAgent: MAIN_AGENTS.includes(agentInfo.id),
};
} else if (currentEntry) {
const trimmed = line.trim();
if (!trimmed) continue;
const standaloneTagsMatch = trimmed.match(/^\[Tags:\s*(.+?)\]$/);
if (standaloneTagsMatch) {
const tags = standaloneTagsMatch[1]
.split(',')
.map((t: string) => t.trim().replace(/^#/, '').trim())
.filter(Boolean);
currentEntry.tags = [...new Set([...currentEntry.tags, ...tags])];
tags.forEach((t: string) => {
if (!allTags.includes(t)) allTags.push(t);
});
} else {
const inlineTagsMatch = trimmed.match(/\[Tags:\s*(.+?)\]/);
if (inlineTagsMatch) {
const tags = inlineTagsMatch[1]
.split(',')
.map((t: string) => t.trim().replace(/^#/, '').trim())
.filter(Boolean);
currentEntry.tags = [...new Set([...currentEntry.tags, ...tags])];
tags.forEach((t: string) => {
if (!allTags.includes(t)) allTags.push(t);
});
}
const cleanLine = trimmed.replace(/\[Tags:.*?\]/g, '').trim();
if (cleanLine) {
currentEntry.content += (currentEntry.content ? '\n' : '') + cleanLine;
}
}
}
}
if (currentEntry?.content?.trim()) {
entries.push(currentEntry);
}
const hasMorning = fs.existsSync(path.join(briefsMorningDir, `${date}.md`));
const hasEod = fs.existsSync(path.join(briefsEodDir, `${date}.md`));
days.push({
date,
entries,
hasBriefs: { morning: hasMorning, eod: hasEod },
});
}
days.sort((a, b) => b.date.localeCompare(a.date));
return NextResponse.json({
days,
allTags,
totalDays: days.length,
});
} catch (error) {
console.error('Memory list error:', error);
return NextResponse.json({ days: [], allTags: [], error: 'Failed to load memory' }, { status: 500 });
}
}